/*
	gba_nds_fat.c
	By chishm (Michael Chisholm)

	Routines for reading a compact flash card
	using the GBA Movie Player or M3.

	Some FAT routines are based on those in fat.c, which
	is part of avrlib by Pascal Stang.

	This software is completely free. No warranty is provided.
	If you use it, please give me credit and email me about your
	project at chishm@hotmail.com

	See gba_nds_fat.txt for help and license details.
*/

//---------------------------------------------------------------
// Includes

#include "gba_nds_fat.h"
#include "disc_io.h"
#include <string.h>
#ifdef NDS
 #include <nds/ipc.h>	// Time on the NDS
#endif
//----------------------------------------------------------------
// Data	types
#ifndef	NULL
 #define	NULL	0
#endif

//----------------------------------------------------------------
// NDS memory access control register
#ifdef NDS
 #ifndef WAIT_CR
  #define WAIT_CR (*(vu16*)0x04000204)
 #endif
#endif

//---------------------------------------------------------------
// Appropriate placement of CF functions and data
#ifdef NDS
 #define _VARS_IN_RAM 
#else
 #define _VARS_IN_RAM __attribute__ ((section (".sbss")))
#endif


//-----------------------------------------------------------------
// FAT constants
#define CLUSTER_EOF_16	0xFFFF
#define	CLUSTER_EOF	0x0FFFFFFF
#define CLUSTER_FREE	0x0000
#define CLUSTER_FIRST	0x0002

#define FILE_LAST 0x00
#define FILE_FREE 0xE5

#define FAT16_ROOT_DIR_CLUSTER 0x00


//-----------------------------------------------------------------
// long file name constants
#define LFN_END 0x40
#define LFN_DEL 0x80

//-----------------------------------------------------------------
// Data Structures

// Take care of packing for GCC - it doesn't obey pragma pack()
// properly for ARM targets.
#ifdef __GNUC__
 #define __PACKED //__attribute__ ((__packed__))
#else
 #define __PACKED 
 #pragma pack(1)
#endif

// Boot Sector - must be packed
typedef struct
{
	__PACKED	u8	jmpBoot[3];
	__PACKED	u8	OEMName[8];
	// BIOS Parameter Block
	__PACKED	u16	bytesPerSector;
	__PACKED	u8	sectorsPerCluster;
	__PACKED	u16	reservedSectors;
	__PACKED	u8	numFATs;
	__PACKED	u16	rootEntries;
	__PACKED	u16	numSectorsSmall;
	__PACKED	u8	mediaDesc;
	__PACKED	u16	sectorsPerFAT;
	__PACKED	u16	sectorsPerTrk;
	__PACKED	u16	numHeads;
	__PACKED	u32	numHiddenSectors;
	__PACKED	u32	numSectors;
	union	// Different types of extended BIOS Parameter Block for FAT16 and FAT32
	{
		struct  
		{
			// Ext BIOS Parameter Block for FAT16
			__PACKED	u8	driveNumber;
			__PACKED	u8	reserved1;
			__PACKED	u8	extBootSig;
			__PACKED	u32	volumeID;
			__PACKED	u8	volumeLabel[11];
			__PACKED	u8	fileSysType[8];
			// Bootcode
			__PACKED	u8	bootCode[448];
		}	fat16;
		struct  
		{
			// FAT32 extended block
			__PACKED	u32	sectorsPerFAT32;
			__PACKED	u16	extFlags;
			__PACKED	u16	fsVer;
			__PACKED	u32	rootClus;
			__PACKED	u16	fsInfo;
			__PACKED	u16	bkBootSec;
			__PACKED	u8	reserved[12];
			// Ext BIOS Parameter Block for FAT16
			__PACKED	u8	driveNumber;
			__PACKED	u8	reserved1;
			__PACKED	u8	extBootSig;
			__PACKED	u32	volumeID;
			__PACKED	u8	volumeLabel[11];
			__PACKED	u8	fileSysType[8];
			// Bootcode
			__PACKED	u8	bootCode[420];
		}	fat32;
	}	extBlock;

	__PACKED	u16	bootSig;

}	BOOT_SEC;

// Directory entry - must be packed
typedef struct
{
	__PACKED	u8	name[8];
	__PACKED	u8	ext[3];
	__PACKED	u8	attrib;
	__PACKED	u8	reserved;
	__PACKED	u8	cTime_ms;
	__PACKED	u16	cTime;
	__PACKED	u16	cDate;
	__PACKED	u16	aDate;
	__PACKED	u16	startClusterHigh;
	__PACKED	u16	mTime;
	__PACKED	u16	mDate;
	__PACKED	u16	startCluster;
	__PACKED	u32	fileSize;
}	DIR_ENT;

// Long file name directory entry - must be packed
typedef struct
{
	__PACKED	u8 ordinal;	// Position within LFN
	__PACKED	u16 char0;	
	__PACKED	u16 char1;
	__PACKED	u16 char2;
	__PACKED	u16 char3;
	__PACKED	u16 char4;
	__PACKED	u8 flag;	// Should be equal to ATTRIB_LFN
	__PACKED	u8 reserved1;	// Always 0x00
	__PACKED	u8 checkSum;	// Checksum of short file name (alias)
	__PACKED	u16 char5;
	__PACKED	u16 char6;
	__PACKED	u16 char7;
	__PACKED	u16 char8;
	__PACKED	u16 char9;
	__PACKED	u16 char10;
	__PACKED	u16 reserved2;	// Always 0x0000
	__PACKED	u16 char11;
	__PACKED	u16 char12;
}	DIR_ENT_LFN;

const char lfn_offset_table[13]={0x01,0x03,0x05,0x07,0x09,0x0E,0x10,0x12,0x14,0x16,0x18,0x1C,0x1E}; 

// End of packed structs
#ifdef __PACKED
 #undef __PACKED
#endif
#ifndef __GNUC__
 #pragma pack()
#endif

//-----------------------------------------------------------------
// Global Variables

// _VARS_IN_RAM variables are stored in the largest section of WRAM 
// available: IWRAM on NDS ARM7, EWRAM on NDS ARM9 and GBA

// Files
_VARS_IN_RAM FAT_FILE openFiles[MAX_FILES_OPEN];

// Long File names
_VARS_IN_RAM char lfnName[MAX_FILENAME_LENGTH];
bool lfnExists;

// Locations on card
int filesysRootDir;
int filesysRootDirClus;
int filesysFAT;
int filesysSecPerFAT;
int filesysNumSec;
int filesysData;
int filesysBytePerSec;
int filesysSecPerClus;
int filesysBytePerClus;

FS_TYPE filesysType = FS_UNKNOWN;
u32 filesysTotalSize;

// Info about FAT
u32 fatLastCluster;
u32 fatFirstFree;

// fatBuffer used to reduce wear on the CF card from multiple writes
_VARS_IN_RAM char fatBuffer[BYTE_PER_READ];
u32 fatBufferCurSector;

// Current working directory
u32 curWorkDirCluster;

// Position of the directory entry last retreived with FAT_GetDirEntry
u32 wrkDirCluster;
int wrkDirSector;
int wrkDirOffset;

// Global sector buffer to save on stack space
_VARS_IN_RAM unsigned char globalBuffer[BYTE_PER_READ];

//-----------------------------------------------------------------
// Functions contained in this file - predeclarations
char ucase (char character);
u16 getRTCtoFileTime (void);
u16 getRTCtoFileDate (void);

bool FAT_AddDirEntry (const char* path, DIR_ENT newDirEntry);
bool FAT_ClearLinks (u32 cluster);
DIR_ENT FAT_DirEntFromPath (const char* path);
u32 FAT_FirstFreeCluster(void);
DIR_ENT FAT_GetDirEntry ( u32 dirCluster, int entry, int origin);
u32 FAT_LinkFreeCluster(u32 cluster);
u32 FAT_NextCluster(u32 cluster);
bool FAT_WriteFatEntry (u32 cluster, u32 value);
bool FAT_GetFilename (DIR_ENT dirEntry, char* alias);

bool FAT_InitFiles (void);
bool FAT_FreeFiles (void);
int FAT_remove (const char* path);
bool FAT_chdir (const char* path);
FILE_TYPE FAT_FindFirstFile (char* filename);
FILE_TYPE FAT_FindNextFile (char* filename);
FILE_TYPE FAT_FileExists (const char* filename);
bool FAT_GetAlias (char* alias);
bool FAT_GetLongFilename (char* filename);
u32 FAT_GetFileSize (void);
u32 FAT_GetFileCluster (void);

FAT_FILE* FAT_fopen(const char* path, const char* mode);
bool FAT_fclose (FAT_FILE* file);
bool FAT_feof(FAT_FILE* file);
int FAT_fseek(FAT_FILE* file, s32 offset, int origin);
u32 FAT_ftell (FAT_FILE* file);
u32 FAT_fread (void* buffer, u32 size, u32 count, FAT_FILE* file);
u32 FAT_fwrite (const void* buffer, u32 size, u32 count, FAT_FILE* file);
char FAT_fgetc (FAT_FILE* file);
char FAT_fputc (char c, FAT_FILE* file);

/*-----------------------------------------------------------------
ucase
Returns the uppercase version of the given char
char IN: a character
char return OUT: uppercase version of character
-----------------------------------------------------------------*/
char ucase (char character)
{
	if ((character > 0x60) && (character < 0x7B))
		character = character - 0x20;
	return (character);
}


/*-----------------------------------------------------------------
getRTCtoFileTime and getRTCtoFileDate
Returns the time / date in Dir Entry styled format
u16 return OUT: time / date in Dir Entry styled format
-----------------------------------------------------------------*/
u16 getRTCtoFileTime (void)
{
#ifdef NDS
	return (
		( ( (IPC->rtc_hours > 11 ? IPC->rtc_hours - 40 : IPC->rtc_hours) & 0x1F) << 11) |
		( (IPC->rtc_minutes & 0x3F) << 5) |
		( (IPC->rtc_seconds >> 1) & 0x1F) );
#else
	return 0;
#endif
}

u16 getRTCtoFileDate (void)
{
#ifdef NDS
	return ( 
		( ((IPC->rtc_year + 20) & 0x7F) <<9) |
		( (IPC->rtc_month & 0xF) << 5) |
		(IPC->rtc_day & 0x1F) );
#else
	return 0;
#endif
}


/*-----------------------------------------------------------------
Disc level FAT routines
-----------------------------------------------------------------*/
#define FAT_ClustToSect(m) \
	(((m-2) * filesysSecPerClus) + filesysData)

/*-----------------------------------------------------------------
FAT_NextCluster
Internal function - gets the cluster linked from input cluster
-----------------------------------------------------------------*/
u32 FAT_NextCluster(u32 cluster)
{
	u32 nextCluster = CLUSTER_FREE;
	u32 sector;
	int offset;
	
	switch (filesysType) 
	{
		case FS_UNKNOWN:
			nextCluster = CLUSTER_FREE;
			break;
			
		case FS_FAT12:
			sector = filesysFAT + (((cluster * 3) / 2) / BYTE_PER_READ);
			offset = ((cluster * 3) / 2) % BYTE_PER_READ;

			// If FAT buffer contains wrong sector
			if (sector != fatBufferCurSector)
			{
				// Load correct sector to buffer
				fatBufferCurSector = sector;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			}

			nextCluster = ((u8*)fatBuffer)[offset];
			offset++;
			
			if (offset >= BYTE_PER_READ) {
				offset = 0;
				fatBufferCurSector++;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			}
			
			nextCluster |= (((u8*)fatBuffer)[offset]) << 8;
			
			if (cluster & 0x01) {
				nextCluster = nextCluster >> 4;
			} else 	{
				nextCluster &= 0x0FFF;
			}
			
			if (nextCluster >= 0x0FF7)
			{
				nextCluster = CLUSTER_EOF;
			}

			break;
			
		case FS_FAT16:
			sector = filesysFAT + ((cluster << 1) / BYTE_PER_READ);
			offset = cluster % (BYTE_PER_READ >> 1);
			
			// If FAT buffer contains wrong sector
			if (sector != fatBufferCurSector)
			{
				// Load correct sector to buffer
				fatBufferCurSector = sector;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			}

			// read the nextCluster value
			nextCluster = ((u16*)fatBuffer)[offset];
			
			if (nextCluster >= 0xFFF7)
			{
				nextCluster = CLUSTER_EOF;
			}
			break;
			
		case FS_FAT32:
			sector = filesysFAT + ((cluster << 2) / BYTE_PER_READ);
			offset = cluster % (BYTE_PER_READ >> 2);
			
			// If FAT buffer contains wrong sector
			if (sector != fatBufferCurSector)
			{
				// Load correct sector to buffer
				fatBufferCurSector = sector;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			}

			// read the nextCluster value
			nextCluster = (((u32*)fatBuffer)[offset]) & 0x0FFFFFFF;
			
			if (nextCluster >= 0x0FFFFFF7)
			{
				nextCluster = CLUSTER_EOF;
			}
			break;
			
		default:
			nextCluster = CLUSTER_FREE;
			break;
	}
	
	return nextCluster;
}

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_WriteFatEntry
Internal function - writes FAT information about a cluster
-----------------------------------------------------------------*/
bool FAT_WriteFatEntry (u32 cluster, u32 value)
{
	u32 sector;
	int offset;

	if ((cluster < 0x0002) || (cluster > fatLastCluster))
	{
		return false;
	}
	
	switch (filesysType) 
	{
		case FS_UNKNOWN:
			return false;
			break;
			
		case FS_FAT12:
			sector = filesysFAT + (((cluster * 3) / 2) / BYTE_PER_READ);
			offset = ((cluster * 3) / 2) % BYTE_PER_READ;

			// If FAT buffer contains wrong sector
			if (sector != fatBufferCurSector)
			{
				// Load correct sector to buffer
				fatBufferCurSector = sector;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			}

			if (cluster & 0x01) {

				((u8*)fatBuffer)[offset] = (((u8*)fatBuffer)[offset] & 0x0F) | ((value & 0x0F) << 4);

				offset++;
				if (offset >= BYTE_PER_READ) {
					offset = 0;
					// write the buffer back to disc
					disc_WriteSector(fatBufferCurSector, fatBuffer);
					// read the next sector	
					fatBufferCurSector++;
					disc_ReadSector(fatBufferCurSector, fatBuffer);
				}
				
				((u8*)fatBuffer)[offset] =  (value & 0x0FF0) >> 4;

			} else {
			
				((u8*)fatBuffer)[offset] = value & 0xFF;
		
				offset++;
				if (offset >= BYTE_PER_READ) {
					offset = 0;
					// write the buffer back to disc
					disc_WriteSector(fatBufferCurSector, fatBuffer);
					// read the next sector	
					fatBufferCurSector++;
					disc_ReadSector(fatBufferCurSector, fatBuffer);
				}
				
				((u8*)fatBuffer)[offset] = (((u8*)fatBuffer)[offset] & 0xF0) | ((value >> 8) & 0x0F);
			}

			break;
			
		case FS_FAT16:
			sector = filesysFAT + ((cluster << 1) / BYTE_PER_READ);
			offset = cluster % (BYTE_PER_READ >> 1);

			// If FAT buffer contains wrong sector
			if (sector != fatBufferCurSector)
			{
				// Load correct sector to buffer
				fatBufferCurSector = sector;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			}

			// write the value to the FAT buffer
			((u16*)fatBuffer)[offset] = (value & 0xFFFF);

			break;
			
		case FS_FAT32:
			sector = filesysFAT + ((cluster << 2) / BYTE_PER_READ);
			offset = cluster % (BYTE_PER_READ >> 2);
			
			// If FAT buffer contains wrong sector
			if (sector != fatBufferCurSector)
			{
				// Load correct sector to buffer
				fatBufferCurSector = sector;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			}

			// write the value to the FAT buffer
			(((u32*)fatBuffer)[offset]) =  value;

			break;
			
		default:
			return false;
			break;
	}
	
	// write the buffer back to disc
	disc_WriteSector(fatBufferCurSector, fatBuffer);
			
	return true;
}
#endif

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_ReadWriteFatEntryBuffered
Internal function - writes FAT information about a cluster to a 
 buffer that should then be flushed to disc using 
 FAT_WriteFatEntryFlushBuffer()
 Call FAT_WriteFatEntry first so as not to ruin the disc.
 Also returns the entry being replaced
-----------------------------------------------------------------*/
u32 FAT_ReadWriteFatEntryBuffered (u32 cluster, u32 value)
{
	u32 sector;
	int offset;
	u32 oldValue;

	if ((cluster < 0x0002) || (cluster > fatLastCluster))
		return CLUSTER_FREE;

	
	switch (filesysType) 
	{
		case FS_UNKNOWN:
			oldValue = CLUSTER_FREE;
			break;
			
		case FS_FAT12:
			sector = filesysFAT + (((cluster * 3) / 2) / BYTE_PER_READ);
			offset = ((cluster * 3) / 2) % BYTE_PER_READ;

			// If FAT buffer contains wrong sector
			if (sector != fatBufferCurSector)
			{
				// write the old buffer to disc
				if ((fatBufferCurSector >= filesysFAT) && (fatBufferCurSector < (filesysFAT + filesysSecPerFAT)))
					disc_WriteSector(fatBufferCurSector, fatBuffer);
				// Load correct sector to buffer
				fatBufferCurSector = sector;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			}

			if (cluster & 0x01) {

				oldValue = (((u8*)fatBuffer)[offset] & 0xF0) >> 4;
				((u8*)fatBuffer)[offset] = (((u8*)fatBuffer)[offset] & 0x0F) | ((value & 0x0F) << 4);

				offset++;
				if (offset >= BYTE_PER_READ) {
					offset = 0;
					// write the buffer back to disc
					disc_WriteSector(fatBufferCurSector, fatBuffer);
					// read the next sector	
					fatBufferCurSector++;
					disc_ReadSector(fatBufferCurSector, fatBuffer);
				}
				
				oldValue |= ((((u8*)fatBuffer)[offset]) << 4) & 0x0FF0;
				((u8*)fatBuffer)[offset] =  (value & 0x0FF0) >> 4;

			} else {
			
				oldValue = ((u8*)fatBuffer)[offset] & 0xFF;
				((u8*)fatBuffer)[offset] = value & 0xFF;
		
				offset++;
				if (offset >= BYTE_PER_READ) {
					offset = 0;
					// write the buffer back to disc
					disc_WriteSector(fatBufferCurSector, fatBuffer);
					// read the next sector	
					fatBufferCurSector++;
					disc_ReadSector(fatBufferCurSector, fatBuffer);
				}
				
				oldValue |= (((u8*)fatBuffer)[offset] & 0x0F) << 8;
				((u8*)fatBuffer)[offset] = (((u8*)fatBuffer)[offset] & 0xF0) | ((value >> 8) & 0x0F);
			}

			if (oldValue >= 0x0FF7) 
			{
				oldValue = CLUSTER_EOF;
			}

			break;
			
		case FS_FAT16:
			sector = filesysFAT + ((cluster << 1) / BYTE_PER_READ);
			offset = cluster % (BYTE_PER_READ >> 1);

			// If FAT buffer contains wrong sector
			if (sector != fatBufferCurSector)
			{
				// write the old buffer to disc
				if ((fatBufferCurSector >= filesysFAT) && (fatBufferCurSector < (filesysFAT + filesysSecPerFAT)))
					disc_WriteSector(fatBufferCurSector, fatBuffer);
				// Load correct sector to buffer
				fatBufferCurSector = sector;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			} 

			// write the value to the FAT buffer
			oldValue = ((u16*)fatBuffer)[offset];
			((u16*)fatBuffer)[offset] = value;

			if (oldValue >= 0xFFF7) 
			{
				oldValue = CLUSTER_EOF;
			}

			break;
			
		case FS_FAT32:
			sector = filesysFAT + ((cluster << 2) / BYTE_PER_READ);
			offset = cluster % (BYTE_PER_READ >> 2);
			
			// If FAT buffer contains wrong sector
			if (sector != fatBufferCurSector)
			{
				// write the old buffer to disc
				if ((fatBufferCurSector >= filesysFAT) && (fatBufferCurSector < (filesysFAT + filesysSecPerFAT)))
					disc_WriteSector(fatBufferCurSector, fatBuffer);
				// Load correct sector to buffer
				fatBufferCurSector = sector;
				disc_ReadSector(fatBufferCurSector, fatBuffer);
			}

			// write the value to the FAT buffer
			oldValue = ((u32*)fatBuffer)[offset];
			((u32*)fatBuffer)[offset] =  value;

			if (oldValue >= 0x0FFFFFF7) 
			{
				oldValue = CLUSTER_EOF;
			}

			break;
			
		default:
			oldValue = CLUSTER_FREE;
			break;
	}
	
	return oldValue;
}
#endif

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_WriteFatEntryFlushBuffer
Flush the FAT buffer back to the disc
-----------------------------------------------------------------*/
bool FAT_WriteFatEntryFlushBuffer (void)
{
	// write the buffer disc
	if ((fatBufferCurSector >= filesysFAT) && (fatBufferCurSector < (filesysFAT + filesysSecPerFAT)))
	{
		disc_WriteSector(fatBufferCurSector, fatBuffer);
		return true;
	} else {
		return false;
	}
}
#endif

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_FirstFreeCluster
Internal function - gets the first available free cluster
-----------------------------------------------------------------*/
u32 FAT_FirstFreeCluster(void)
{
	// Start at first valid cluster
	if (fatFirstFree < CLUSTER_FIRST)
		fatFirstFree = CLUSTER_FIRST;

	while ((FAT_NextCluster(fatFirstFree) != CLUSTER_FREE) && (fatFirstFree <= fatLastCluster))
	{
		fatFirstFree++;
	}
	if (fatFirstFree > fatLastCluster)
	{
		return CLUSTER_EOF;
	}
	return fatFirstFree;
}
#endif

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_LinkFreeCluster
Internal function - gets the first available free cluster, sets it
to end of file, links the input cluster to it then returns the 
cluster number
-----------------------------------------------------------------*/
u32 FAT_LinkFreeCluster(u32 cluster)
{
	u32 firstFree;
	u32 curLink;

	if (cluster > fatLastCluster)
	{
		return CLUSTER_FREE;
	}

	// Check if the cluster already has a link, and return it if so
	curLink = FAT_NextCluster (cluster);
	if ((curLink >= CLUSTER_FIRST) && (curLink < fatLastCluster))
	{
		return curLink;	// Return the current link - don't allocate a new one
	}
	
	// Get a free cluster
	firstFree = FAT_FirstFreeCluster();

	// If couldn't get a free cluster then return
	if (firstFree == CLUSTER_EOF)
	{
		return CLUSTER_FREE;
	}

	if ((cluster >= CLUSTER_FIRST) && (cluster < fatLastCluster))
	{
		// Update the linked from FAT entry
		FAT_WriteFatEntry (cluster, firstFree);
	}
	// Create the linked to FAT entry
	FAT_WriteFatEntry (firstFree, CLUSTER_EOF);

	return firstFree;
}
#endif


#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_ClearLinks
Internal function - frees any cluster used by a file
-----------------------------------------------------------------*/
bool FAT_ClearLinks (u32 cluster)
{
	u32 nextCluster;
	
	if ((cluster < 0x0002) || (cluster > fatLastCluster))
		return false;

	// Store next cluster before erasing the link
	nextCluster = FAT_NextCluster (cluster);

	// Erase the link
	FAT_WriteFatEntry (cluster, CLUSTER_FREE);

	// Move onto next cluster
	cluster = nextCluster;

	while ((cluster != CLUSTER_EOF) && (cluster != CLUSTER_FREE))
	{
		cluster = FAT_ReadWriteFatEntryBuffered (cluster, CLUSTER_FREE);
	} 

	// Flush fat write buffer
	FAT_WriteFatEntryFlushBuffer ();

	return true;
}
#endif


/*-----------------------------------------------------------------
FAT_InitFiles
Reads the FAT information from the CF card.
You need to call this before reading any files.
bool return OUT: true if successful.
-----------------------------------------------------------------*/
bool FAT_InitFiles (void)
{
	int i;
	int bootSector;
	BOOT_SEC* bootSec;
	
	if (!disc_Init())
	{
		return (false);
	}
	
	// Read first sector of CF card
	if ( !disc_ReadSector(0, globalBuffer)) {
		return false;
	}

	// Make sure it is a valid MBR or boot sector
	if ( (globalBuffer[0x1FE] != 0x55) || (globalBuffer[0x1FF] != 0xAA)) {
		return false;
	}

	// Check if there is a FAT string, which indicates this is a boot sector
	if ((globalBuffer[0x36] == 'F') && (globalBuffer[0x37] == 'A') && (globalBuffer[0x38] == 'T'))
	{
		bootSector = 0;
	}
	// Check for FAT32
	else if ((globalBuffer[0x52] == 'F') && (globalBuffer[0x53] == 'A') && (globalBuffer[0x54] == 'T'))
	{
		bootSector = 0;
	}
	else	// This is an MBR
	{
		// Find first valid partition from MBR
		// First check for an active partition
		for (i=0x1BE; (i < 0x1FE) && (globalBuffer[i] != 0x80); i+= 0x10);
		// If it didn't find an active partition, search for any valid partition
		if (i == 0x1FE) 
			for (i=0x1BE; (i < 0x1FE) && (globalBuffer[i+0x04] == 0x00); i+= 0x10);
		
		// Go to first valid partition
		if ( i != 0x1FE)	// Make sure it found a partition
		{
			bootSector = globalBuffer[0x8 + i] + (globalBuffer[0x9 + i] << 8) + (globalBuffer[0xA + i] << 16) + ((globalBuffer[0xB + i] << 24) & 0x0F);
		} else {
			bootSector = 0;	// No partition found, assume this is a MBR free disk
		}
	}

	// Read in boot sector
	bootSec = (BOOT_SEC*) globalBuffer;
	if (!disc_ReadSector (bootSector,  bootSec)) {
		return false;
	}
	
	// Store required information about the file system
	if (bootSec->sectorsPerFAT != 0)
	{
		filesysSecPerFAT = bootSec->sectorsPerFAT;
	}
	else
	{
		filesysSecPerFAT = bootSec->extBlock.fat32.sectorsPerFAT32;
	}
	
	if (bootSec->numSectorsSmall != 0)
	{
		filesysNumSec = bootSec->numSectorsSmall;
	}
	else
	{
		filesysNumSec = bootSec->numSectors;
	}

	filesysBytePerSec = BYTE_PER_READ;	// Sector size is redefined to be 512 bytes
	filesysSecPerClus = bootSec->sectorsPerCluster * bootSec->bytesPerSector / BYTE_PER_READ;
	filesysBytePerClus = filesysBytePerSec * filesysSecPerClus;
	filesysFAT = bootSector + bootSec->reservedSectors;

	filesysRootDir = filesysFAT + (bootSec->numFATs * filesysSecPerFAT);
	filesysData = filesysRootDir + ((bootSec->rootEntries * sizeof(DIR_ENT)) / filesysBytePerSec);

	filesysTotalSize = (filesysNumSec - filesysData) * filesysBytePerSec;

	// Store info about FAT
	fatLastCluster = (filesysNumSec - filesysData) / bootSec->sectorsPerCluster;
	fatFirstFree = CLUSTER_FIRST;
	fatBufferCurSector = 0;
	disc_ReadSector(fatBufferCurSector, fatBuffer);

	if (fatLastCluster < 4085)
	{
		filesysType = FS_FAT12;	// FAT12 volume - unsupported
	}
	else if (fatLastCluster < 65525)
	{
		filesysType = FS_FAT16;	// FAT16 volume
	}
	else
	{
		filesysType = FS_FAT32;	// FAT32 volume
	}

	if (filesysType != FS_FAT32)
	{
		filesysRootDirClus = FAT16_ROOT_DIR_CLUSTER;
	}
	else	// Set up for the FAT32 way
	{
		filesysRootDirClus = bootSec->extBlock.fat32.rootClus;
		// Check if FAT mirroring is enabled
		if (!(bootSec->extBlock.fat32.extFlags & 0x80))
		{
			// Use the active FAT
			filesysFAT = filesysFAT + ( filesysSecPerFAT * (bootSec->extBlock.fat32.extFlags & 0x0F));
		}
	}

	// Set current directory to the root
	curWorkDirCluster = filesysRootDirClus;
	wrkDirCluster = filesysRootDirClus;
	wrkDirSector = 0;
	wrkDirOffset = 0;

	// Set all files to free
	for (i=0; i < MAX_FILES_OPEN; i++)
	{
		openFiles[i].inUse = false;
	}

	// No long filenames so far
	lfnExists = false;
	for (i = 0; i < MAX_FILENAME_LENGTH; i++)
	{
		lfnName[i] = '\0';
	}

	return (true);
}

/*-----------------------------------------------------------------
FAT_FreeFiles
Closes all open files then resets the CF card.
Call this before exiting back to the GBAMP
bool return OUT: true if successful.
-----------------------------------------------------------------*/
bool FAT_FreeFiles (void)
{
	int i;

	// Close all open files
	for (i=0; i < MAX_FILES_OPEN; i++)
	{
		if (openFiles[i].inUse == true)
		{
			FAT_fclose(&openFiles[i]);
		}
	}

	// Flush any sectors in disc cache
	disc_CacheFlush();

	// Clear card status
	disc_ClearStatus();

	// Return status of card
	return disc_IsInserted();
}


/*-----------------------------------------------------------------
FAT_GetDirEntry
Return the file info structure of the next valid file entry
u32 dirCluster: IN cluster of subdirectory table
int entry: IN the desired file entry
int origin IN: relative position of the entry
DIR_ENT return OUT: desired dirEntry. First char will be FILE_FREE if 
	the entry does not exist.
-----------------------------------------------------------------*/
DIR_ENT FAT_GetDirEntry ( u32 dirCluster, int entry, int origin)
{
	DIR_ENT dir;
	DIR_ENT_LFN lfn;
	int firstSector = 0;
	bool notFound = false;
	bool found = false;
	int maxSectors;
	int lfnPos, aliasPos;
	u8 lfnChkSum, chkSum;

	int i;

	dir.name[0] = FILE_FREE; // default to no file found
	dir.attrib = 0x00;

	// Check if fat has been initialised
	if (filesysBytePerSec == 0)
	{
		return (dir);
	}
	
	switch (origin) 
	{
	case SEEK_SET:
		wrkDirCluster = dirCluster;
		wrkDirSector = 0;
		wrkDirOffset = -1;
		break;
	case SEEK_CUR:	// Don't change anything
		break;
	case SEEK_END:	// Find entry signifying end of directory
		// Subtraction will never reach 0, so it keeps going 
		// until reaches end of directory
		wrkDirCluster = dirCluster;
		wrkDirSector = 0;
		wrkDirOffset = -1;
		entry = -1;
		break;
	default:
		return dir;
	}

	lfnChkSum = 0;
	maxSectors = (wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? (filesysData - filesysRootDir) : filesysSecPerClus);

	// Scan Dir for correct entry
	firstSector = (wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster));
	disc_ReadSector (firstSector + wrkDirSector, globalBuffer);
	found = false;
	notFound = false;
	do {
		wrkDirOffset++;
		if (wrkDirOffset == BYTE_PER_READ / sizeof (DIR_ENT))
		{
			wrkDirOffset = 0;
			wrkDirSector++;
			if ((wrkDirSector == filesysSecPerClus) && (wrkDirCluster != FAT16_ROOT_DIR_CLUSTER))
			{
				wrkDirSector = 0;
				wrkDirCluster = FAT_NextCluster(wrkDirCluster);
				if (wrkDirCluster == CLUSTER_EOF)
				{
					notFound = true;
				}
				firstSector = FAT_ClustToSect(wrkDirCluster);		
			}
			else if ((wrkDirCluster == FAT16_ROOT_DIR_CLUSTER) && (wrkDirSector == (filesysData - filesysRootDir)))
			{
				notFound = true;	// Got to end of root dir
			}
			disc_ReadSector (firstSector + wrkDirSector, globalBuffer);
		}
		dir = ((DIR_ENT*) globalBuffer)[wrkDirOffset];
		if ((dir.name[0] != FILE_FREE) && (dir.name[0] > 0x20) && ((dir.attrib & ATTRIB_VOL) != ATTRIB_VOL))
		{
			entry--;
			if (lfnExists)
			{
				// Calculate file checksum
				chkSum = 0;
				for (aliasPos=0; aliasPos < 11; aliasPos++)
				{
					// NOTE: The operation is an unsigned char rotate right
					chkSum = ((chkSum & 1) ? 0x80 : 0) + (chkSum >> 1) + (aliasPos < 8 ? dir.name[aliasPos] : dir.ext[aliasPos - 8]);
				}
				if (chkSum != lfnChkSum)
				{
					lfnExists = false;
					lfnName[0] = '\0';
				}
			}
			if (entry == 0) 
			{
				if (!lfnExists)
				{
					FAT_GetFilename (dir, lfnName);
				}
				found = true;
			}
		}
		else if (dir.name[0] == FILE_LAST)
		{
			if (origin == SEEK_END)
			{
				found = true;
			}
			else
			{
				notFound = true;
			}
		}
		else if (dir.attrib == ATTRIB_LFN)
		{
			lfn = ((DIR_ENT_LFN*) globalBuffer)[wrkDirOffset];
			if (lfn.ordinal & LFN_DEL)
			{
				lfnExists = false;
			}
			else if (lfn.ordinal & LFN_END)	// Last part of LFN, make sure it isn't deleted (Thanks MoonLight)
			{
				lfnExists = true;
				lfnName[(lfn.ordinal & ~LFN_END) * 13] = '\0';	// Set end of lfn to null character
				lfnChkSum = lfn.checkSum;
			}
			if (lfnChkSum != lfn.checkSum)
			{
				lfnExists = false;
			}
			if (lfnExists)
			{
				lfnPos = ((lfn.ordinal & ~LFN_END) - 1) * 13;
				for (i = 0; i < 13; i++) {
					lfnName[lfnPos + i] = ((u8*)&lfn)[(int)(lfn_offset_table[i])] /* | ((u8*)&lfn)[(int)(lfn_offset_table[i]) + 1]  include this for unicode support*/;
				}
			}
		}
	} while (!found && !notFound);
	
	// If no file is found, return FILE_FREE
	if (notFound)
	{
		dir.name[0] = FILE_FREE;
	}

	return (dir);
}


/*-----------------------------------------------------------------
FAT_GetLongFilename
Get the long name of the last file or directory retrived with 
	GetDirEntry. Also works for FindFirstFile and FindNextFile.
	If a long name doesn't exist, it returns the short name
	instead.
char* filename: OUT will be filled with the filename, should be at
	least 256 bytes long
bool return OUT: return true if successful
-----------------------------------------------------------------*/
bool FAT_GetLongFilename (char* filename)
{
	if (filename == NULL)
		return false;

	strncpy (filename, lfnName, MAX_FILENAME_LENGTH - 1);
	filename[MAX_FILENAME_LENGTH - 1] = '\0';
	
	return true;
}


/*-----------------------------------------------------------------
FAT_GetFilename
Get the alias (short name) of the file or directory stored in 
	dirEntry
DIR_ENT dirEntry: IN a valid directory table entry
char* alias OUT: will be filled with the alias (short filename),
	should be at least 13 bytes long
bool return OUT: return true if successful
-----------------------------------------------------------------*/
bool FAT_GetFilename (DIR_ENT dirEntry, char* alias)
{
	int i=0;
	int j=0;

	alias[0] = '\0';
	if (dirEntry.name[0] != FILE_FREE)
	{
		if (dirEntry.name[0] == '.')
		{
			alias[0] = '.';
			if (dirEntry.name[1] == '.')
			{
				alias[1] = '.';
				alias[2] = '\0';
			}
			else
			{
				alias[1] = '\0';
			}
		}
		else
		{		
			// Copy the filename from the dirEntry to the string
			for (i = 0; (i < 8) && (dirEntry.name[i] != ' '); i++)
			{
				alias[i] = dirEntry.name[i];
			}
			// Copy the extension from the dirEntry to the string
			if (dirEntry.ext[0] != ' ')
			{
				alias[i++] = '.';
				for ( j = 0; (j < 3) && (dirEntry.ext[j] != ' '); j++)
				{
					alias[i++] = dirEntry.ext[j];
				}
			}
			alias[i] = '\0';
		}
	}

	return (alias[0] != '\0');
}

/*-----------------------------------------------------------------
FAT_GetAlias
Get the alias (short name) of the last file or directory entry read
	using GetDirEntry. Works for FindFirstFile and FindNextFile
char* alias OUT: will be filled with the alias (short filename),
	should be at least 13 bytes long
bool return OUT: return true if successful
-----------------------------------------------------------------*/
bool FAT_GetAlias (char* alias)
{
	if (alias == NULL)
	{
		return false;
	}
	// Read in the last accessed directory entry
	disc_ReadSector ((wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector, globalBuffer);
	
	return 	FAT_GetFilename (((DIR_ENT*)globalBuffer)[wrkDirOffset], alias);
}

/*-----------------------------------------------------------------
FAT_GetFileSize
Get the file size of the last file found or openned.
This idea is based on a modification by MoonLight
u32 return OUT: the file size
-----------------------------------------------------------------*/
u32 FAT_GetFileSize (void)
{
	// Read in the last accessed directory entry
	disc_ReadSector ((wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector, globalBuffer);
	
	return 	((DIR_ENT*)globalBuffer)[wrkDirOffset].fileSize;
}

/*-----------------------------------------------------------------
FAT_GetFileCluster
Get the first cluster of the last file found or openned.
u32 return OUT: the file start cluster
-----------------------------------------------------------------*/
u32 FAT_GetFileCluster (void)
{
	// Read in the last accessed directory entry
	disc_ReadSector ((wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector, globalBuffer);
	
	return 	(((DIR_ENT*)globalBuffer)[wrkDirOffset].startCluster) | (((DIR_ENT*)globalBuffer)[wrkDirOffset].startClusterHigh << 16);
}

/*-----------------------------------------------------------------
FAT_GetFileAttributes
Get the attributes of the last file found or openned.
u8 return OUT: the file's attributes
-----------------------------------------------------------------*/
u8 FAT_GetFileAttributes (void)
{
	// Read in the last accessed directory entry
	disc_ReadSector ((wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector, globalBuffer);
	
	return 	((DIR_ENT*)globalBuffer)[wrkDirOffset].attrib;
}

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_SetFileAttributes
Set the attributes of a file.
const char* filename IN: The name and path of the file to modify
u8 attributes IN: The attribute values to assign
u8 mask IN: Detemines which attributes are changed
u8 return OUT: the file's new attributes
-----------------------------------------------------------------*/
u8 FAT_SetFileAttributes (const char* filename, u8 attributes, u8 mask)
{
	// Get the file
	if (!FAT_FileExists(filename)) {
		return 0xff;
	}

	// Read in the last accessed directory entry
	disc_ReadSector ((wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector, globalBuffer);

	((DIR_ENT*)globalBuffer)[wrkDirOffset].attrib = (((DIR_ENT*)globalBuffer)[wrkDirOffset].attrib & ~(mask & 0x27)) | (attributes & 0x27);	// 0x27 is he settable attributes
	
	disc_WriteSector ((wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector, globalBuffer);

	return 	((DIR_ENT*)globalBuffer)[wrkDirOffset].attrib;
}
#endif

#ifdef FILE_TIME_SUPPORT
time_t FAT_FileTimeToCTime (u16 fileTime, u16 fileDate)
{
	struct tm timeInfo;
	
	timeInfo.tm_year = (fileDate >> 9) + 80;		// years since midnight January 1970
	timeInfo.tm_mon = ((fileDate >> 5) & 0xf) - 1;	// Months since january
	timeInfo.tm_mday = fileDate & 0x1f;				// Day of the month

	timeInfo.tm_hour = fileTime >> 11;				// hours past midnight
	timeInfo.tm_min = (fileTime >> 5) & 0x3f;		// minutes past the hour
	timeInfo.tm_sec = (fileTime & 0x1f) * 2;		// seconds past the minute

	return mktime(&timeInfo);
}

/*-----------------------------------------------------------------
FAT_GetFileCreationTime
Get the creation time of the last file found or openned.
time_t return OUT: the file's creation time
-----------------------------------------------------------------*/
time_t FAT_GetFileCreationTime (void)
{
	// Read in the last accessed directory entry
	disc_ReadSector ((wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector, globalBuffer);
	
	return 	FAT_FileTimeToCTime(((DIR_ENT*)globalBuffer)[wrkDirOffset].cTime, ((DIR_ENT*)globalBuffer)[wrkDirOffset].cDate);
}

/*-----------------------------------------------------------------
FAT_GetFileLastWriteTime
Get the creation time of the last file found or openned.
time_t return OUT: the file's creation time
-----------------------------------------------------------------*/
time_t FAT_GetFileLastWriteTime (void)
{
	// Read in the last accessed directory entry
	disc_ReadSector ((wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector, globalBuffer);
	
	return 	FAT_FileTimeToCTime(((DIR_ENT*)globalBuffer)[wrkDirOffset].mTime, ((DIR_ENT*)globalBuffer)[wrkDirOffset].mDate);
}
#endif

/*-----------------------------------------------------------------
FAT_DirEntFromPath
Finds the directory entry for a file or directory from a path
Path separator is a forward slash /
const char* path: IN null terminated string of path.
DIR_ENT return OUT: dirEntry of found file. First char will be FILE_FREE
	if the file was not found
-----------------------------------------------------------------*/
DIR_ENT FAT_DirEntFromPath (const char* path)
{
	int pathPos;
	char name[MAX_FILENAME_LENGTH];
	char alias[13];
	int namePos;
	bool found, notFound;
	DIR_ENT dirEntry;
	u32 dirCluster;
	bool flagLFN, dotSeen;
	
	// Start at beginning of path
	pathPos = 0;
	
	if (path[pathPos] == '/') 
	{
		dirCluster = filesysRootDirClus;	// Start at root directory
	}
	else
	{
		dirCluster = curWorkDirCluster;	// Start at current working dir
	}
	
	// Eat any slash /
	while ((path[pathPos] == '/') && (path[pathPos] != '\0'))
	{
		pathPos++;
	}
	
	// Search until can't continue
	found = false;
	notFound = false;
	while (!notFound && !found)
	{
		flagLFN = false;
		// Copy name from path
		namePos = 0;
		if ((path[pathPos] == '.') && ((path[pathPos + 1] == '\0') || (path[pathPos + 1] == '/'))) {
			// Dot entry
			name[namePos++] = '.';
			pathPos++;
		} else if ((path[pathPos] == '.') && (path[pathPos + 1] == '.') && ((path[pathPos + 2] == '\0') || (path[pathPos + 2] == '/'))){
			// Double dot entry
			name[namePos++] = '.';
			pathPos++;
			name[namePos++] = '.';
			pathPos++;
		} else {
			// Copy name from path
			if (path[pathPos] == '.') {
				flagLFN = true;
			}
			dotSeen = false;
			while ((namePos < MAX_FILENAME_LENGTH - 1) && (path[pathPos] != '\0') && (path[pathPos] != '/'))
			{
				name[namePos] = ucase(path[pathPos]);
				if ((name[namePos] <= ' ') || ((name[namePos] >= ':') && (name[namePos] <= '?'))) // Invalid character
				{
					flagLFN = true;
				}
				if (name[namePos] == '.') {
					if (!dotSeen) {
						dotSeen = true;
					} else {
						flagLFN = true;
					}
				}
				namePos++;
				pathPos++;
			}
			// Check if a long filename was specified
			if (namePos > 12)
			{
				flagLFN = true;
			}
		}
		
		// Add end of string char
		name[namePos] = '\0';

		// Move through path to correct place
		while ((path[pathPos] != '/') && (path[pathPos] != '\0'))
			pathPos++;
		// Eat any slash /
		while ((path[pathPos] == '/') && (path[pathPos] != '\0'))
		{
			pathPos++;
		}

		// Search current Dir for correct entry
		dirEntry = FAT_GetDirEntry (dirCluster, 1, SEEK_SET);
		while ( !found && !notFound)
		{
			// Match filename
			found = true;
			for (namePos = 0; (namePos < MAX_FILENAME_LENGTH) && found && (name[namePos] != '\0') && (lfnName[namePos] != '\0'); namePos++)
			{
				if (name[namePos] != ucase(lfnName[namePos]))
				{
					found = false;
				}
			}
			if ((name[namePos] == '\0') != (lfnName[namePos] == '\0'))
			{
				found = false;
			}

			// Check against alias as well.
			if (!found)
			{
				FAT_GetFilename(dirEntry, alias);
				found = true;
				for (namePos = 0; (namePos < 13) && found && (name[namePos] != '\0') && (alias[namePos] != '\0'); namePos++)
				{
					if (name[namePos] != ucase(alias[namePos]))
					{
						found = false;
					}
				}
				if ((name[namePos] == '\0') != (alias[namePos] == '\0'))
				{
					found = false;
				}
			}

			if (dirEntry.name[0] == FILE_FREE)
				// Couldn't find specified file
			{
				found = false;
				notFound = true;
			}
			if (!found && !notFound)
			{
				dirEntry = FAT_GetDirEntry (dirCluster, 1, SEEK_CUR);
			}
		}
		
		if (found && ((dirEntry.attrib & ATTRIB_DIR) == ATTRIB_DIR) && (path[pathPos] != '\0'))
			// It has found a directory from within the path that needs to be followed
		{
			found = false;
			dirCluster = dirEntry.startCluster | (dirEntry.startClusterHigh << 16);
		}
	}
	
	if (notFound)
	{
		dirEntry.name[0] = FILE_FREE;
		dirEntry.attrib = 0x00;
	}

	return (dirEntry);
}


#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_AddDirEntry
Creates a new dir entry for a file
Path separator is a forward slash /
const char* path: IN null terminated string of path to file.
DIR_ENT newDirEntry IN: The directory entry to use.
int file IN: The file being added (optional, use -1 if not used)
bool return OUT: true if successful
-----------------------------------------------------------------*/
bool FAT_AddDirEntry (const char* path, DIR_ENT newDirEntry)
{
	char filename[MAX_FILENAME_LENGTH];
	int filePos, pathPos, aliasPos;
	char tempChar;
	bool flagLFN, dotSeen;
	char fileAlias[13] = {0};
	int tailNum;
	
	unsigned char chkSum = 0;
	
	u32 oldWorkDirCluster;
	
	DIR_ENT* dirEntries = (DIR_ENT*)globalBuffer;
	u32 dirCluster;
	int secOffset;
	int entryOffset;
	int maxSectors;
	u32 firstSector;

	DIR_ENT_LFN lfnEntry;
	int lfnPos = 0;

	int dirEntryLength = 0;
	int dirEntryRemain = 0;
	u32 tempDirCluster;
	int tempSecOffset;
	int tempEntryOffset;
	bool dirEndFlag = false;

	int i;

	// Store current working directory
	oldWorkDirCluster = curWorkDirCluster;

	// Find filename within path and change to correct directory
	if (path[0] == '/')
	{
		curWorkDirCluster = filesysRootDirClus;
	}
	
	pathPos = 0;
	filePos = 0;
	flagLFN = false;

	while (path[pathPos + filePos] != '\0')
	{
		if (path[pathPos + filePos] == '/')
		{
			filename[filePos] = '\0';
			if (FAT_chdir(filename) == false) 
			{
				curWorkDirCluster = oldWorkDirCluster;
				return false; // Couldn't change directory
			}
			pathPos += filePos + 1;
			filePos = 0;
		}
		filename[filePos] = path[pathPos + filePos];
		filePos++;
	}
	
	// Skip over last slashes
	while (path[pathPos] == '/')
		pathPos++;
	
	// Check if the filename has a leading "."
	// If so, it is an LFN
	if (path[pathPos] == '.') {
		flagLFN = true;
	}
	
	// Copy name from path
	filePos = 0;
	dotSeen = false;

	while ((filePos < MAX_FILENAME_LENGTH - 1) && (path[pathPos] != '\0'))
	{
		filename[filePos] = path[pathPos];
		if ((filename[filePos] <= ' ') || ((filename[filePos] >= ':') && (filename[filePos] <= '?'))) // Invalid character
		{
			flagLFN = true;
		}
		if (filename[filePos] == '.') {
			if (!dotSeen) {
				dotSeen = true;
			} else {
				flagLFN = true;
			}
		}
		filePos++;
		pathPos++;
		if ((filePos > 8) && !dotSeen) {
			flagLFN = true;
		}
	}
	
	if (filePos == 0)	// No filename
	{
		return false;
	}
	
	// Check if a long filename was specified
	if (filePos > 12)
	{
		flagLFN = true;
	}

	// Check if extension is > 3 characters long
	if (!flagLFN && (strrchr (filename, '.') != NULL) && (strlen(strrchr(filename, '.')) > 4)) {
		flagLFN = true;
	}
	
	lfnPos = (filePos - 1) / 13;

	// Add end of string char
	filename[filePos++] = '\0';
	// Clear remaining chars
	while (filePos < MAX_FILENAME_LENGTH)
		filename[filePos++] = 0x01;	// Set for LFN compatibility
	
	
	if (flagLFN)
	{
		// Generate short filename - always a 2 digit number for tail
		// Get first 5 chars of alias from LFN
		aliasPos = 0;
		filePos = 0;
		if (filename[filePos] == '.') {
			filePos++;
		}
		for ( ; (aliasPos < 5) && (filename[filePos] != '\0') && (filename[filePos] != '.') ; filePos++)
		{
			tempChar = ucase(filename[filePos]);
			if (((tempChar > ' ' && tempChar < ':') || tempChar > '?') && tempChar != '.')
				fileAlias[aliasPos++] = tempChar;
		}
		// Pad Alias with underscores
		while (aliasPos < 5)
			fileAlias[aliasPos++] = '_';
		
		fileAlias[5] = '~';
		fileAlias[8] = '.';
		fileAlias[9] = ' ';
		fileAlias[10] = ' ';
		fileAlias[11] = ' ';
		if (strchr (filename, '.') != NULL) {
			while(filename[filePos] != '\0')
			{
				filePos++;
				if (filename[filePos] == '.')
				{
					pathPos = filePos;
				}
			}
			filePos = pathPos + 1;	//pathPos is used as a temporary variable
			// Copy first 3 characters of extension
			for (aliasPos = 9; (aliasPos < 12) && (filename[filePos] != '\0'); filePos++)
			{
				tempChar = ucase(filename[filePos]);
				if ((tempChar > ' ' && tempChar < ':') || tempChar > '?')
					fileAlias[aliasPos++] = tempChar;
			}
		} else {
			aliasPos = 9;
		}
		
		// Pad Alias extension with spaces
		while (aliasPos < 12)
			fileAlias[aliasPos++] = ' ';
		
		fileAlias[12] = '\0';
		
		
		// Get a valid tail number
		tailNum = 0;
		do {
			tailNum++;
			fileAlias[6] = 0x30 + ((tailNum / 10) % 10);	// 10's digit
			fileAlias[7] = 0x30 + (tailNum % 10);	// 1's digit
		} while ((FAT_DirEntFromPath(fileAlias).name[0] != FILE_FREE) && (tailNum < 100));
		
		if (tailNum < 100)	// Found an alias not being used
		{
			// Calculate file checksum
			chkSum = 0;
			for (aliasPos=0; aliasPos < 12; aliasPos++)
			{
				// Skip '.'
				if (fileAlias[aliasPos] == '.')
					aliasPos++;
				// NOTE: The operation is an unsigned char rotate right
				chkSum = ((chkSum & 1) ? 0x80 : 0) + (chkSum >> 1) + fileAlias[aliasPos];
			}
		}
		else	// Couldn't find a valid alias
		{
			return false;
		}
		
		dirEntryLength = lfnPos + 2;
	}
	else	// Its not a long file name
	{
		// Just copy alias straight from filename
		for (aliasPos = 0; aliasPos < 13; aliasPos++)
		{
			tempChar = ucase(filename[aliasPos]);
			if ((tempChar > ' ' && tempChar < ':') || tempChar > '?')
				fileAlias[aliasPos] = tempChar;
		}
		fileAlias[12] = '\0';

		lfnPos = -1;

		dirEntryLength = 1;
	}
	
	// Change dirEntry name to match alias
	for (aliasPos = 0; ((fileAlias[aliasPos] != '.') && (fileAlias[aliasPos] != '\0') && (aliasPos < 8)); aliasPos++)
	{
		newDirEntry.name[aliasPos] = fileAlias[aliasPos];
	}
	while (aliasPos < 8)
	{
		newDirEntry.name[aliasPos++] = ' ';
	}
	aliasPos = 0;
	while ((fileAlias[aliasPos] != '.') && (fileAlias[aliasPos] != '\0'))
		aliasPos++;
	filePos = 0;
	while (( filePos < 3 ) && (fileAlias[aliasPos] != '\0'))
	{
		tempChar = fileAlias[aliasPos++];
		if ((tempChar > ' ' && tempChar < ':' && tempChar!='.') || tempChar > '?')
			newDirEntry.ext[filePos++] = tempChar;
	}
	while (filePos < 3)
	{
		newDirEntry.ext[filePos++] = ' ';
	}

	// Scan Dir for free entry
	dirCluster = curWorkDirCluster;
	secOffset = 0;
	entryOffset = 0;
	maxSectors = (dirCluster == FAT16_ROOT_DIR_CLUSTER ? (filesysData - filesysRootDir) : filesysSecPerClus);
	firstSector = (dirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(dirCluster));
	disc_ReadSector (firstSector + secOffset, dirEntries);
	
	dirEntryRemain = dirEntryLength;
	tempDirCluster = dirCluster;
	tempSecOffset = secOffset;
	tempEntryOffset = entryOffset;
		
	// Search for a large enough space to fit in new directory entry
	while ((dirEntries[entryOffset].name[0] != FILE_LAST) && (dirEntryRemain > 0))
	{

		entryOffset++;

		if (entryOffset == BYTE_PER_READ / sizeof (DIR_ENT))
		{
			entryOffset = 0;
			secOffset++;
			if ((secOffset == filesysSecPerClus) && (dirCluster != FAT16_ROOT_DIR_CLUSTER))
			{
				secOffset = 0;
				if (FAT_NextCluster(dirCluster) == CLUSTER_EOF)
				{
					dirCluster = FAT_LinkFreeCluster(dirCluster);
					dirEntries[0].name[0] = FILE_LAST;
				}
				else
				{
					dirCluster = FAT_NextCluster(dirCluster);
				}
				firstSector = FAT_ClustToSect(dirCluster);		
			}
			else if ((dirCluster == FAT16_ROOT_DIR_CLUSTER) && (secOffset == (filesysData - filesysRootDir)))
			{
				return false;	// Got to end of root dir - can't fit in more files
			}
			disc_ReadSector (firstSector + secOffset, dirEntries);
		}

		if ((dirEntries[entryOffset].name[0] == FILE_FREE) || (dirEntries[entryOffset].name[0] == FILE_LAST) )
		{
			dirEntryRemain--;
		} else {
			dirEntryRemain = dirEntryLength;
			tempDirCluster = dirCluster;
			tempSecOffset = secOffset;
			tempEntryOffset = entryOffset;
		}
	}

	// Modifying the last directory is a special case - have to erase following entries
	if (dirEntries[entryOffset].name[0] == FILE_LAST) 
	{
		dirEndFlag = true;
	}

	// Recall last used entry
	dirCluster = tempDirCluster;
	secOffset = tempSecOffset;
	entryOffset = tempEntryOffset;
	dirEntryRemain = dirEntryLength;

	// Re-read in first sector that will be written to
	if (dirEndFlag && (entryOffset == 0))	{
		memset (dirEntries, FILE_LAST, BYTE_PER_READ);
	} else {
		disc_ReadSector (firstSector + secOffset, dirEntries);
	}

	// Add new directory entry
	while (dirEntryRemain > 0)	
	{
		// Move to next entry, first pass advances from last used entry
		entryOffset++;
		if (entryOffset == BYTE_PER_READ / sizeof (DIR_ENT))
		{
			// Write out the current sector if we need to
			entryOffset = 0;
			if (dirEntryRemain < dirEntryLength) // Don't write out sector on first pass
			{
				disc_WriteSector (firstSector + secOffset, dirEntries);
			}
			secOffset++;
			if ((secOffset == filesysSecPerClus) && (dirCluster != FAT16_ROOT_DIR_CLUSTER))
			{
				secOffset = 0;
				if (FAT_NextCluster(dirCluster) == CLUSTER_EOF)
				{
					dirCluster = FAT_LinkFreeCluster(dirCluster);
					dirEntries[0].name[0] = FILE_LAST;
				}
				else
				{
					dirCluster = FAT_NextCluster(dirCluster);
				}
				firstSector = FAT_ClustToSect(dirCluster);		
			}
			else if ((dirCluster == FAT16_ROOT_DIR_CLUSTER) && (secOffset == (filesysData - filesysRootDir)))
			{
				return false;	// Got to end of root dir - can't fit in more files
			}
			if (dirEndFlag)
			{
				memset (dirEntries, FILE_LAST, BYTE_PER_READ);
			} else {
				disc_ReadSector (firstSector + secOffset, dirEntries);
			}
		}

		// Generate LFN entries
		if (lfnPos >= 0)
		{
			lfnEntry.ordinal = (lfnPos + 1) | (dirEntryRemain == dirEntryLength ? LFN_END : 0);
			for (i = 0; i < 13; i++) {
				if (filename [lfnPos * 13 + i] == 0x01) {
					((u8*)&lfnEntry)[(int)lfn_offset_table[i]] = 0xff;
					((u8*)&lfnEntry)[(int)(lfn_offset_table[i]) + 1] = 0xff;
				} else {
					((u8*)&lfnEntry)[(int)lfn_offset_table[i]] = filename [lfnPos * 13 + i];
					((u8*)&lfnEntry)[(int)(lfn_offset_table[i]) + 1] = 0x00;
				}
			}

			lfnEntry.checkSum = chkSum;
			lfnEntry.flag = ATTRIB_LFN;
			lfnEntry.reserved1 = 0;
			lfnEntry.reserved2 = 0;
			
			*((DIR_ENT_LFN*)&dirEntries[entryOffset]) = lfnEntry;
			lfnPos --;
			lfnEntry.ordinal = 0;
		}	// end writing long filename entries
		else
		{
			dirEntries[entryOffset] = newDirEntry;
			if (dirEndFlag && (entryOffset < (BYTE_PER_READ / sizeof (DIR_ENT))) )
				dirEntries[entryOffset+1].name[0] = FILE_LAST;
		}

		dirEntryRemain--;
	}
	
	// Write directory back to disk
	disc_WriteSector (firstSector + secOffset, dirEntries);

	// Change back to Working DIR
	curWorkDirCluster = oldWorkDirCluster;

	return true;
}
#endif

/*-----------------------------------------------------------------
FAT_FindNextFile
Gets the name of the next directory entry
	(can be a file or subdirectory)
char* filename: OUT filename, must be at least 13 chars long
FILE_TYPE return: OUT returns FT_NONE if failed, 
	FT_FILE if it found a file and FT_DIR if it found a directory
-----------------------------------------------------------------*/
FILE_TYPE FAT_FindNextFile(char* filename)
{
	// Get the next directory entry
	DIR_ENT file;
	file = FAT_GetDirEntry (curWorkDirCluster, 1, SEEK_CUR);

	if (file.name[0] == FILE_FREE)
	{
		return FT_NONE;	// Did not find a file
	}

	// Get the filename
	if (filename != NULL)
		FAT_GetFilename (file, filename);

	if ((file.attrib & ATTRIB_DIR) != 0)
	{
		return FT_DIR;	// Found a directory
	}
	else
	{
		return FT_FILE;	// Found a file
	}
}

/*-----------------------------------------------------------------
FAT_FindFirstFile
Gets the name of the first directory entry and resets the count
	(can be a file or subdirectory)
char* filename: OUT filename, must be at least 13 chars long
FILE_TYPE return: OUT returns FT_NONE if failed, 
	FT_FILE if it found a file and FT_DIR if it found a directory
-----------------------------------------------------------------*/
FILE_TYPE FAT_FindFirstFile(char* filename)
{
	// Get the first directory entry
	DIR_ENT file;
	file = FAT_GetDirEntry (curWorkDirCluster, 1, SEEK_SET);
	
	if (file.name[0] == FILE_FREE)
	{
		return FT_NONE;	// Did not find a file
	}

	// Get the filename
	if (filename != NULL)
		FAT_GetFilename (file, filename);

	if ((file.attrib & ATTRIB_DIR) != 0)
	{
		return FT_DIR;	// Found a directory
	}
	else
	{
		return FT_FILE;	// Found a file
	}
}

/*-----------------------------------------------------------------
FAT_FindFirstFileLFN
Gets the long file name of the first directory entry and resets
	the count (can be a file or subdirectory)
char* lfn: OUT long file name, must be at least 256 chars long
FILE_TYPE return: OUT returns FT_NONE if failed, 
	FT_FILE if it found a file and FT_DIR if it found a directory
-----------------------------------------------------------------*/
FILE_TYPE FAT_FindFirstFileLFN(char* lfn)
{
	FILE_TYPE type;
	type = FAT_FindFirstFile(NULL);
	FAT_GetLongFilename (lfn);
	return type;
}

/*-----------------------------------------------------------------
FAT_FindNextFileLFN
Gets the long file name of the next directory entry
	(can be a file or subdirectory)
char* lfn: OUT long file name, must be at least 256 chars long
FILE_TYPE return: OUT returns FT_NONE if failed, 
	FT_FILE if it found a file and FT_DIR if it found a directory
-----------------------------------------------------------------*/
FILE_TYPE FAT_FindNextFileLFN(char* lfn)
{
	FILE_TYPE type;
	type = FAT_FindNextFile(NULL);
	FAT_GetLongFilename (lfn);
	return type;
}


/*-----------------------------------------------------------------
FAT_FileExists
Returns the type of file 
char* filename: IN filename of the file to look for
FILE_TYPE return: OUT returns FT_NONE if there is now file with 
	that name, FT_FILE if it is a file and FT_DIR if it is a directory
-----------------------------------------------------------------*/
FILE_TYPE FAT_FileExists(const char* filename)
{
    DIR_ENT dirEntry; 
    // Get the dirEntry for the path specified 
    dirEntry = FAT_DirEntFromPath (filename); 

    if (dirEntry.name[0] == FILE_FREE) 
    { 
        return FT_NONE; 
    } 
    else if (dirEntry.attrib & ATTRIB_DIR) 
    { 
        return FT_DIR; 
    } 
    else 
    { 
         return FT_FILE; 
    } 
}

/*-----------------------------------------------------------------
FAT_GetFileSystemType
FS_TYPE return: OUT returns the current file system type
-----------------------------------------------------------------*/
FS_TYPE FAT_GetFileSystemType (void)
{
	return filesysType;
}

/*-----------------------------------------------------------------
FAT_GetFileSystemTotalSize
u32 return: OUT returns the total disk space (used + free)
-----------------------------------------------------------------*/
u32 FAT_GetFileSystemTotalSize (void)
{
	return filesysTotalSize;
}



/*-----------------------------------------------------------------
FAT_chdir
Changes the current working directory
const char* path: IN null terminated string of directory separated by 
	forward slashes, / is root
bool return: OUT returns true if successful
-----------------------------------------------------------------*/
bool FAT_chdir (const char* path)
{
	DIR_ENT dir;
	if (path[0] == '/' && path[1] == '\0')
	{
		curWorkDirCluster = filesysRootDirClus;
		return true;
	}
	if (path[0] == '\0')	// Return true if changing relative to nothing
	{
		return true;
	}
	
	dir = FAT_DirEntFromPath (path);

	if (((dir.attrib & ATTRIB_DIR) == ATTRIB_DIR) && (dir.name[0] != FILE_FREE))
	{
		// Change directory
		curWorkDirCluster = dir.startCluster | (dir.startClusterHigh << 16);

		// Move to correct cluster for root directory
		if (curWorkDirCluster == FAT16_ROOT_DIR_CLUSTER)
		{
			curWorkDirCluster = filesysRootDirClus;
		}

		// Reset file position in directory
		wrkDirCluster = curWorkDirCluster;
		wrkDirSector = 0;
		wrkDirOffset = -1;
		return true;
	}
	else
	{ 
		// Couldn't change directory - wrong path specified
		return false;
	}
}

/*-----------------------------------------------------------------
FAT_fopen(filename, mode)
Opens a file
const char* path: IN null terminated string of filename and path 
	separated by forward slashes, / is root
const char* mode: IN mode to open file in
	Supported modes: "r", "r+", "w", "w+", "a", "a+", don't use
	"b" or "t" in any mode, as all files are openned in binary mode
FAT_FILE* return: OUT handle to open file, returns NULL if the file 
	couldn't be openned
-----------------------------------------------------------------*/
FAT_FILE* FAT_fopen(const char* path, const char* mode)
{
	int fileNum;
	FAT_FILE* file;
	DIR_ENT dirEntry;
#ifdef CAN_WRITE_TO_DISC
	u32 startCluster;
	int clusCount;
#endif

	char* pchTemp;
	// Check that a valid mode was specified
	pchTemp = strpbrk ( mode, "rRwWaA" );
	if (pchTemp == NULL)
	{
		return NULL;
	}
	if (strpbrk ( pchTemp+1, "rRwWaA" ) != NULL)
	{
		return NULL;
	}
		
	// Get the dirEntry for the path specified
	dirEntry = FAT_DirEntFromPath (path);
	
	// Check that it is not a directory
	if (dirEntry.attrib & ATTRIB_DIR)
	{
		return NULL;
	}

#ifdef CAN_WRITE_TO_DISC
	// Check that it is not a read only file being openned in a writing mode
	if ( (strpbrk(mode, "wWaA+") != NULL) && (dirEntry.attrib & ATTRIB_RO))
	{
		return NULL;
	}
#else
	if ( (strpbrk(mode, "wWaA+") != NULL))
	{
		return NULL;
	}
#endif

	// Find a free file buffer
	for (fileNum = 0; (fileNum < MAX_FILES_OPEN) && (openFiles[fileNum].inUse == true); fileNum++);
	
	if (fileNum == MAX_FILES_OPEN) // No free files
	{
		return NULL;
	}

	file = &openFiles[fileNum];
	// Remember where directory entry was
	file->dirEntSector = (wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector;
	file->dirEntOffset = wrkDirOffset;

	if ( strpbrk(mode, "rR") != NULL )  //(ucase(mode[0]) == 'R')
	{
		if (dirEntry.name[0] == FILE_FREE)	// File must exist
		{
			return NULL;
		}
		
		file->read = true;
#ifdef CAN_WRITE_TO_DISC
		file->write = ( strchr(mode, '+') != NULL ); //(mode[1] == '+');
#else
		file->write = false;
#endif
		file->append = false;
		
		// Store information about position within the file, for use
		// by FAT_fread, FAT_fseek, etc.
		file->firstCluster = dirEntry.startCluster | (dirEntry.startClusterHigh << 16);
	
#ifdef CAN_WRITE_TO_DISC
		// Check if file is openned for random. If it is, and currently has no cluster, one must be 
		// assigned to it.
		if (file->write && file->firstCluster == CLUSTER_FREE)
		{
			file->firstCluster = FAT_LinkFreeCluster (CLUSTER_FREE);
			if (file->firstCluster == CLUSTER_FREE)	// Couldn't get a free cluster
			{
				return NULL;
			}

			// Store cluster position into the directory entry
			dirEntry.startCluster = (file->firstCluster & 0xFFFF);
			dirEntry.startClusterHigh = ((file->firstCluster >> 16) & 0xFFFF);
			disc_ReadSector (file->dirEntSector, globalBuffer);
			((DIR_ENT*) globalBuffer)[file->dirEntOffset] = dirEntry;
			disc_WriteSector (file->dirEntSector, globalBuffer);
		}
#endif
			
		file->length = dirEntry.fileSize;
		file->curPos = 0;
		file->curClus = dirEntry.startCluster | (dirEntry.startClusterHigh << 16);
		file->curSect = 0;
		file->curByte = 0;

		// Not appending
		file->appByte = 0;
		file->appClus = 0;
		file->appSect = 0;
	
		disc_ReadSector( FAT_ClustToSect( file->curClus), file->readBuffer);
		file->inUse = true;	// We're using this file now

		return file;
	}	// mode "r"

#ifdef CAN_WRITE_TO_DISC
	if ( strpbrk(mode, "wW") != NULL ) // (ucase(mode[0]) == 'W')
	{
		if (dirEntry.name[0] == FILE_FREE)	// Create file if it doesn't exist
		{
			dirEntry.attrib = ATTRIB_ARCH;
			dirEntry.reserved = 0;
			
			// Time and date set to system time and date
			dirEntry.cTime_ms = 0;
			dirEntry.cTime = getRTCtoFileTime();
			dirEntry.cDate = getRTCtoFileDate();
			dirEntry.aDate = getRTCtoFileDate();
			dirEntry.mTime = getRTCtoFileTime();
			dirEntry.mDate = getRTCtoFileDate();
		}
		else	// Already a file entry 
		{
			// Free any clusters used
			FAT_ClearLinks (dirEntry.startCluster | (dirEntry.startClusterHigh << 16));
		}
		
		// Get a cluster to use
		startCluster = FAT_LinkFreeCluster (CLUSTER_FREE);
		if (startCluster == CLUSTER_FREE)	// Couldn't get a free cluster
		{
			return NULL;
		}

		// Store cluster position into the directory entry
		dirEntry.startCluster = (startCluster & 0xFFFF);
		dirEntry.startClusterHigh = ((startCluster >> 16) & 0xFFFF);

		// The file has no data in it - its over written so should be empty
		dirEntry.fileSize = 0;

		if (dirEntry.name[0] == FILE_FREE)	// No file
		{
			// Have to create a new entry
			if(!FAT_AddDirEntry (path, dirEntry))
			{
				return NULL;
			}
			// Get the newly created dirEntry
			dirEntry = FAT_DirEntFromPath (path);

			// Remember where directory entry was
			file->dirEntSector = (wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector;
			file->dirEntOffset = wrkDirOffset;
		}
		else	// Already a file
		{
			// Just modify the old entry
			disc_ReadSector (file->dirEntSector, globalBuffer);
			((DIR_ENT*) globalBuffer)[file->dirEntOffset] = dirEntry;
			disc_WriteSector (file->dirEntSector, globalBuffer);
		}
		

		// Now that file is created, open it
		file->read = ( strchr(mode, '+') != NULL ); //(mode[1] == '+');
		file->write = true;
		file->append = false;
		
		// Store information about position within the file, for use
		// by FAT_fread, FAT_fseek, etc.
		file->firstCluster = startCluster;
		file->length = 0;	// Should always have 0 bytes if openning in "w" mode
		file->curPos = 0;
		file->curClus = startCluster;
		file->curSect = 0;
		file->curByte = 0;

		// Not appending
		file->appByte = 0;
		file->appClus = 0;
		file->appSect = 0;
		
		// Empty file, so empty read buffer
		memset (file->readBuffer, 0, BYTE_PER_READ);
		file->inUse = true;	// We're using this file now

		return file;
	}

	if ( strpbrk(mode, "aA") != NULL ) // (ucase(mode[0]) == 'A')
	{
		if (dirEntry.name[0] == FILE_FREE)	// Create file if it doesn't exist
		{
			dirEntry.attrib = ATTRIB_ARCH;
			dirEntry.reserved = 0;
			
			// Time and date set to system time and date
			dirEntry.cTime_ms = 0;
			dirEntry.cTime = getRTCtoFileTime();
			dirEntry.cDate = getRTCtoFileDate();
			dirEntry.aDate = getRTCtoFileDate();
			dirEntry.mTime = getRTCtoFileTime();
			dirEntry.mDate = getRTCtoFileDate();

			// The file has no data in it
			dirEntry.fileSize = 0;

			// Get a cluster to use
			startCluster = FAT_LinkFreeCluster (CLUSTER_FREE);
			if (startCluster == CLUSTER_FREE)	// Couldn't get a free cluster
			{
				return NULL;
			}
			dirEntry.startCluster = (startCluster & 0xFFFF);
			dirEntry.startClusterHigh = ((startCluster >> 16) & 0xFFFF);
			
			if(!FAT_AddDirEntry (path, dirEntry))
				return NULL;
			
			// Get the newly created dirEntry
			dirEntry = FAT_DirEntFromPath (path);
			
			// Store append cluster
			file->appClus = startCluster;

			// Remember where directory entry was
			file->dirEntSector = (wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector;
			file->dirEntOffset = wrkDirOffset;
		}
		else	// File already exists - reuse the old directory entry
		{
			startCluster = dirEntry.startCluster | (dirEntry.startClusterHigh << 16);
			// If it currently has no cluster, one must be assigned to it.
			if (startCluster == CLUSTER_FREE)
			{
				file->firstCluster = FAT_LinkFreeCluster (CLUSTER_FREE);
				if (file->firstCluster == CLUSTER_FREE)	// Couldn't get a free cluster
				{
					return NULL;
				}
				
				// Store cluster position into the directory entry
				dirEntry.startCluster = (file->firstCluster & 0xFFFF);
				dirEntry.startClusterHigh = ((file->firstCluster >> 16) & 0xFFFF);
				disc_ReadSector (file->dirEntSector, globalBuffer);
				((DIR_ENT*) globalBuffer)[file->dirEntOffset] = dirEntry;
				disc_WriteSector (file->dirEntSector, globalBuffer);

				// Store append cluster
				file->appClus = startCluster;
		
			} else {

				// Follow cluster list until last one is found
				clusCount = dirEntry.fileSize / filesysBytePerClus;
				file->appClus = startCluster;
				while ((clusCount--) && (FAT_NextCluster (file->appClus) != CLUSTER_FREE) && (FAT_NextCluster (file->appClus) != CLUSTER_EOF))
				{
					file->appClus = FAT_NextCluster (file->appClus);
				}
				if (clusCount >= 0) // Check if ran out of clusters
				{
					// Set flag to allocate new cluster when needed
					file->appSect = filesysSecPerClus;
					file->appByte = 0;
				}
			}
		}

		// Now that file is created, open it
		file->read = ( strchr(mode, '+') != NULL );
		file->write = false;
		file->append = true;
		
		// Calculate the sector and byte of the current position,
		// and store them
		file->appSect = (dirEntry.fileSize % filesysBytePerClus) / BYTE_PER_READ;
		file->appByte = dirEntry.fileSize % BYTE_PER_READ;

		// Store information about position within the file, for use
		// by FAT_fread, FAT_fseek, etc.
		file->firstCluster = startCluster;
		file->length = dirEntry.fileSize;
		file->curPos = dirEntry.fileSize;
		file->curClus = file->appClus;
		file->curSect = file->appSect;
		file->curByte = file->appByte;
		
		// Read into buffer
		disc_ReadSector( FAT_ClustToSect(file->curClus) + file->curSect, file->readBuffer);
		file->inUse = true;	// We're using this file now
		return file;
	}
#endif

	// Can only reach here if a bad mode was specified
	return NULL;
}

/*-----------------------------------------------------------------
FAT_fclose(file)
Closes a file
FAT_FILE* file: IN handle of the file to close
bool return OUT: true if successful, false if not
-----------------------------------------------------------------*/
bool FAT_fclose (FAT_FILE* file)
{
	// Clear memory used by file information
	if ((file != NULL) && (file->inUse == true))
	{
#ifdef CAN_WRITE_TO_DISC
		if (file->write || file->append)
		{
			// Write new length, time and date back to directory entry
			disc_ReadSector (file->dirEntSector, globalBuffer);

			((DIR_ENT*)globalBuffer)[file->dirEntOffset].fileSize = file->length;
			((DIR_ENT*)globalBuffer)[file->dirEntOffset].mTime = getRTCtoFileTime();
			((DIR_ENT*)globalBuffer)[file->dirEntOffset].mDate = getRTCtoFileDate();
			((DIR_ENT*)globalBuffer)[file->dirEntOffset].aDate = getRTCtoFileDate();

			disc_WriteSector (file->dirEntSector, globalBuffer);

			// Flush any sectors in disc cache
			disc_CacheFlush();
		}
#endif
		file->inUse = false;		
		return true;
	}
	else
	{
		return false;
	}
}

/*-----------------------------------------------------------------
FAT_ftell(file)
Returns the current position in a file
FAT_FILE* file: IN handle of an open file
u32 OUT: Current position
-----------------------------------------------------------------*/
u32 FAT_ftell (FAT_FILE* file)
{
	// Return the position as specified in the FAT_FILE structure
	if ((file != NULL) && (file->inUse == true))
	{
		return file->curPos;
	}
	else
	{
		// Return -1 if no file was given
		return -1;
	}
}

/*-----------------------------------------------------------------
FAT_fseek(file, offset, origin)
Seeks to specified byte position in file
FAT_FILE* file: IN handle of an open file
s32 offset IN: position to seek to, relative to origin
int origin IN: origin to seek from
int OUT: Returns 0 if successful, -1 if not
-----------------------------------------------------------------*/
int FAT_fseek(FAT_FILE* file, s32 offset, int origin)
{
	u32 cluster, nextCluster;
	int clusCount;
	u32 position;
	u32 curPos;

	if ((file == NULL) || (file->inUse == false))	// invalid file
	{
		return -1;
	}

	// Can't seek in append only mode
	if (!file->read && !file->write)	
	{
		return -1;
	}

	curPos = file->curPos;

	switch (origin) 
	{
	case SEEK_SET:
		if (offset >= 0)
		{
			position = offset;
		} else {
			// Tried to seek before start of file
			position = 0;
		}
		break;
	case SEEK_CUR:
		if (offset >= 0)
		{
			position = curPos + offset;
		} 
		else if ( (u32)(offset * -1) >= curPos )
		{
			// Tried to seek before start of file
			position = 0;
		}
		else 
		{
			// Using u32 to maintain 32 bits of accuracy
			position = curPos - (u32)(offset * -1);
		}
		break;
	case SEEK_END:
		if (offset >= 0)
		{
			// Seeking to end of file
			position = file->length;	// Fixed thanks to MoonLight
		}
		else if ( (u32)(offset * -1) >= file->length )
		{
			// Tried to seek before start of file
			position = 0;
		}
		else 
		{
			// Using u32 to maintain 32 bits of accuracy
			position = file->length - (u32)(offset * -1);
		}
		break;
	default:
		return -1;
	}

	if (position > file->length)
	{
		// Tried to go past end of file
		position = file->length;
	}
	
	// Save position
	file->curPos = position;


	// Calculate where the correct cluster is
	if (position > curPos)
	{
		clusCount = (position - curPos + (file->curSect * filesysBytePerSec) + file->curByte) / filesysBytePerClus;	// Fixed thanks to AgentQ
		cluster = file->curClus;
	} else {
		clusCount = position / filesysBytePerClus;
		cluster = file->firstCluster;
	}

	// Calculate the sector and byte of the current position,
	// and store them
	file->curSect = (position % filesysBytePerClus) / BYTE_PER_READ;
	file->curByte = position % BYTE_PER_READ;

	// Follow cluster list until desired one is found
	if (clusCount > 0)	// Only look at next cluster if need to
	{
		nextCluster = FAT_NextCluster (cluster);
	} else {
		nextCluster = cluster;
	}
	while ((clusCount--) && (nextCluster != CLUSTER_FREE) && (nextCluster != CLUSTER_EOF))
	{
		cluster = nextCluster;
		nextCluster = FAT_NextCluster (cluster);
	}
	// Check if ran out of clusters, and the file is being written to
	if ((clusCount >= 0) && (file->write || file->append)) 
	{
		// Set flag to allocate a new cluster
		file->curSect = filesysSecPerClus;
		file->curByte = 0;
	}
	file->curClus = cluster;
	
	// Reload sector buffer for new position in file, if it is a different sector
	if ((curPos ^ position) >= BYTE_PER_READ)
	{
		disc_ReadSector( file->curSect + FAT_ClustToSect(file->curClus), file->readBuffer);
	}

	return 0;
}

/*-----------------------------------------------------------------
FAT_fread(buffer, size, count, file)
Reads in size * count bytes into buffer from file, starting
	from current position. It then sets the current position to the
	byte after the last byte read. If it reaches the end of file
	before filling the buffer then it stops reading.
void* buffer OUT: Pointer to buffer to fill. Should be at least as
	big as the number of bytes required
u32 size IN: size of each item to read
u32 count IN: number of items to read
FAT_FILE* file IN: Handle of an open file
u32 OUT: returns the actual number of bytes read
-----------------------------------------------------------------*/
u32 FAT_fread (void* buffer, u32 size, u32 count, FAT_FILE* file)
{
	int curByte;
	int curSect;
	u32 curClus;
	u32 tempNextCluster;
	
	int tempVar;

	char* data = (char*)buffer;

	u32 length = size * count;
	u32 remain;

	bool flagNoError = true;

	// Can't read non-existant files
	if ((file == NULL) || (file->inUse == false) || size == 0 || count == 0 || buffer == NULL)
		return 0;

	// Can only read files openned for reading
	if (!file->read)
		return 0;

	// Don't read past end of file
	if (length + file->curPos > file->length)
		length = file->length - file->curPos;

	remain = length;

	curByte = file->curByte;
	curSect = file->curSect;
	curClus = file->curClus;

	// Align to sector
	tempVar = BYTE_PER_READ - curByte;
	if (tempVar > remain)
		tempVar = remain;

	if ((tempVar < BYTE_PER_READ) && flagNoError) 
	{
		memcpy(data, &(file->readBuffer[curByte]), tempVar);
		remain -= tempVar;
		data += tempVar;

		curByte += tempVar;
		if (curByte >= BYTE_PER_READ) 
		{
			curByte = 0;
			curSect++;
		}
	}

	// align to cluster
	// tempVar is number of sectors to read
	if (remain > (filesysSecPerClus - curSect) * BYTE_PER_READ) 
	{
		tempVar = filesysSecPerClus - curSect;
	} else {
		tempVar = remain / BYTE_PER_READ;
	}

	if ((tempVar > 0) && flagNoError)
	{
		disc_ReadSectors ( curSect + FAT_ClustToSect(curClus), tempVar, data);
		data += tempVar * BYTE_PER_READ;
		remain -= tempVar * BYTE_PER_READ;

		curSect += tempVar;
	}

	// Move onto next cluster
	// It should get to here without reading anything if a cluster is due to be allocated
	if (curSect >= filesysSecPerClus)
	{
		tempNextCluster = FAT_NextCluster(curClus);
		if ((remain == 0) && (tempNextCluster == CLUSTER_EOF))
		{
			curSect = filesysSecPerClus;
		} else {
			curSect = 0;
			curClus = tempNextCluster;
			if (curClus == CLUSTER_FREE)
			{
				flagNoError = false;
			}
		}
	}

	// Read in whole clusters
	while ((remain >= filesysBytePerClus) && flagNoError)
	{
		disc_ReadSectors (FAT_ClustToSect(curClus), filesysSecPerClus, data);
		data += filesysBytePerClus;
		remain -= filesysBytePerClus;

		// Advance to next cluster
		tempNextCluster = FAT_NextCluster(curClus);
		if ((remain == 0) && (tempNextCluster == CLUSTER_EOF))
		{
			curSect = filesysSecPerClus;
		} else {
			curSect = 0;
			curClus = tempNextCluster;
			if (curClus == CLUSTER_FREE)
			{
				flagNoError = false;
			}
		}
	}

	// Read remaining sectors
	tempVar = remain / BYTE_PER_READ; // Number of sectors left
	if ((tempVar > 0) && flagNoError)
	{
		disc_ReadSectors (FAT_ClustToSect(curClus), tempVar, data);
		data += tempVar * BYTE_PER_READ;
		remain -= tempVar * BYTE_PER_READ;
		curSect += tempVar;
	}

	// Last remaining sector
	// Check if sector wanted is different to the one started with
	if ( ((file->curByte + length) >= BYTE_PER_READ) && flagNoError)
	{
		disc_ReadSector( curSect + FAT_ClustToSect( curClus), file->readBuffer);
		if (remain > 0)
		{
			memcpy(data, file->readBuffer, remain);
			curByte += remain;
			remain = 0;
		}
	}

	// Length read is the wanted length minus the stuff not read
	length = length - remain;

	// Update file information
	file->curByte = curByte;
	file->curSect = curSect;
	file->curClus = curClus;
	file->curPos = file->curPos + length;
	return length;
}

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_fwrite(buffer, size, count, file)
Writes size * count bytes into file from buffer, starting
	from current position. It then sets the current position to the
	byte after the last byte written. If the file was openned in 
	append mode it always writes to the end of the file.
const void* buffer IN: Pointer to buffer containing data. Should be
	at least as big as the number of bytes to be written.
u32 size IN: size of each item to write
u32 count IN: number of items to write
FAT_FILE* file IN: Handle of an open file
u32 OUT: returns the actual number of bytes written
-----------------------------------------------------------------*/
u32 FAT_fwrite (const void* buffer, u32 size, u32 count, FAT_FILE* file)
{
	int curByte;
	int curSect;
	u32 curClus;

	u32 tempNextCluster;
	int tempVar;
	u32 length = size * count;
	u32 remain = length;
	char* data = (char*)buffer;

	char* writeBuffer;

	bool flagNoError = true;
	bool flagAppending = false;

	if ((file == NULL) || (file->inUse == false) || length == 0 || buffer == NULL)
		return 0;

	if (file->write)
	{
		// Write at current read pointer
		curByte = file->curByte;
		curSect = file->curSect;
		curClus = file->curClus;

		// Use read buffer as write buffer
		writeBuffer = file->readBuffer;

		// If it is writing past the current end of file, set appending flag
		if (length + file->curPos > file->length)
		{
			flagAppending = true;
		}
	}
	else if (file->append)
	{
		// Write at end of file
		curByte = file->appByte;
		curSect = file->appSect;
		curClus = file->appClus;
		flagAppending = true;

		// Use global buffer as write buffer, don't touch read buffer
		writeBuffer = (char*)globalBuffer;
		disc_ReadSector(curSect + FAT_ClustToSect(curClus), writeBuffer);
	}
	else
	{
		return 0;
	}

	// Move onto next cluster if needed
	if (curSect >= filesysSecPerClus)
	{
		curSect = 0;
		tempNextCluster = FAT_NextCluster(curClus);
		if ((tempNextCluster == CLUSTER_EOF) || (tempNextCluster == CLUSTER_FREE))
		{
			// Ran out of clusters so get a new one
			curClus = FAT_LinkFreeCluster(curClus);
			if (curClus == CLUSTER_FREE) // Couldn't get a cluster, so abort
			{
				flagNoError = false;
			}
			memset(writeBuffer, 0, BYTE_PER_READ);
		} else {
			curClus = tempNextCluster;
			disc_ReadSector( FAT_ClustToSect( curClus), writeBuffer);
		}
	}
	
	// Align to sector
	tempVar = BYTE_PER_READ - curByte;
	if (tempVar > remain)
		tempVar = remain;

	if ((tempVar < BYTE_PER_READ) && flagNoError)
	{
		memcpy(&(writeBuffer[curByte]), data, tempVar);
		remain -= tempVar;
		data += tempVar;
		curByte += tempVar;

		// Write buffer back to disk
		disc_WriteSector (curSect + FAT_ClustToSect(curClus), writeBuffer);
		
		// Move onto next sector
		if (curByte >= BYTE_PER_READ) 
		{
			curByte = 0;
			curSect++;
		}
	}

	// Align to cluster
	// tempVar is number of sectors to write
	if (remain > (filesysSecPerClus - curSect) * BYTE_PER_READ) 
	{
		tempVar = filesysSecPerClus - curSect;
	} else {
		tempVar = remain / BYTE_PER_READ;
	}

	if ((tempVar > 0) && flagNoError)
	{
		disc_WriteSectors ( curSect + FAT_ClustToSect(curClus), tempVar, data);
		data += tempVar * BYTE_PER_READ;
		remain -= tempVar * BYTE_PER_READ;
		curSect += tempVar;
	}

	if (((curSect >= filesysSecPerClus) && flagNoError) && (remain > 0))
	{
		curSect = 0;
		tempNextCluster = FAT_NextCluster(curClus);
		if ((tempNextCluster == CLUSTER_EOF) || (tempNextCluster == CLUSTER_FREE))
		{
			// Ran out of clusters so get a new one
			curClus = FAT_LinkFreeCluster(curClus);
			if (curClus == CLUSTER_FREE) // Couldn't get a cluster, so abort
			{
				flagNoError = false;
			}
		} else {
			curClus = tempNextCluster;
		}
	}

	// Write whole clusters
	while ((remain >= filesysBytePerClus) && flagNoError)
	{
		disc_WriteSectors (FAT_ClustToSect(curClus), filesysSecPerClus, data);
		data += filesysBytePerClus;
		remain -= filesysBytePerClus;
		if (remain > 0)
		{
			tempNextCluster = FAT_NextCluster(curClus);
			if ((tempNextCluster == CLUSTER_EOF) || (tempNextCluster == CLUSTER_FREE))
			{
				// Ran out of clusters so get a new one
				curClus = FAT_LinkFreeCluster(curClus);
				if (curClus == CLUSTER_FREE) // Couldn't get a cluster, so abort
				{
					flagNoError = false;
					break;
				}
			} else {
				curClus = tempNextCluster;
			}
		} else {
			// Allocate a new cluster when next writing the file
			curSect = filesysSecPerClus;
		}
	}

	// Write remaining sectors
	tempVar = remain / BYTE_PER_READ; // Number of sectors left
	if ((tempVar > 0) && flagNoError)
	{
		disc_WriteSectors (FAT_ClustToSect(curClus), tempVar, data);
		data += tempVar * BYTE_PER_READ;
		remain -= tempVar * BYTE_PER_READ;
		curSect += tempVar;
	}
	
	// Last remaining sector
	// Check if sector wanted is different to the one started with
	if ( (( (file->append ? file->appByte : file->curByte) + length) >= BYTE_PER_READ) && flagNoError)
	{
		if (flagAppending)
		{
			// Zero sector before using it
			memset (writeBuffer, 0, BYTE_PER_READ);
		} else {
			// Modify existing sector
			disc_ReadSector( curSect + FAT_ClustToSect( curClus), writeBuffer);
		}
		if (remain > 0) {
			memcpy(writeBuffer, data, remain);
			curByte += remain;
			remain = 0;
			disc_WriteSector( curSect + FAT_ClustToSect( curClus), writeBuffer);
		}
	}
	
	// Amount read is the originally requested amount minus stuff remaining
	length = length - remain;

	// Update file information
	if (file->write)	// Writing also shifts the read pointer
	{
		file->curByte = curByte;
		file->curSect = curSect;
		file->curClus = curClus;
		file->curPos = file->curPos + length;
		if (file->length < file->curPos)
		{
			file->length = file->curPos;
		}
	}
	else if (file->append)	// Appending doesn't affect the read pointer
	{
		file->appByte = curByte;
		file->appSect = curSect;
		file->appClus = curClus;
		file->length = file->length + length;
	}

	return length;
}
#endif


/*-----------------------------------------------------------------
FAT_feof(file)
Returns true if the end of file has been reached
FAT_FILE* file IN: Handle of an open file
bool return OUT: true if EOF, false if not
-----------------------------------------------------------------*/
bool FAT_feof(FAT_FILE* file)
{
	if ((file == NULL) || (file->inUse == false))
		return true;	// Return eof on invalid files

	return (file->length == file->curPos);
}


#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_remove (path)
Deletes the file or empty directory sepecified in path
const char* path IN: Path of item to delete
int return OUT: zero if successful, non-zero if not
-----------------------------------------------------------------*/
int FAT_remove (const char* path)
{
	DIR_ENT dirEntry;
	u32 oldWorkDirCluster;
	char checkFilename[13];
	FILE_TYPE checkFiletype;
	
	dirEntry = FAT_DirEntFromPath (path);

	if (dirEntry.name[0] == FILE_FREE)
	{
		return -1;
	}
	
	// Only delete directories if the directory is entry
	if (dirEntry.attrib & ATTRIB_DIR)
	{
		// Change to the directory temporarily
		oldWorkDirCluster = curWorkDirCluster;
		FAT_chdir(path);

		// Search for files or directories, excluding the . and .. entries
		checkFiletype = FAT_FindFirstFile (checkFilename);
		while ((checkFilename[0] == '.')  && (checkFiletype != FT_NONE))
		{
			checkFiletype = FAT_FindNextFile (checkFilename);
		}
		
		// Change back to working directory
		curWorkDirCluster = oldWorkDirCluster;
		
		// Check that the directory is empty
		if (checkFiletype != FT_NONE)
		{
			// Directory isn't empty
			return -1;
		}
	}

	// Refresh directory information
	dirEntry = FAT_DirEntFromPath (path);

	// Free any clusters used
	FAT_ClearLinks (dirEntry.startCluster | (dirEntry.startClusterHigh << 16));

	// Remove Directory entry
	disc_ReadSector ( (wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector , globalBuffer);
	((DIR_ENT*)globalBuffer)[wrkDirOffset].name[0] = FILE_FREE;
	disc_WriteSector ( (wrkDirCluster == FAT16_ROOT_DIR_CLUSTER ? filesysRootDir : FAT_ClustToSect(wrkDirCluster)) + wrkDirSector , globalBuffer);
		
	// Flush any sectors in disc cache
	disc_CacheFlush();

	return 0;
}
#endif

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_mkdir (path)
Makes a new directory, so long as no other directory or file has 
	the same name.
const char* path IN: Path and filename of directory to make
int return OUT: zero if successful, non-zero if not
-----------------------------------------------------------------*/
int FAT_mkdir (const char* path)
{
	u32 newDirCluster;
	u32 parentDirCluster;
	DIR_ENT dirEntry;
	DIR_ENT* entries = (DIR_ENT*)globalBuffer;
	int i;

	int pathPos, filePos;
	char pathname[MAX_FILENAME_LENGTH];
	u32 oldDirCluster;

	if (FAT_FileExists(path) != FT_NONE)
	{
		return -1;	// File or directory exists with that name
	}

	// Find filename within path and change to that directory
	oldDirCluster = curWorkDirCluster;
	if (path[0] == '/')
	{
		curWorkDirCluster = filesysRootDirClus;
	}
	
	pathPos = 0;
	filePos = 0;

	while (path[pathPos + filePos] != '\0')
	{
		if (path[pathPos + filePos] == '/')
		{
			pathname[filePos] = '\0';
			if (FAT_chdir(pathname) == false) 
			{
				curWorkDirCluster = oldDirCluster;
				return -1; // Couldn't change directory
			}
			pathPos += filePos + 1;
			filePos = 0;
		}
		pathname[filePos] = path[pathPos + filePos];
		filePos++;
	}

	// Now grab the parent directory's cluster
	parentDirCluster = curWorkDirCluster;
	curWorkDirCluster = oldDirCluster;

	// Get a new cluster for the file
	newDirCluster = FAT_LinkFreeCluster(CLUSTER_FREE);

	if (newDirCluster == CLUSTER_FREE)
	{
		return -1;	// Couldn't get a new cluster for the directory
	}
	// Fill in directory entry's information
	dirEntry.attrib = ATTRIB_DIR;
	dirEntry.reserved = 0;
	// Time and date set to system time and date
	dirEntry.cTime_ms = 0;
	dirEntry.cTime = getRTCtoFileTime();
	dirEntry.cDate = getRTCtoFileDate();
	dirEntry.aDate = getRTCtoFileDate();
	dirEntry.mTime = getRTCtoFileTime();
	dirEntry.mDate = getRTCtoFileDate();
	// Store cluster position into the directory entry
	dirEntry.startCluster = (newDirCluster & 0xFFFF);
	dirEntry.startClusterHigh = ((newDirCluster >> 16) & 0xFFFF);
	// The file has no data in it - its over written so should be empty
	dirEntry.fileSize = 0;

	if (FAT_AddDirEntry (path, dirEntry) == false)
	{
		return -1;	// Couldn't add the directory entry
	}

	// Create the new directory itself
	memset(entries, FILE_LAST, BYTE_PER_READ);

	// Create . directory entry
	dirEntry.name[0] = '.';
	// Fill name and extension with spaces
	for (i = 1; i < 11; i++)
	{
		dirEntry.name[i] = ' ';
	}

	memcpy(entries, &dirEntry, sizeof(dirEntry));

	// Create .. directory entry
	dirEntry.name[1] = '.';
	dirEntry.startCluster = (parentDirCluster & 0xFFFF);
	dirEntry.startClusterHigh = ((parentDirCluster >> 16) & 0xFFFF);

	memcpy(&entries[1], &dirEntry, sizeof(dirEntry));

	// Write entry to disc
	disc_WriteSector(FAT_ClustToSect(newDirCluster), entries);

	// Flush any sectors in disc cache
	disc_CacheFlush();
	return 0;
}
#endif

/*-----------------------------------------------------------------
FAT_fgetc (handle)
Gets the next character in the file
FAT_FILE* file IN: Handle of open file
bool return OUT: character if successful, EOF if not
-----------------------------------------------------------------*/
char FAT_fgetc (FAT_FILE* file)
{
	char c;
	return (FAT_fread(&c, 1, 1, file) == 1) ? c : EOF;
}

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_fputc (character, handle)
Writes the given character into the file
char c IN: Character to be written
FAT_FILE* file IN: Handle of open file
bool return OUT: character if successful, EOF if not
-----------------------------------------------------------------*/
char FAT_fputc (char c, FAT_FILE* file)
{
	return (FAT_fwrite(&c, 1, 1, file) == 1) ? c : EOF;
}
#endif

/*-----------------------------------------------------------------
FAT_fgets (char *tgtBuffer, int num, FAT_FILE* file)
Gets a up to num bytes from file, stopping at the first
 newline.

CAUTION: does not do strictly streaming. I.e. it's 
 reading more then needed bytes and seeking back.
 shouldn't matter for random access 

char *tgtBuffer OUT: buffer to write to
int num IN: size of target buffer
FAT_FILE* file IN: Handle of open file
bool return OUT: character if successful, EOF if not

  Written by MightyMax
  Modified by Chishm - 2005-11-17
	* Added check for unix style text files
	* Removed seek when no newline is found, since it isn't necessary
-------------------------------------------------------------------*/
char *FAT_fgets(char *tgtBuffer, int num, FAT_FILE* file) 
{ 
	u32 curPos;
	u32 readLength;
	char *returnChar;
	
	// invalid filehandle 
	if (file == NULL)
	{
		return NULL ; 
	}
	
	// end of file 
	if (FAT_feof(file)==true)
	{
		return NULL ; 
	}
	
	// save current position 
	curPos = FAT_ftell(file); 
	
	// read the full buffer (max string chars is num-1 and one end of string \0 
	readLength = FAT_fread(tgtBuffer,1,num-1,file) ; 
	
	// mark least possible end of string 
	tgtBuffer[readLength] = '\0' ;    
	
	if (readLength==0) { 
		// return error 
		return NULL ; 
	}
	
	// get position of first return '\r' 
	returnChar = strchr(tgtBuffer,'\r'); 
	
	// if no return is found, search for a newline
	if (returnChar == NULL)
	{
		returnChar = strchr(tgtBuffer,'\n');
	}
	
	// Mark the return, if existant, as end of line/string 
	if (returnChar!=NULL) { 
		*returnChar++ = 0 ; 
		if (*returnChar=='\n') { // catch newline too when jumping over the end 
			// return to location after \r\n (strlen+2) 
			FAT_fseek(file,curPos+strlen(tgtBuffer)+2,SEEK_SET) ; 
			return tgtBuffer ; 
		} else { 
			// return to location after \r (strlen+1) 
			FAT_fseek(file,curPos+strlen(tgtBuffer)+1,SEEK_SET) ; 
			return tgtBuffer ; 
		}
	}
	
	return tgtBuffer ; 
}

#ifdef CAN_WRITE_TO_DISC
/*-----------------------------------------------------------------
FAT_fputs (const char *string, FAT_FILE* file)
Writes string to file, excluding end of string character
const char *string IN: string to write
FAT_FILE* file IN: Handle of open file
bool return OUT: number of characters written if successful,
	EOF if not

  Written by MightyMax
  Modified by Chishm - 2005-11-17
	* Uses FAT_FILE instead of int
	* writtenBytes is now u32 instead of int
-------------------------------------------------------------------*/
int FAT_fputs (const char *string, FAT_FILE* file) 
{ 
   u32 writtenBytes;
	// save string except end of string '\0' 
   writtenBytes = FAT_fwrite((void *)string, 1, strlen(string), file); 

   // check if we had an error 
   if (writtenBytes != strlen(string)) 
   { 
      // return EOF error 
      return EOF;
   }

   // return the charcount written 
   return writtenBytes ; 
}
#endif



