diff --git a/desmume/src/Makefile.am b/desmume/src/Makefile.am index 948ae6806..a4967c7f4 100644 --- a/desmume/src/Makefile.am +++ b/desmume/src/Makefile.am @@ -19,7 +19,7 @@ libdesmume_a_SOURCES = \ common.cpp common.h \ debug.cpp debug.h \ Disassembler.cpp Disassembler.h \ - emufile.h emufile.cpp fat.h FIFO.cpp FIFO.h \ + emufat.h emufat.cpp emufat_types.h emufile.h emufile.cpp fat.h FIFO.cpp FIFO.h \ firmware.cpp firmware.h GPU.cpp GPU.h \ GPU_osd.h \ mem.h mc.cpp mc.h \ diff --git a/desmume/src/addons/compactFlash.cpp b/desmume/src/addons/compactFlash.cpp index dd8d8d681..f70c061dd 100644 --- a/desmume/src/addons/compactFlash.cpp +++ b/desmume/src/addons/compactFlash.cpp @@ -1,7 +1,6 @@ /* Copyright (C) 2006 yopyop Copyright (C) 2006 Mic - Copyright (C) 2009 CrazyMax - Copyright (C) 2009 DeSmuME team + Copyright (C) 2009-2010 DeSmuME team This file is part of DeSmuME @@ -28,6 +27,9 @@ #include #include +#include "../emufat.h" +#include + #include #include #ifdef _MSC_VER @@ -77,9 +79,6 @@ typedef struct { #define CF_CMD_READ 0x20 #define CF_CMD_WRITE 0x30 -static int disk_image = -1; -static off_t file_size; - static u16 cf_reg_sts, cf_reg_lba1, cf_reg_lba2, @@ -119,6 +118,8 @@ static BOOL cflashDeviceEnabled = FALSE; static std::string sFlashPath; +static EMUFILE* file; + // =========================== BOOL inited; @@ -245,8 +246,14 @@ static void add_file(char *fname, FsEntry * entry, int fileLevel) } } +enum EListCallbackArg { + EListCallbackArg_Item, EListCallbackArg_Pop +}; + +typedef void (*ListCallback)(FsEntry* fs, EListCallbackArg); + // List all files and subdirectories recursively -static void list_files(const char *filepath) +static void list_files(const char *filepath, ListCallback list_callback) { char DirSpec[255+1], SubDir[255+1]; FsEntry entry; @@ -265,12 +272,14 @@ static void list_files(const char *filepath) if (hFind == NULL) return; fname = (strlen(entry.cAlternateFileName)>0) ? entry.cAlternateFileName : entry.cFileName; - add_file(fname, &entry, fileLevel); + list_callback(&entry,EListCallbackArg_Item); + //add_file(fname, &entry, fileLevel); while (FsReadNext(hFind, &entry) != 0) { fname = (strlen(entry.cAlternateFileName)>0) ? entry.cAlternateFileName : entry.cFileName; - add_file(fname, &entry, fileLevel); + //add_file(fname, &entry, fileLevel); + list_callback(&entry,EListCallbackArg_Item); printf("cflash added %s\n",fname); if (numFiles==MAXFILES-1) break; @@ -280,7 +289,8 @@ static void list_files(const char *filepath) if (strlen(fname)+strlen(filepath)+2 < 256) { sprintf(SubDir, "%s%c%s", filepath, FS_SEPARATOR, fname); - list_files(SubDir); + list_files(SubDir, list_callback); + list_callback(&entry, EListCallbackArg_Pop); } } } @@ -289,213 +299,307 @@ static void list_files(const char *filepath) FsClose(hFind); if (dwError != FS_ERR_NO_MORE_FILES) return; - if (numFiles < MAXFILES) - { - fileLink[numFiles].parent = fileLevel; - files[numFiles++].name[0] = 0; - } + //if (numFiles < MAXFILES) + //{ + // fileLink[numFiles].parent = fileLevel; + // files[numFiles++].name[0] = 0; + //} } +static u32 dataSectors = 0; +void count_ListCallback(FsEntry* fs, EListCallbackArg arg) +{ + if(arg == EListCallbackArg_Pop) return; + u32 sectors = 1; + if(fs->flags & FS_IS_DIR) + { + } + else + sectors += (fs->fileSize+511)/512 + 1; + dataSectors += sectors; +} + +static std::string currPath; +static EmuFatFile currFatFile; +static std::stack fatStack; +static std::stack pathStack; +void build_ListCallback(FsEntry* fs, EListCallbackArg arg) +{ + char* fname = (strlen(fs->cAlternateFileName)>0) ? fs->cAlternateFileName : fs->cFileName; + + if(arg == EListCallbackArg_Pop) + { + currFatFile = fatStack.top(); + fatStack.pop(); + currPath = pathStack.top(); + pathStack.pop(); + return; + } + + if(fs->flags & FS_IS_DIR) + { + if(!strcmp(fname,".")) return; + if(!strcmp(fname,"..")) return; + + pathStack.push(currPath); + fatStack.push(currFatFile); + + EmuFatFile newDir; + newDir.makeDir(&currFatFile,fname); + newDir.sync(); + currFatFile = newDir; + + currPath = currPath + std::string(1,FS_SEPARATOR) + fname; + return; + } + else + { + std::string path = currPath + std::string(1,FS_SEPARATOR) + fname; + + FILE* inf = fopen(path.c_str(),"rb"); + fseek(inf,0,SEEK_END); + long len = ftell(inf); + fseek(inf,0,SEEK_SET); + u8 *buf = new u8[len]; + fread(buf,1,len,inf); + fclose(inf); + + EmuFatFile f; + f.open(&currFatFile,fname,EO_RDWR | EO_CREAT); + f.write(buf,len); + f.close(); + delete[] buf; + } + +} + + // Set up the MBR, FAT and DIR_ENTs static BOOL cflash_build_fat() { - int i,j,k,l, - clust,numClusters, - clusterNum2,rootCluster; - int fileLevel; + dataSectors = 0; + currPath = sFlashPath; + list_files(sFlashPath.c_str(), count_ListCallback); - numFiles = 0; - fileLevel = -1; - maxLevel = -1; + dataSectors += 16*1024*1024/512; //add 16MB worth of write space. this is probably enough for anyone, but maybe it should be configurable. + //we could always suggest to users to add a big file to their directory to overwrite (that would cause the image to get padded) + + delete file; + file = new EMUFILE_MEMORY(dataSectors*512+1); + EmuFat fat(file); + EmuFatVolume vol; + u8 ok = vol.init(&fat); + vol.format(dataSectors); - files = (DIR_ENT *) malloc(MAXFILES*sizeof(DIR_ENT)); - memset(files,0,MAXFILES*sizeof(DIR_ENT)); - if (files == NULL) return FALSE; - fileLink = (FILE_INFO *) malloc(MAXFILES*sizeof(FILE_INFO)); - if (fileLink == NULL) - { - free(files); - return FALSE; - } + reconstruct(&currFatFile); + currFatFile.openRoot(&vol); - for (i=0; ibuf(),1,memf->size(),outf); + fclose(outf); - extraDirEntries[i] = NULL; - numExtraEntries[i] = 0; - } + - list_files(sFlashPath.c_str()); + //int i,j,k,l, + //clust,numClusters, + //clusterNum2,rootCluster; + //int fileLevel; - k = 0; - clusterNum = rootCluster = (SECRESV + SECPERFAT)/SECPERCLUS; - numClusters = 0; - clust = 0; - numRootFiles = 0; + //numFiles = 0; + //fileLevel = -1; + //maxLevel = -1; - // Allocate memory to hold information about the files - dirEntries = (DIR_ENT *) malloc(numFiles*sizeof(DIR_ENT)); - if (dirEntries==NULL) return FALSE; + //files = (DIR_ENT *) malloc(MAXFILES*sizeof(DIR_ENT)); + //memset(files,0,MAXFILES*sizeof(DIR_ENT)); + //if (files == NULL) return FALSE; + //fileLink = (FILE_INFO *) malloc(MAXFILES*sizeof(FILE_INFO)); + //if (fileLink == NULL) + //{ + // free(files); + // return FALSE; + //} - dirEntryLink = (FILE_INFO *) malloc(numFiles*sizeof(FILE_INFO)); - if (dirEntryLink==NULL) - { - free(dirEntries); - return FALSE; - } - dirEntriesInCluster = (int *) malloc(NUMCLUSTERS*sizeof(int)); - if (dirEntriesInCluster==NULL) - { - free(dirEntries); - free(dirEntryLink); - return FALSE; - } - dirEntryPtr = (DIR_ENT **) malloc(NUMCLUSTERS*sizeof(DIR_ENT*)); - if (dirEntryPtr==NULL) - { - free(dirEntries); - free(dirEntryLink); - free(dirEntriesInCluster); - return FALSE; - } + //for (i=0; i>8; - clust += l; - numClusters += l; - } - } - else - dirEntries[k-1].startCluster = clusterNum; - } - if (i==0) numRootFiles++; - dirEntriesInCluster[clusterNum]++; - if (dirEntriesInCluster[clusterNum]==256) - clusterNum++; - } - } - clusterNum = clusterNum2 + ((fileLink[i].filesInDir)>>8) + 1; - numClusters++; - } + // extraDirEntries[i] = NULL; + // numExtraEntries[i] = 0; + //} - // Free the file indexing buffer - free(files); - free(fileLink); + //list_files(sFlashPath.c_str()); - // Set up the MBR - MBR.bytesPerSector = 512; - MBR.numFATs = 1; - // replaced strcpy with strncpy. It doesnt matter here, as the strings are constant - // but we should extingish all unrestricted strcpy,strcat from the project - strncpy((char*)&MBR.OEMName[0],"DESMUM",8); - strncpy((char*)&MBR.fat16.fileSysType[0],"FAT16 ",8); - MBR.reservedSectors = SECRESV; - MBR.numSectors = 524288; - MBR.numSectorsSmall = 0; - MBR.sectorsPerCluster = SECPERCLUS; - MBR.sectorsPerFAT = SECPERFAT; - MBR.rootEntries = 512; - MBR.fat16.signature = 0xAA55; - MBR.mediaDesc = 1; + //k = 0; + //clusterNum = rootCluster = (SECRESV + SECPERFAT)/SECPERCLUS; + //numClusters = 0; + //clust = 0; + //numRootFiles = 0; - filesysFAT = 0 + MBR.reservedSectors; - filesysRootDir = filesysFAT + (MBR.numFATs * MBR.sectorsPerFAT); - filesysData = filesysRootDir + ((MBR.rootEntries * sizeof(DIR_ENT)) / 512); + //// Allocate memory to hold information about the files + //dirEntries = (DIR_ENT *) malloc(numFiles*sizeof(DIR_ENT)); + //if (dirEntries==NULL) return FALSE; - // Set up the cluster values for all subdirectories - clust = filesysData / SECPERCLUS; - firstDirEntCluster = clust; - for (i=1; i rootCluster) - dirEntries[i].startCluster += clust-rootCluster; - } - } - lastDirEntCluster = clust+numClusters-1; + //dirEntryLink = (FILE_INFO *) malloc(numFiles*sizeof(FILE_INFO)); + //if (dirEntryLink==NULL) + //{ + // free(dirEntries); + // return FALSE; + //} + //dirEntriesInCluster = (int *) malloc(NUMCLUSTERS*sizeof(int)); + //if (dirEntriesInCluster==NULL) + //{ + // free(dirEntries); + // free(dirEntryLink); + // return FALSE; + //} + //dirEntryPtr = (DIR_ENT **) malloc(NUMCLUSTERS*sizeof(DIR_ENT*)); + //if (dirEntryPtr==NULL) + //{ + // free(dirEntries); + // free(dirEntryLink); + // free(dirEntriesInCluster); + // return FALSE; + //} - // Set up the cluster values for all files - clust += numClusters; //clusterNum; - for (i=0; i 0) - { - if (dirEntries[i].startCluster+j < MAXFILES) - FAT16[dirEntries[i].startCluster+j] = dirEntries[i].startCluster+j+1; - j++; - l -= (512*16); - } - if ((dirEntries[i].attrib & ATTRIB_DIR)==0) - { - if (dirEntries[i].startCluster+j < MAXFILES) - FAT16[dirEntries[i].startCluster+j] = 0xFFFF; - } - k = dirEntries[i].startCluster+j; - } - } + //// Change the hierarchical layout to a flat one + //for (i=0; i<=maxLevel; i++) + //{ + // clusterNum2 = clusterNum; + // for (j=0; j>8; + // clust += l; + // numClusters += l; + // } + // } + // else + // dirEntries[k-1].startCluster = clusterNum; + // } + // if (i==0) numRootFiles++; + // dirEntriesInCluster[clusterNum]++; + // if (dirEntriesInCluster[clusterNum]==256) + // clusterNum++; + // } + // } + // clusterNum = clusterNum2 + ((fileLink[i].filesInDir)>>8) + 1; + // numClusters++; + //} - for (i=(filesysData/SECPERCLUS); i rootCluster) + // dirEntries[i].startCluster += clust-rootCluster; + // } + //} + //lastDirEntCluster = clust+numClusters-1; + + //// Set up the cluster values for all files + //clust += numClusters; //clusterNum; + //for (i=0; i 0) + // { + // if (dirEntries[i].startCluster+j < MAXFILES) + // FAT16[dirEntries[i].startCluster+j] = dirEntries[i].startCluster+j+1; + // j++; + // l -= (512*16); + // } + // if ((dirEntries[i].attrib & ATTRIB_DIR)==0) + // { + // if (dirEntries[i].startCluster+j < MAXFILES) + // FAT16[dirEntries[i].startCluster+j] = 0xFFFF; + // } + // k = dirEntries[i].startCluster+j; + // } + //} + + //for (i=(filesysData/SECPERCLUS); ifail()) { - file_size = LSEEK_FN( disk_image, 0, SEEK_END); - if (0 && file_size == -1) - { - CFLASHLOG( "Error when seeking to end of disk image" ); - } - else - { - LSEEK_FN( disk_image, 0, SEEK_SET); - CFLASHLOG( "Disk image size = %ld (%ld sectors)\n", file_size, file_size / 512); - init_good = TRUE; - } + CFLASHLOG("Failed to open file %s\n", sFlashPath.c_str()); + delete file; } - else - // TODO: create image if not exist - CFLASHLOG("Failed to open file %s: \"%s\"\n", sFlashPath.c_str(), strerror( errno)); } // READY @@ -692,126 +783,16 @@ static unsigned int cflash_read(unsigned int address) case CF_REG_DATA: if (cf_reg_cmd == CF_CMD_READ) { - if (!CFlash_IsUsingPath()) + if(file) { - if ( disk_image != -1) - { - u8 data[2]; -#if 0 - if (currLBA < buffered_start_index || currLBA >= (buffered_start_index + BUFFERED_BLOCK_SIZE)) - { - size_t read_bytes = 0; - LSEEK_FN( disk_image, currLBA, SEEK_SET); - - while (read_bytes < BUFFERED_BLOCK_SIZE) - { - size_t cur_read = READ_FN( disk_image, &block_buffer[read_bytes], - BUFFERED_BLOCK_SIZE - read_bytes); - - if ( cur_read == -1) - { - CFLASHLOG( "Error during read: %s\n", strerror(errno) ); - break; - } - read_bytes += cur_read; - } - - CFLASHLOG( "Read %d bytes\n", read_bytes); - - buffered_start_index = currLBA; - } - data[0] = block_buffer[currLBA - buffered_start_index]; - data[1] = block_buffer[currLBA + 1 - buffered_start_index]; -#else - LSEEK_FN( disk_image, currLBA, SEEK_SET); - elems_read += READ_FN( disk_image, data, 2); -#endif - ret_value = data[1] << 8 | data[0]; - } - currLBA += 2; - } - else // use path - { - unsigned char *p; - int i; - u32 cluster,cluster2,cluster3,fileLBA; - cluster = (currLBA / (512 * SECPERCLUS)); - cluster2 = (((currLBA/512) - filesysData) / SECPERCLUS) + 2; - - // Reading from the MBR - if (currLBA < 512 && currLBA >= 0) - { - p = (unsigned char*)&MBR; - ret_value = T1ReadWord(p, currLBA); - - // Reading the FAT - } - else - if (((u32)currLBA >= filesysFAT*512) && ((u32)currLBA < filesysRootDir*512)) - { - p = (unsigned char*)&FAT16[0]; - ret_value = T1ReadWord(p, currLBA - filesysFAT * 512); - - // Reading directory entries - } - else - if (((u32)currLBA >= filesysRootDir*512) && (cluster <= (u32)lastDirEntCluster)) - { - cluster3 = ((currLBA - (SECRESV * 512)) / (512 * SECPERCLUS)); - i = (currLBA-(((cluster3-(filesysRootDir/SECPERCLUS))*SECPERCLUS+filesysRootDir)*512)); //(currLBA - cluster*BYTESPERCLUS); - if (i < (dirEntriesInCluster[cluster3]*32)) - { - p = (unsigned char*)dirEntryPtr[cluster3]; - ret_value = T1ReadWord(p, i); - } - else - { - i /= 32; - i -= dirEntriesInCluster[cluster3]; - if ((i>=0)&&(i (u32)lastDirEntCluster) && (cluster2 <= (u32)lastFileDataCluster)) - { - //else if ((cluster>lastDirEntCluster)&&(cluster<=lastFileDataCluster)) { - fileLBA = currLBA - (filesysData-32)*512; // 32 = # sectors used for the root entries - - // Check if the read is from the currently opened file - if ((fileLBA >= fileStartLBA) && (fileLBA < fileEndLBA)) - { - cluster = (fileLBA / (512 * SECPERCLUS)); - ret_value = fread_buffered(activeDirEnt,cluster-dirEntries[activeDirEnt].startCluster,(fileLBA-fileStartLBA)&(BYTESPERCLUS-1)); - } - else - { - for (i=0; i=(u32)(dirEntries[i].startCluster*512*SECPERCLUS)) && - (fileLBA <(dirEntries[i].startCluster*512*SECPERCLUS)+dirEntries[i].fileSize) && - ((dirEntries[i].attrib & (ATTRIB_DIR|ATTRIB_LFN))==0)) - { - cluster = (fileLBA / (512 * SECPERCLUS)); - ret_value = fread_buffered(i,cluster-dirEntries[i].startCluster,fileLBA&(BYTESPERCLUS-1)); - break; - } - } - } - } - currLBA += 2; + u8 data[2]; + file->fseek(currLBA, SEEK_SET); + elems_read += file->fread(data,2); + ret_value = data[1] << 8 | data[0]; } + currLBA += 2; } - break; + break; case CF_REG_CMD: break; @@ -838,7 +819,6 @@ static void cflash_write(unsigned int address,unsigned int data) case CF_REG_DATA: if (cf_reg_cmd == CF_CMD_WRITE) { - if (!CFlash_IsUsingPath()) { sector_data[sector_write_index] = (data >> 0) & 0xff; sector_data[sector_write_index + 1] = (data >> 8) & 0xff; @@ -850,22 +830,21 @@ static void cflash_write(unsigned int address,unsigned int data) CFLASHLOG( "Write sector to %ld\n", currLBA); size_t written = 0; - if (currLBA + 512 < file_size) - { - if (disk_image != -1) + //TODO - calling size() every time we write something is sort of unnecessarily slow in the case of disk-backed files... + if(file) + if(currLBA + 512 < file->size()) { - LSEEK_FN( disk_image, currLBA, SEEK_SET); + file->fseek(currLBA,SEEK_SET); while(written < 512) { - size_t cur_write = - WRITE_FN( disk_image, §or_data[written], 512 - written); + size_t todo = 512-written; + file->fwrite(§or_data[written], todo); + size_t cur_write = todo; written += cur_write; - if ( cur_write == (size_t)-1) break; } } - } CFLASHLOG("Wrote %u bytes\n", written); @@ -873,9 +852,6 @@ static void cflash_write(unsigned int address,unsigned int data) sector_write_index = 0; } } - else // TODO: write to real partition - { - } } break; @@ -917,11 +893,8 @@ static void cflash_close( void) if (!inited) return; if (!CFlash_IsUsingPath()) { - if (disk_image != -1) - { - CLOSE_FN(disk_image); - disk_image = -1; - } + delete file; + file = NULL; } else { diff --git a/desmume/src/emufat.cpp b/desmume/src/emufat.cpp new file mode 100644 index 000000000..7af09d68c --- /dev/null +++ b/desmume/src/emufat.cpp @@ -0,0 +1,1525 @@ +//Copyright (C) 2009-2010 DeSmuME team +//based on Arduino SdFat Library ( http://code.google.com/p/sdfatlib/ ) +/* + * Copyright (C) 2009 by William Greiman + * + * This file is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ + +#include "emufat.h" + +static const u8 mkdosfs_bootcode[420] = +{ + 0xFE, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6E, 0x6F, 0x74, 0x20, 0x61, 0x20, 0x62, + 0x6F, 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x64, 0x69, 0x73, 0x6B, 0x2E, 0x20, 0x20, 0x50, + 0x6C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x69, 0x6E, 0x73, 0x65, 0x72, 0x74, 0x20, 0x61, 0x20, 0x62, + 0x6F, 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x66, 0x6C, 0x6F, 0x70, 0x70, 0x79, 0x20, 0x61, + 0x6E, 0x64, 0x0D, 0x0A, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6E, 0x79, 0x20, 0x6B, 0x65, + 0x79, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x72, 0x79, 0x20, 0x61, 0x67, 0x61, 0x69, 0x6E, 0x20, 0x2E, + 0x2E, 0x2E, 0x20, 0x0D, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + + + +EmuFat::EmuFat(const char* fname, bool readonly) + : m_readonly(readonly) + , m_owns(true) +{ + m_pFile = new EMUFILE_FILE(fname,readonly?"rb":"rb+"); +} + +EmuFat::EmuFat() + : m_readonly(false) + , m_owns(true) +{ + m_pFile = new EMUFILE_MEMORY(); +} + +EmuFat::EmuFat(EMUFILE* fileNotToDelete) + : m_pFile(fileNotToDelete) + , m_owns(false) + , m_readonly(false) +{ +} + +EmuFat::~EmuFat() +{ + if(m_owns) + delete m_pFile; +} + + +u8 EmuFat::cacheRawBlock(u32 blockNumber, u8 action) +{ + if (cache_.cacheBlockNumber_ != blockNumber) { + if (!cacheFlush()) return false; + if (!readBlock(blockNumber, cache_.cacheBuffer_.data)) return false; + cache_.cacheBlockNumber_ = blockNumber; + } + cache_.cacheDirty_ |= action; + return true; +} + +u8 EmuFat::cacheZeroBlock(u32 blockNumber) +{ + if (!cacheFlush()) return false; + + // loop take less flash than memset(cacheBuffer_.data, 0, 512); + for (u16 i = 0; i < 512; i++) { + cache_.cacheBuffer_.data[i] = 0; + } + cache_.cacheBlockNumber_ = blockNumber; + cacheSetDirty(); + return true; +} + +u8 EmuFat::cacheFlush() { + if (cache_.cacheDirty_) { + if (!writeBlock(cache_.cacheBlockNumber_, cache_.cacheBuffer_.data)) { + return false; + } + // mirror FAT tables + if (cache_.cacheMirrorBlock_) { + if (!writeBlock(cache_.cacheMirrorBlock_, cache_.cacheBuffer_.data)) { + return false; + } + cache_.cacheMirrorBlock_ = 0; + } + cache_.cacheDirty_ = 0; + } + return true; +} + +u8 EmuFat::readBlock(u32 block, u8* dst) +{ + m_pFile->fseek(block*512,SEEK_SET); + m_pFile->fread(dst,512); + if(m_pFile->fail()) + { + m_pFile->unfail(); + return 0; + } + return 1; +} + +u8 EmuFat::writeBlock(u32 blockNumber, const u8* src) +{ + m_pFile->fseek(blockNumber*512,SEEK_SET); + m_pFile->fwrite(src,512); + if(m_pFile->fail()) + { + m_pFile->unfail(); + return 0; + } + return 1; +} + +u8 EmuFat::readData(u32 block, u16 offset, u16 count, u8* dst) +{ + m_pFile->fseek(block*512+offset,SEEK_SET); + m_pFile->fread(dst,count); + if(m_pFile->fail()) + { + m_pFile->unfail(); + return 0; + } + return 1; +} + +void EmuFat::truncate(u32 size) +{ + m_pFile->truncate(size); +} + +//------------------------------------------------------------------------------------- + +//well, there are a lot of ways to format a disk. this is just a simple one. +//it would be nice if someone who understood fat better could modify the root +//directory setup to use reasonable code instead of magic arrays +bool EmuFatVolume::format(u32 sectors) +{ + u32 volumeStartBlock = 0; + dev_->truncate(0); + dev_->truncate(sectors*512); + if (!dev_->cacheRawBlock(volumeStartBlock, EmuFat::CACHE_FOR_WRITE)) return false; + memset(&dev_->cache_.cacheBuffer_,0,sizeof(dev_->cache_.cacheBuffer_)); + TFat32BootSector* bs = &dev_->cache_.cacheBuffer_.fbs; + TBiosParmBlock* bpb = &bs->bpb; + + bs->jmpToBootCode[0] = 0xEB; + bs->jmpToBootCode[1] = 0x3C; + bs->jmpToBootCode[2] = 0x90; + memcpy(bs->oemName,"mkdosfs",8); + bs->driveNumber = 0; + bs->reserved1 = 0; + bs->bootSignature = 0x29; + bs->volumeSerialNumber = 0; + memcpy(bs->volumeLabel," ",11); + memcpy(bs->fileSystemType,"FAT16 ",8); + memcpy(bs->bootCode,mkdosfs_bootcode,420); + bs->bootSectorSig0 = 0x55; + bs->bootSectorSig1 = 0xAA; + + bpb->bytesPerSector = 512; + bpb->sectorsPerCluster = 4; + bpb->reservedSectorCount = 1; + bpb->fatCount = 2; + bpb->rootDirEntryCount = 512; + bpb->totalSectors16 = 0; + bpb->mediaType = 0xF8; + bpb->sectorsPerFat16 = 32; + bpb->sectorsPerTrack = 32; + bpb->headCount = 64; + bpb->hiddenSectors = 0; + bpb->totalSectors32 = sectors; + bpb->fat32Flags = 0xbe0d; + bpb->fat32Version = 0x20Fd; + bpb->fat32RootCluster = 0x20202020; + bpb->fat32FSInfo = 0x2020; + bpb->fat32BackBootBlock = 0x2020; + + if(!dev_->cacheFlush()) + return false; + + if (!dev_->cacheRawBlock(1, EmuFat::CACHE_FOR_WRITE)) return false; + + static const u8 rootEntry[8] = + { + 0xF8, 0xFF, 0xFF, 0xFF, + } ; + + memcpy(dev_->cache_.cacheBuffer_.data,rootEntry,4); + + if(!dev_->cacheFlush()) + return false; + + if (!dev_->cacheRawBlock(33, EmuFat::CACHE_FOR_WRITE)) return false; + + memcpy(dev_->cache_.cacheBuffer_.data,rootEntry,4); + + if(!dev_->cacheFlush()) + return false; + + return init(dev_,0); +} + +bool EmuFatVolume::init(EmuFat* dev, u8 part) { + u32 volumeStartBlock = 0; + dev_ = dev; + // if part == 0 assume super floppy with FAT boot sector in block zero + // if part > 0 assume mbr volume with partition table + if (part) { + if (part > 4) return false; + if (!dev->cacheRawBlock(volumeStartBlock, EmuFat::CACHE_FOR_READ)) return false; + TPartitionRecord* p = &dev->cache_.cacheBuffer_.mbr.part[part-1]; + if ((p->boot & 0X7F) !=0 || + p->totalSectors < 100 || + p->firstSector == 0) { + // not a valid partition + return false; + } + volumeStartBlock = p->firstSector; + } + if (!dev->cacheRawBlock(volumeStartBlock, EmuFat::CACHE_FOR_READ)) return false; + TBiosParmBlock* bpb = &dev->cache_.cacheBuffer_.fbs.bpb; + if (bpb->bytesPerSector != 512 || + bpb->fatCount == 0 || + bpb->reservedSectorCount == 0 || + bpb->sectorsPerCluster == 0) { + // not valid FAT volume + return false; + } + fatCount_ = bpb->fatCount; + blocksPerCluster_ = bpb->sectorsPerCluster; + + // determine shift that is same as multiply by blocksPerCluster_ + clusterSizeShift_ = 0; + while (blocksPerCluster_ != (1 << clusterSizeShift_)) { + // error if not power of 2 + if (clusterSizeShift_++ > 7) return false; + } + blocksPerFat_ = bpb->sectorsPerFat16 ? + bpb->sectorsPerFat16 : bpb->sectorsPerFat32; + + fatStartBlock_ = volumeStartBlock + bpb->reservedSectorCount; + + // count for FAT16 zero for FAT32 + rootDirEntryCount_ = bpb->rootDirEntryCount; + + // directory start for FAT16 dataStart for FAT32 + rootDirStart_ = fatStartBlock_ + bpb->fatCount * blocksPerFat_; + + // data start for FAT16 and FAT32 + dataStartBlock_ = rootDirStart_ + ((32 * bpb->rootDirEntryCount + 511)/512); + + // total blocks for FAT16 or FAT32 + u32 totalBlocks = bpb->totalSectors16 ? + bpb->totalSectors16 : bpb->totalSectors32; + // total data blocks + clusterCount_ = totalBlocks - (dataStartBlock_ - volumeStartBlock); + + // divide by cluster size to get cluster count + clusterCount_ >>= clusterSizeShift_; + + // FAT type is determined by cluster count + if (clusterCount_ < 4085) { + fatType_ = 12; + } else if (clusterCount_ < 65525) { + fatType_ = 16; + } else { + rootDirStart_ = bpb->fat32RootCluster; + fatType_ = 32; + } + return true; +} + +u8 EmuFatVolume::allocContiguous(u32 count, u32* curCluster) { + // start of group + u32 bgnCluster; + + // flag to save place to start next search + u8 setStart; + + // set search start cluster + if (*curCluster) { + // try to make file contiguous + bgnCluster = *curCluster + 1; + + // don't save new start location + setStart = false; + } else { + // start at likely place for free cluster + bgnCluster = allocSearchStart_; + + // save next search start if one cluster + setStart = 1 == count; + } + // end of group + u32 endCluster = bgnCluster; + + // last cluster of FAT + u32 fatEnd = clusterCount_ + 1; + + // search the FAT for free clusters + for (u32 n = 0;; n++, endCluster++) { + // can't find space checked all clusters + if (n >= clusterCount_) return false; + + // past end - start from beginning of FAT + if (endCluster > fatEnd) { + bgnCluster = endCluster = 2; + } + u32 f; + if (!fatGet(endCluster, &f)) return false; + + if (f != 0) { + // cluster in use try next cluster as bgnCluster + bgnCluster = endCluster + 1; + } else if ((endCluster - bgnCluster + 1) == count) { + // done - found space + break; + } + } + // mark end of chain + if (!fatPutEOC(endCluster)) return false; + + // link clusters + while (endCluster > bgnCluster) { + if (!fatPut(endCluster - 1, endCluster)) return false; + endCluster--; + } + if (*curCluster != 0) { + // connect chains + if (!fatPut(*curCluster, bgnCluster)) return false; + } + // return first cluster number to caller + *curCluster = bgnCluster; + + // remember possible next free cluster + if (setStart) allocSearchStart_ = bgnCluster + 1; + + return true; +} + +u8 EmuFatVolume::fatGet(u32 cluster, u32* value) const { + if (cluster > (clusterCount_ + 1)) return false; + u32 lba = fatStartBlock_; + lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7; + if (lba != dev_->cache_.cacheBlockNumber_) { + if (!dev_->cacheRawBlock(lba, EmuFat::CACHE_FOR_READ)) return false; + } + if (fatType_ == 16) { + *value = dev_->cache_.cacheBuffer_.fat16[cluster & 0XFF]; + } else { + *value = dev_->cache_.cacheBuffer_.fat32[cluster & 0X7F] & FAT32MASK; + } + return true; +} +// Store a FAT entry +u8 EmuFatVolume::fatPut(u32 cluster, u32 value) { + // error if reserved cluster + if (cluster < 2) return false; + + // error if not in FAT + if (cluster > (clusterCount_ + 1)) return false; + + // calculate block address for entry + u32 lba = fatStartBlock_; + lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7; + + if (lba != dev_->cache_.cacheBlockNumber_) { + if (!dev_->cacheRawBlock(lba, EmuFat::CACHE_FOR_READ)) return false; + } + // store entry + if (fatType_ == 16) { + dev_->cache_.cacheBuffer_.fat16[cluster & 0xFF] = value; + } else { + dev_->cache_.cacheBuffer_.fat32[cluster & 0x7F] = value; + } + dev_->cacheSetDirty(); + + // mirror second FAT + if (fatCount_ > 1) dev_->cache_.cacheMirrorBlock_ = lba + blocksPerFat_; + return true; +} + +// return the size in bytes of a cluster chain +u8 EmuFatVolume::chainSize(u32 cluster, u32* size) const { + u32 s = 0; + do { + if (!fatGet(cluster, &cluster)) return false; + s += 512UL << clusterSizeShift_; + } while (!isEOC(cluster)); + *size = s; + return true; +} + +// free a cluster chain +u8 EmuFatVolume::freeChain(u32 cluster) { + // clear free cluster location + allocSearchStart_ = 2; + + do { + u32 next; + if (!fatGet(cluster, &next)) return false; + + // free cluster + if (!fatPut(cluster, 0)) return false; + + cluster = next; + } while (!isEOC(cluster)); + + return true; +} +u8 EmuFatVolume::readData(u32 block, u16 offset, u16 count, u8* dst) { + return dev_->readData(block, offset, count, dst); + } + +u8 EmuFatVolume::writeBlock(u32 block, const u8* dst) { + return dev_->writeBlock(block, dst); + } + +//----------------------------------------------------------------------------------- +//EmuFatFile: + +// add a cluster to a file +u8 EmuFatFile::addCluster() { + if (!vol_->allocContiguous(1, &curCluster_)) return false; + + // if first cluster of file link to directory entry + if (firstCluster_ == 0) { + firstCluster_ = curCluster_; + flags_ |= F_FILE_DIR_DIRTY; + } + return true; +} + + +// Add a cluster to a directory file and zero the cluster. +// return with first block of cluster in the cache +u8 EmuFatFile::addDirCluster(void) { + if (!addCluster()) return false; + + // zero data in cluster insure first cluster is in cache + u32 block = vol_->clusterStartBlock(curCluster_); + for (u8 i = vol_->blocksPerCluster_; i != 0; i--) { + if (!vol_->dev_->cacheZeroBlock(block + i - 1)) return false; + } + // Increase directory file size by cluster size + fileSize_ += 512UL << vol_->clusterSizeShift_; + return true; +} + + +// cache a file's directory entry +// return pointer to cached entry or null for failure +TDirectoryEntry* EmuFatFile::cacheDirEntry(u8 action) { + if (!vol_->dev_->cacheRawBlock(dirBlock_, action)) return NULL; + return vol_->dev_->cache_.cacheBuffer_.dir + dirIndex_; +} + + +/** + * Close a file and force cached data and directory information + * to be written to the storage device. + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include no file is open or an I/O error. + */ +u8 EmuFatFile::close(void) { + if (!sync())return false; + type_ = FAT_FILE_TYPE_CLOSED; + return true; +} + + +/** + * Check for contiguous file and return its raw block range. + * + * \param[out] bgnBlock the first block address for the file. + * \param[out] endBlock the last block address for the file. + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include file is not contiguous, file has zero length + * or an I/O error occurred. + */ +u8 EmuFatFile::contiguousRange(u32* bgnBlock, u32* endBlock) { + // error if no blocks + if (firstCluster_ == 0) return false; + + for (u32 c = firstCluster_; ; c++) { + u32 next; + if (!vol_->fatGet(c, &next)) return false; + + // check for contiguous + if (next != (c + 1)) { + // error if not end of chain + if (!vol_->isEOC(next)) return false; + *bgnBlock = vol_->clusterStartBlock(firstCluster_); + *endBlock = vol_->clusterStartBlock(c) + + vol_->blocksPerCluster_ - 1; + return true; + } + } +} + +//------------------------------------------------------------------------------ +/** + * Create and open a new contiguous file of a specified size. + * + * \note This function only supports short DOS 8.3 names. + * See open() for more information. + * + * \param[in] dirFile The directory where the file will be created. + * \param[in] fileName A valid DOS 8.3 file name. + * \param[in] size The desired file size. + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include \a fileName contains + * an invalid DOS 8.3 file name, the FAT volume has not been initialized, + * a file is already open, the file already exists, the root + * directory is full or an I/O error. + * + */ +u8 EmuFatFile::createContiguous(EmuFatFile* dirFile, const char* fileName, u32 size) { + // don't allow zero length file + if (size == 0) return false; + if (!open(dirFile, fileName, EO_CREAT | EO_EXCL | EO_RDWR)) return false; + + // calculate number of clusters needed + u32 count = ((size - 1) >> (vol_->clusterSizeShift_ + 9)) + 1; + + // allocate clusters + if (!vol_->allocContiguous(count, &firstCluster_)) { + remove(); + return false; + } + fileSize_ = size; + + // insure sync() will update dir entry + flags_ |= F_FILE_DIR_DIRTY; + return sync(); +} + +/** + * Return a files directory entry + * + * \param[out] dir Location for return of the files directory entry. + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + */ +u8 EmuFatFile::dirEntry(TDirectoryEntry* dir) { + // make sure fields on SD are correct + if (!sync()) return false; + + // read entry + TDirectoryEntry* p = cacheDirEntry(EmuFat::CACHE_FOR_READ); + if (!p) return false; + + // copy to caller's struct + memcpy(dir, p, sizeof(TDirectoryEntry)); + return true; +} + +/** + * Format the name field of \a dir into the 13 byte array + * \a name in standard 8.3 short name format. + * + * \param[in] dir The directory structure containing the name. + * \param[out] name A 13 byte char array for the formatted name. + */ +void EmuFatFile::dirName(const TDirectoryEntry& dir, char* name) { + u8 j = 0; + for (u8 i = 0; i < 11; i++) { + if (dir.name[i] == ' ')continue; + if (i == 8) name[j++] = '.'; + name[j++] = dir.name[i]; + } + name[j] = 0; +} + +// format directory name field from a 8.3 name string +u8 EmuFatFile::make83Name(const char* str, u8* name) { + u8 c; + u8 n = 7; // max index for part before dot + u8 i = 0; + // blank fill name and extension + while (i < 11) name[i++] = ' '; + i = 0; + while ((c = *str++) != '\0') { + if (c == '.') { + if (n == 10) return false; // only one dot allowed + n = 10; // max index for full 8.3 name + i = 8; // place for extension + } else { + // illegal FAT characters + static const char* px = "\\/:*?\"<>"; + const char* p = px; + u8 b; + while ((b = *p++)) if (b == c) return false; + // check size and only allow ASCII printable characters + if (i > n || c < 0X21 || c > 0X7E)return false; + // only upper case allowed in 8.3 names - convert lower to upper + name[i++] = c < 'a' || c > 'z' ? c : c + ('A' - 'a'); + } + } + // must have a file name, extension is optional + return name[0] != ' '; +} + +/** Make a new directory. + * + * \param[in] dir An open SdFat instance for the directory that will containing + * the new directory. + * + * \param[in] dirName A valid 8.3 DOS name for the new directory. + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include this SdFile is already open, \a dir is not a + * directory, \a dirName is invalid or already exists in \a dir. + */ +u8 EmuFatFile::makeDir(EmuFatFile* dir, const char* dirName) { + TDirectoryEntry d; + + // create a normal file + if (!open(dir, dirName, EO_CREAT | EO_EXCL | EO_RDWR)) return false; + + // convert SdFile to directory + flags_ = EO_READ; + type_ = FAT_FILE_TYPE_SUBDIR; + + // allocate and zero first cluster + if (!addDirCluster())return false; + + // force entry to SD + if (!sync()) return false; + + // cache entry - should already be in cache due to sync() call + TDirectoryEntry* p = cacheDirEntry(EmuFat::CACHE_FOR_WRITE); + if (!p) return false; + + // change directory entry attribute + p->attributes = DIR_ATT_DIRECTORY; + + // make entry for '.' + memcpy(&d, p, sizeof(d)); + for (u8 i = 1; i < 11; i++) d.name[i] = ' '; + d.name[0] = '.'; + + // cache block for '.' and '..' + u32 block = vol_->clusterStartBlock(firstCluster_); + if (!vol_->dev_->cacheRawBlock(block, EmuFat::CACHE_FOR_WRITE)) return false; + + // copy '.' to block + memcpy(&vol_->dev_->cache_.cacheBuffer_.dir[0], &d, sizeof(d)); + + // make entry for '..' + d.name[1] = '.'; + if (dir->isRoot()) { + d.firstClusterLow = 0; + d.firstClusterHigh = 0; + } else { + d.firstClusterLow = dir->firstCluster_ & 0XFFFF; + d.firstClusterHigh = dir->firstCluster_ >> 16; + } + // copy '..' to block + memcpy(&vol_->dev_->cache_.cacheBuffer_.dir[1], &d, sizeof(d)); + + // set position after '..' + curPosition_ = 2 * sizeof(d); + + // write first block + return vol_->dev_->cacheFlush(); +} + +/** + * Open a file or directory by name. + * + * \param[in] dirFile An open SdFat instance for the directory containing the + * file to be opened. + * + * \param[in] fileName A valid 8.3 DOS name for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + * OR of flags from the following list + * + * O_READ - Open for reading. + * + * O_RDONLY - Same as O_READ. + * + * O_WRITE - Open for writing. + * + * O_WRONLY - Same as O_WRITE. + * + * O_RDWR - Open for reading and writing. + * + * O_APPEND - If set, the file offset shall be set to the end of the + * file prior to each write. + * + * O_CREAT - If the file exists, this flag has no effect except as noted + * under O_EXCL below. Otherwise, the file shall be created + * + * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file exists. + * + * O_SYNC - Call sync() after each write. This flag should not be used with + * write(uint8_t), write_P(PGM_P), writeln_P(PGM_P), or the Arduino Print class. + * These functions do character at a time writes so sync() will be called + * after each byte. + * + * O_TRUNC - If the file exists and is a regular file, and the file is + * successfully opened and is not read only, its length shall be truncated to 0. + * + * \note Directory files must be opened read only. Write and truncation is + * not allowed for directory files. + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include this SdFile is already open, \a difFile is not + * a directory, \a fileName is invalid, the file does not exist + * or can't be opened in the access mode specified by oflag. + */ +u8 EmuFatFile::open(EmuFatFile* dirFile, const char* fileName, u8 oflag) { + u8 dname[11]; + TDirectoryEntry* p; + + // error if already open + if (isOpen())return false; + + if (!make83Name(fileName, dname)) return false; + vol_ = dirFile->vol_; + dirFile->rewind(); + + // bool for empty entry found + u8 emptyFound = false; + + // search for file + while (dirFile->curPosition_ < dirFile->fileSize_) { + u8 index = 0XF & (dirFile->curPosition_ >> 5); + p = dirFile->readDirCache(); + if (p == NULL) return false; + + if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) { + // remember first empty slot + if (!emptyFound) { + emptyFound = true; + dirIndex_ = index; + dirBlock_ = vol_->dev_->cache_.cacheBlockNumber_; + } + // done if no entries follow + if (p->name[0] == DIR_NAME_FREE) break; + } else if (!memcmp(dname, p->name, 11)) { + // don't open existing file if O_CREAT and O_EXCL + if ((oflag & (EO_CREAT | EO_EXCL)) == (EO_CREAT | EO_EXCL)) return false; + + // open found file + return openCachedEntry(0XF & index, oflag); + } + } + // only create file if O_CREAT and O_WRITE + if ((oflag & (EO_CREAT | EO_WRITE)) != (EO_CREAT | EO_WRITE)) return false; + + // cache found slot or add cluster if end of file + if (emptyFound) { + p = cacheDirEntry(EmuFat::CACHE_FOR_WRITE); + if (!p) return false; + } else { + if (dirFile->type_ == FAT_FILE_TYPE_ROOT16) return false; + + // add and zero cluster for dirFile - first cluster is in cache for write + if (!dirFile->addDirCluster()) return false; + + // use first entry in cluster + dirIndex_ = 0; + p = vol_->dev_->cache_.cacheBuffer_.dir; + } + // initialize as empty file + memset(p, 0, sizeof(TDirectoryEntry)); + memcpy(p->name, dname, 11); + + // set timestamps + if (dateTime_) { + // call user function + dateTime_(&p->creationDate, &p->creationTime); + } else { + // use default date/time + p->creationDate = FAT_DEFAULT_DATE; + p->creationTime = FAT_DEFAULT_TIME; + } + p->lastAccessDate = p->creationDate; + p->lastWriteDate = p->creationDate; + p->lastWriteTime = p->creationTime; + + // force write of entry to SD + if (!vol_->dev_->cacheFlush()) return false; + + // open entry in cache + return openCachedEntry(dirIndex_, oflag); +} + +/** + * Open a file by index. + * + * \param[in] dirFile An open SdFat instance for the directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + * OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC. + * + * See open() by fileName for definition of flags and return values. + * + */ +u8 EmuFatFile::open(EmuFatFile* dirFile, u16 index, u8 oflag) { + // error if already open + if (isOpen())return false; + + // don't open existing file if O_CREAT and O_EXCL - user call error + if ((oflag & (EO_CREAT | EO_EXCL)) == (EO_CREAT | EO_EXCL)) return false; + + vol_ = dirFile->vol_; + + // seek to location of entry + if (!dirFile->seekSet(32 * index)) return false; + + // read entry into cache + TDirectoryEntry* p = dirFile->readDirCache(); + if (p == NULL) return false; + + // error if empty slot or '.' or '..' + if (p->name[0] == DIR_NAME_FREE || + p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') { + return false; + } + // open cached entry + return openCachedEntry(index & 0XF, oflag); +} + +//------------------------------------------------------------------------------ +// open a cached directory entry. Assumes vol_ is initializes +u8 EmuFatFile::openCachedEntry(u8 dirIndex, u8 oflag) { + // location of entry in cache + TDirectoryEntry* p = vol_->dev_->cache_.cacheBuffer_.dir + dirIndex; + + // write or truncate is an error for a directory or read-only file + if (p->attributes & (DIR_ATT_READ_ONLY | DIR_ATT_DIRECTORY)) { + if (oflag & (EO_WRITE | EO_TRUNC)) return false; + } + // remember location of directory entry on SD + dirIndex_ = dirIndex; + dirBlock_ = vol_->dev_->cache_.cacheBlockNumber_; + + // copy first cluster number for directory fields + firstCluster_ = (u32)p->firstClusterHigh << 16; + firstCluster_ |= p->firstClusterLow; + + // make sure it is a normal file or subdirectory + if (DIR_IS_FILE(p)) { + fileSize_ = p->fileSize; + type_ = FAT_FILE_TYPE_NORMAL; + } else if (DIR_IS_SUBDIR(p)) { + if (!vol_->chainSize(firstCluster_, &fileSize_)) return false; + type_ = FAT_FILE_TYPE_SUBDIR; + } else { + return false; + } + // save open flags for read/write + flags_ = oflag & (EO_ACCMODE | EO_SYNC | EO_APPEND); + + // set to start of file + curCluster_ = 0; + curPosition_ = 0; + + // truncate file to zero length if requested + if (oflag & EO_TRUNC) return truncate(0); + return true; +} + +//------------------------------------------------------------------------------ +/** + * Open a volume's root directory. + * + * \param[in] vol The FAT volume containing the root directory to be opened. + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include the FAT volume has not been initialized + * or it a FAT12 volume. + */ +u8 EmuFatFile::openRoot(EmuFatVolume* vol) { + // error if file is already open + if (isOpen()) return false; + + if (vol->fatType() == 16) { + type_ = FAT_FILE_TYPE_ROOT16; + firstCluster_ = 0; + fileSize_ = 32 * vol->rootDirEntryCount(); + } else if (vol->fatType() == 32) { + type_ = FAT_FILE_TYPE_ROOT32; + firstCluster_ = vol->rootDirStart(); + if (!vol->chainSize(firstCluster_, &fileSize_)) return false; + } else { + // volume is not initialized or FAT12 + return false; + } + vol_ = vol; + // read only + flags_ = EO_READ; + + // set to start of file + curCluster_ = 0; + curPosition_ = 0; + + // root has no directory entry + dirBlock_ = 0; + dirIndex_ = 0; + return true; +} + +//------------------------------------------------------------------------------ +/** + * Read data from a file starting at the current position. + * + * \param[out] buf Pointer to the location that will receive the data. + * + * \param[in] nbyte Maximum number of bytes to read. + * + * \return For success read() returns the number of bytes read. + * A value less than \a nbyte, including zero, will be returned + * if end of file is reached. + * If an error occurs, read() returns -1. Possible errors include + * read() called before a file has been opened, corrupt file system + * or an I/O error occurred. + */ +s32 EmuFatFile::read(void* buf, u32 nbyte) { + u8* dst = reinterpret_cast(buf); + + // error if not open or write only + if (!isOpen() || !(flags_ & EO_READ)) return -1; + + // max bytes left in file + if (nbyte > (fileSize_ - curPosition_)) nbyte = fileSize_ - curPosition_; + + // amount left to read + u32 toRead = nbyte; + while (toRead > 0) { + u32 block; // raw device block number + u16 offset = curPosition_ & 0x1FF; // offset in block + if (type_ == FAT_FILE_TYPE_ROOT16) { + block = vol_->rootDirStart() + (curPosition_ >> 9); + } else { + u8 blockOfCluster = vol_->blockOfCluster(curPosition_); + if (offset == 0 && blockOfCluster == 0) { + // start of new cluster + if (curPosition_ == 0) { + // use first cluster in file + curCluster_ = firstCluster_; + } else { + // get next cluster from FAT + if (!vol_->fatGet(curCluster_, &curCluster_)) return -1; + } + } + block = vol_->clusterStartBlock(curCluster_) + blockOfCluster; + } + u32 n = toRead; + + // amount to be read from current block + if (n > (512UL - offset)) n = 512 - offset; + + // no buffering needed if n == 512 or user requests no buffering + if ((unbufferedRead() || n == 512) && + block != vol_->dev_->cache_.cacheBlockNumber_) { + if (!vol_->readData(block, offset, n, dst)) return -1; + dst += n; + } else { + // read block to cache and copy data to caller + if (!vol_->dev_->cacheRawBlock(block, EmuFat::CACHE_FOR_READ)) return -1; + u8* src = vol_->dev_->cache_.cacheBuffer_.data + offset; + u8* end = src + n; + while (src != end) *dst++ = *src++; + } + curPosition_ += n; + toRead -= n; + } + return nbyte; +} + +//------------------------------------------------------------------------------ +/** + * Read the next directory entry from a directory file. + * + * \param[out] dir The dir_t struct that will receive the data. + * + * \return For success readDir() returns the number of bytes read. + * A value of zero will be returned if end of file is reached. + * If an error occurs, readDir() returns -1. Possible errors include + * readDir() called before a directory has been opened, this is not + * a directory file or an I/O error occurred. + */ +s8 EmuFatFile::readDir(TDirectoryEntry* dir) { + s16 n; + // if not a directory file or miss-positioned return an error + if (!isDir() || (0x1F & curPosition_)) return -1; + + while ((n = read(dir, sizeof(TDirectoryEntry))) == sizeof(TDirectoryEntry)) { + // last entry if DIR_NAME_FREE + if (dir->name[0] == DIR_NAME_FREE) break; + // skip empty entries and entry for . and .. + if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') continue; + // return if normal file or subdirectory + if (DIR_IS_FILE_OR_SUBDIR(dir)) return (s8)n; + } + // error, end of file, or past last entry + return n < 0 ? -1 : 0; +} + +// Read next directory entry into the cache +// Assumes file is correctly positioned +TDirectoryEntry* EmuFatFile::readDirCache(void) { + // error if not directory + if (!isDir()) return NULL; + + // index of entry in cache + u8 i = (curPosition_ >> 5) & 0XF; + + // use read to locate and cache block + if (read() < 0) return NULL; + + // advance to next entry + curPosition_ += 31; + + // return pointer to entry + return (vol_->dev_->cache_.cacheBuffer_.dir + i); +} + +//------------------------------------------------------------------------------ +/** + * Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include the file read-only, is a directory, + * or an I/O error occurred. + */ +u8 EmuFatFile::remove(void) { + // free any clusters - will fail if read-only or directory + if (!truncate(0)) return false; + + // cache directory entry + TDirectoryEntry* d = cacheDirEntry(EmuFat::CACHE_FOR_WRITE); + if (!d) return false; + + // mark entry deleted + d->name[0] = DIR_NAME_DELETED; + + // set this SdFile closed + type_ = FAT_FILE_TYPE_CLOSED; + + // write entry to SD + return vol_->dev_->cacheFlush(); +} + +//------------------------------------------------------------------------------ +/** + * Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \param[in] dirFile The directory that contains the file. + * \param[in] fileName The name of the file to be removed. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include the file is a directory, is read only, + * \a dirFile is not a directory, \a fileName is not found + * or an I/O error occurred. + */ +u8 EmuFatFile::remove(EmuFatFile* dirFile, const char* fileName) { + EmuFatFile file; + if (!file.open(dirFile, fileName, EO_WRITE)) return false; + return file.remove(); +} + +//------------------------------------------------------------------------------ +/** Remove a directory file. + * + * The directory file will be removed only if it is empty and is not the + * root directory. rmDir() follows DOS and Windows and ignores the + * read-only attribute for the directory. + * + * \note This function should not be used to delete the 8.3 version of a + * directory that has a long name. For example if a directory has the + * long name "New folder" you should not delete the 8.3 name "NEWFOL~1". + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include the file is not a directory, is the root + * directory, is not empty, or an I/O error occurred. + */ +u8 EmuFatFile::rmDir(void) { + // must be open subdirectory + if (!isSubDir()) return false; + + rewind(); + + // make sure directory is empty + while (curPosition_ < fileSize_) { + TDirectoryEntry* p = readDirCache(); + if (p == NULL) return false; + // done if past last used entry + if (p->name[0] == DIR_NAME_FREE) break; + // skip empty slot or '.' or '..' + if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue; + // error not empty + if (DIR_IS_FILE_OR_SUBDIR(p)) return false; + } + // convert empty directory to normal file for remove + type_ = FAT_FILE_TYPE_NORMAL; + flags_ |= EO_WRITE; + return remove(); +} + +//------------------------------------------------------------------------------ +/** Recursively delete a directory and all contained files. + * + * This is like the Unix/Linux 'rm -rf *' if called with the root directory + * hence the name. + * + * Warning - This will remove all contents of the directory including + * subdirectories. The directory will then be removed if it is not root. + * The read-only attribute for files will be ignored. + * + * \note This function should not be used to delete the 8.3 version of + * a directory that has a long name. See remove() and rmDir(). + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + */ +u8 EmuFatFile::rmRfStar(void) { + rewind(); + while (curPosition_ < fileSize_) { + EmuFatFile f; + + // remember position + u16 index = curPosition_/32; + + TDirectoryEntry* p = readDirCache(); + if (!p) return false; + + // done if past last entry + if (p->name[0] == DIR_NAME_FREE) break; + + // skip empty slot or '.' or '..' + if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue; + + // skip if part of long file name or volume label in root + if (!DIR_IS_FILE_OR_SUBDIR(p)) continue; + + if (!f.open(this, index, EO_READ)) return false; + if (f.isSubDir()) { + // recursively delete + return rmRfStar(); + } else { + // ignore read-only + f.flags_ |= EO_WRITE; + if (!f.remove()) return false; + } + // position to next entry if required + if (curPosition_ != (32*(index + 1))) { + if (!seekSet(32*(index + 1))) return false; + } + } + // don't try to delete root + if (isRoot()) return true; + return rmDir(); +} + +u8 EmuFatFile::seekSet(u32 pos) { + // error if file not open or seek past end of file + if (!isOpen() || pos > fileSize_) return false; + + if (type_ == FAT_FILE_TYPE_ROOT16) { + curPosition_ = pos; + return true; + } + if (pos == 0) { + // set position to start of file + curCluster_ = 0; + curPosition_ = 0; + return true; + } + // calculate cluster index for cur and new position + u32 nCur = (curPosition_ - 1) >> (vol_->clusterSizeShift_ + 9); + u32 nNew = (pos - 1) >> (vol_->clusterSizeShift_ + 9); + + if (nNew < nCur || curPosition_ == 0) { + // must follow chain from first cluster + curCluster_ = firstCluster_; + } else { + // advance from curPosition + nNew -= nCur; + } + while (nNew--) { + if (!vol_->fatGet(curCluster_, &curCluster_)) return false; + } + curPosition_ = pos; + return true; +} + +//------------------------------------------------------------------------------ +/** + * The sync() call causes all modified data and directory fields + * to be written to the storage device. + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include a call to sync() before a file has been + * opened or an I/O error. + */ +u8 EmuFatFile::sync(void) { + // only allow open files and directories + if (!isOpen()) return false; + + if (flags_ & F_FILE_DIR_DIRTY) { + TDirectoryEntry* d = cacheDirEntry(EmuFat::CACHE_FOR_WRITE); + if (!d) return false; + + // do not set filesize for dir files + if (!isDir()) d->fileSize = fileSize_; + + // update first cluster fields + d->firstClusterLow = firstCluster_ & 0XFFFF; + d->firstClusterHigh = firstCluster_ >> 16; + + // set modify time if user supplied a callback date/time function + if (dateTime_) { + dateTime_(&d->lastWriteDate, &d->lastWriteTime); + d->lastAccessDate = d->lastWriteDate; + } + // clear directory dirty + flags_ &= ~F_FILE_DIR_DIRTY; + } + return vol_->dev_->cacheFlush(); +} + +//------------------------------------------------------------------------------ +/** + * Set a file's timestamps in its directory entry. + * + * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive + * OR of flags from the following list + * + * T_ACCESS - Set the file's last access date. + * + * T_CREATE - Set the file's creation date and time. + * + * T_WRITE - Set the file's last write/modification date and time. + * + * \param[in] year Valid range 1980 - 2107 inclusive. + * + * \param[in] month Valid range 1 - 12 inclusive. + * + * \param[in] day Valid range 1 - 31 inclusive. + * + * \param[in] hour Valid range 0 - 23 inclusive. + * + * \param[in] minute Valid range 0 - 59 inclusive. + * + * \param[in] second Valid range 0 - 59 inclusive + * + * \note It is possible to set an invalid date since there is no check for + * the number of days in a month. + * + * \note + * Modify and access timestamps may be overwritten if a date time callback + * function has been set by dateTimeCallback(). + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + */ +u8 EmuFatFile::timestamp(u8 flags, u16 year, u8 month, u8 day, u8 hour, u8 minute, u8 second) { + if (!isOpen() + || year < 1980 + || year > 2107 + || month < 1 + || month > 12 + || day < 1 + || day > 31 + || hour > 23 + || minute > 59 + || second > 59) { + return false; + } + TDirectoryEntry* d = cacheDirEntry(EmuFat::CACHE_FOR_WRITE); + if (!d) return false; + + u16 dirDate = FAT_DATE(year, month, day); + u16 dirTime = FAT_TIME(hour, minute, second); + if (flags & T_ACCESS) { + d->lastAccessDate = dirDate; + } + if (flags & T_CREATE) { + d->creationDate = dirDate; + d->creationTime = dirTime; + // seems to be units of 1/100 second not 1/10 as Microsoft states + d->creationTimeTenths = second & 1 ? 100 : 0; + } + if (flags & T_WRITE) { + d->lastWriteDate = dirDate; + d->lastWriteTime = dirTime; + } + vol_->dev_->cacheSetDirty(); + return sync(); +} + +//------------------------------------------------------------------------------ +/** + * Truncate a file to a specified length. The current file position + * will be maintained if it is less than or equal to \a length otherwise + * it will be set to end of file. + * + * \param[in] length The desired length for the file. + * + * \return The value one, true, is returned for success and + * the value zero, false, is returned for failure. + * Reasons for failure include file is read only, file is a directory, + * \a length is greater than the current file size or an I/O error occurs. + */ +u8 EmuFatFile::truncate(u32 length) { +// error if not a normal file or read-only + if (!isFile() || !(flags_ & EO_WRITE)) return false; + + // error if length is greater than current size + if (length > fileSize_) return false; + + // fileSize and length are zero - nothing to do + if (fileSize_ == 0) return true; + + // remember position for seek after truncation + u32 newPos = curPosition_ > length ? length : curPosition_; + + // position to last cluster in truncated file + if (!seekSet(length)) return false; + + if (length == 0) { + // free all clusters + if (!vol_->freeChain(firstCluster_)) return false; + firstCluster_ = 0; + } else { + u32 toFree; + if (!vol_->fatGet(curCluster_, &toFree)) return false; + + if (!vol_->isEOC(toFree)) { + // free extra clusters + if (!vol_->freeChain(toFree)) return false; + + // current cluster is end of chain + if (!vol_->fatPutEOC(curCluster_)) return false; + } + } + fileSize_ = length; + + // need to update directory entry + flags_ |= F_FILE_DIR_DIRTY; + + if (!sync()) return false; + + // set file to correct position + return seekSet(newPos); +} + +/** + * Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] nbyte Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a nbyte. If an error occurs, write() returns -1. Possible errors + * include write() is called before a file has been opened, write is called + * for a read-only file, device is full, a corrupt file system or an I/O error. + * + */ +s32 EmuFatFile::write(const void* buf, u32 nbyte) { + // convert void* to uint8_t* - must be before goto statements + const u8* src = reinterpret_cast(buf); + + // number of bytes left to write - must be before goto statements + u32 nToWrite = nbyte; + + // error if not a normal file or is read-only + if (!isFile() || !(flags_ & EO_WRITE)) goto writeErrorReturn; + + // seek to end of file if append flag + if ((flags_ & EO_APPEND) && curPosition_ != fileSize_) { + if (!seekEnd()) goto writeErrorReturn; + } + + while (nToWrite > 0) { + u8 blockOfCluster = vol_->blockOfCluster(curPosition_); + u16 blockOffset = curPosition_ & 0X1FF; + if (blockOfCluster == 0 && blockOffset == 0) { + // start of new cluster + if (curCluster_ == 0) { + if (firstCluster_ == 0) { + // allocate first cluster of file + if (!addCluster()) goto writeErrorReturn; + } else { + curCluster_ = firstCluster_; + } + } else { + u32 next; + if (!vol_->fatGet(curCluster_, &next)) return false; + if (vol_->isEOC(next)) { + // add cluster if at end of chain + if (!addCluster()) goto writeErrorReturn; + } else { + curCluster_ = next; + } + } + } + // max space in block + u32 n = 512 - blockOffset; + + // lesser of space and amount to write + if (n > nToWrite) n = nToWrite; + + // block for data write + u32 block = vol_->clusterStartBlock(curCluster_) + blockOfCluster; + if (n == 512) { + // full block - don't need to use cache + // invalidate cache if block is in cache + if (vol_->dev_->cache_.cacheBlockNumber_ == block) { + vol_->dev_->cache_.cacheBlockNumber_ = 0XFFFFFFFF; + } + if (!vol_->writeBlock(block, src)) goto writeErrorReturn; + src += 512; + } else { + if (blockOffset == 0 && curPosition_ >= fileSize_) { + // start of new block don't need to read into cache + if (!vol_->dev_->cacheFlush()) goto writeErrorReturn; + vol_->dev_->cache_.cacheBlockNumber_ = block; + vol_->dev_->cacheSetDirty(); + } else { + // rewrite part of block + if (!vol_->dev_->cacheRawBlock(block, EmuFat::CACHE_FOR_WRITE)) { + goto writeErrorReturn; + } + } + u8* dst = vol_->dev_->cache_.cacheBuffer_.data + blockOffset; + u8* end = dst + n; + while (dst != end) *dst++ = *src++; + } + nToWrite -= n; + curPosition_ += n; + } + if (curPosition_ > fileSize_) { + // update fileSize and insure sync will update dir entry + fileSize_ = curPosition_; + flags_ |= F_FILE_DIR_DIRTY; + } else if (dateTime_ && nbyte) { + // insure sync will update modified date and time + flags_ |= F_FILE_DIR_DIRTY; + } + + if (flags_ & EO_SYNC) { + if (!sync()) goto writeErrorReturn; + } + return nbyte; + + writeErrorReturn: + // return for write error + writeError = true; + return -1; +} diff --git a/desmume/src/emufat.h b/desmume/src/emufat.h new file mode 100644 index 000000000..2745d6ef7 --- /dev/null +++ b/desmume/src/emufat.h @@ -0,0 +1,699 @@ +//Copyright (C) 2009-2010 DeSmuME team +//based on Arduino SdFat Library ( http://code.google.com/p/sdfatlib/ ) +/* + * Copyright (C) 2009 by William Greiman + * + * This file is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ + +#ifndef EMUFAT_H +#define EMUFAT_H + +#include "emufat_types.h" +#include "emufile.h" +#include + +// use the gnu style oflag in open() +/** open() oflag for reading */ +static const u8 EO_READ = 0X01; +/** open() oflag - same as O_READ */ +static const u8 EO_RDONLY = EO_READ; +/** open() oflag for write */ +static const u8 EO_WRITE = 0X02; +/** open() oflag - same as O_WRITE */ +static const u8 EO_WRONLY = EO_WRITE; +/** open() oflag for reading and writing */ +static const u8 EO_RDWR = (EO_READ | EO_WRITE); +/** open() oflag mask for access modes */ +static const u8 EO_ACCMODE = (EO_READ | EO_WRITE); +/** The file offset shall be set to the end of the file prior to each write. */ +static const u8 EO_APPEND = 0X04; +/** synchronous writes - call sync() after each write */ +static const u8 EO_SYNC = 0X08; +/** create the file if nonexistent */ +static const u8 EO_CREAT = 0X10; +/** If O_CREAT and O_EXCL are set, open() shall fail if the file exists */ +static const u8 EO_EXCL = 0X20; +/** truncate the file to zero length */ +static const u8 EO_TRUNC = 0X40; + + +//Value for byte 510 of boot block or MBR +static const u8 BOOTSIG0 = 0X55; +//Value for byte 511 of boot block or MBR +static const u8 BOOTSIG1 = 0XAA; + +static void (*dateTime_)(u16* date, u16* time) = NULL; + + +#include "PACKED.h" + +//A partition table entry for a MBR formatted storage device. +//The MBR partition table has four entries. +struct __PACKED TPartitionRecord { + //Boot Indicator . Indicates whether the volume is the active + //partition. Legal values include: 0X00. Do not use for booting. + //0X80 Active partition. + u8 boot; + + //Head part of Cylinder-head-sector address of the first block in + //the partition. Legal values are 0-255. Only used in old PC BIOS. + u8 beginHead; + + struct + { + //Sector part of Cylinder-head-sector address of the first block in + //the partition. Legal values are 1-63. Only used in old PC BIOS. + u32 beginSector : 6; + //High bits cylinder for first block in partition. + u32 beginCylinderHigh : 2; + }; + + //Combine beginCylinderLow with beginCylinderHigh. Legal values + //are 0-1023. Only used in old PC BIOS. + u8 beginCylinderLow; + //Partition type. See defines that begin with PART_TYPE_ for + //some Microsoft partition types. + u8 type; + + //head part of cylinder-head-sector address of the last sector in the + //partition. Legal values are 0-255. Only used in old PC BIOS. + u8 endHead; + + struct + { + //Sector part of cylinder-head-sector address of the last sector in + //the partition. Legal values are 1-63. Only used in old PC BIOS. + u32 endSector : 6; + // High bits of end cylinder + u32 endCylinderHigh : 2; + }; + + //Combine endCylinderLow with endCylinderHigh. Legal values + //are 0-1023. Only used in old PC BIOS. + u8 endCylinderLow; + + //Logical block address of the first block in the partition. + u32 firstSector; + + //Length of the partition, in blocks. + u32 totalSectors; +}; + +//Master Boot Record: +//The first block of a storage device that is formatted with a MBR. +struct __PACKED TMasterBootRecord { + //Code Area for master boot program. + u8 codeArea[440]; + //Optional WindowsNT disk signature. May contain more boot code. + u32 diskSignature; + //Usually zero but may be more boot code. + u16 usuallyZero; + //Partition tables. + TPartitionRecord part[4]; + //First MBR signature byte. Must be 0X55 + u8 mbrSig0; + //Second MBR signature byte. Must be 0XAA + u8 mbrSig1; +}; + +//BIOS parameter block +//The BIOS parameter block describes the physical layout of a FAT volume. +struct __PACKED TBiosParmBlock { + //Count of bytes per sector. This value may take on only the + //following values: 512, 1024, 2048 or 4096 + u16 bytesPerSector; + //Number of sectors per allocation unit. This value must be a + //power of 2 that is greater than 0. The legal values are + //1, 2, 4, 8, 16, 32, 64, and 128. + u8 sectorsPerCluster; + //Number of sectors before the first FAT. + //This value must not be zero. + u16 reservedSectorCount; + //The count of FAT data structures on the volume. This field should + //always contain the value 2 for any FAT volume of any type. + u8 fatCount; + //For FAT12 and FAT16 volumes, this field contains the count of + //32-byte directory entries in the root directory. For FAT32 volumes, + //this field must be set to 0. For FAT12 and FAT16 volumes, this + //value should always specify a count that when multiplied by 32 + //results in a multiple of bytesPerSector. FAT16 volumes should + //use the value 512. + u16 rootDirEntryCount; + //This field is the old 16-bit total count of sectors on the volume. + //This count includes the count of all sectors in all four regions + //of the volume. This field can be 0; if it is 0, then totalSectors32 + //must be non-zero. For FAT32 volumes, this field must be 0. For + //FAT12 and FAT16 volumes, this field contains the sector count, and + //totalSectors32 is 0 if the total sector count fits + //(is less than 0x10000). + u16 totalSectors16; + //This dates back to the old MS-DOS 1.x media determination and is + //no longer usually used for anything. 0xF8 is the standard value + //for fixed (non-removable) media. For removable media, 0xF0 is + //frequently used. Legal values are 0xF0 or 0xF8-0xFF. + u8 mediaType; + //Count of sectors occupied by one FAT on FAT12/FAT16 volumes. + //On FAT32 volumes this field must be 0, and sectorsPerFat32 + //contains the FAT size count. + u16 sectorsPerFat16; + //Sectors per track for interrupt 0x13. Not used otherwise. + u16 sectorsPerTrack; + //Number of heads for interrupt 0x13. Not used otherwise. + u16 headCount; + //Count of hidden sectors preceding the partition that contains this + //FAT volume. This field is generally only relevant for media + //visible on interrupt 0x13. + u32 hiddenSectors; + //This field is the new 32-bit total count of sectors on the volume. + //This count includes the count of all sectors in all four regions + //of the volume. This field can be 0; if it is 0, then + //totalSectors16 must be non-zero. + u32 totalSectors32; + //Count of sectors occupied by one FAT on FAT32 volumes. + u32 sectorsPerFat32; + //This field is only defined for FAT32 media and does not exist on + //FAT12 and FAT16 media. + //Bits 0-3 -- Zero-based number of active FAT. + // Only valid if mirroring is disabled. + //Bits 4-6 -- Reserved. + //Bit 7 -- 0 means the FAT is mirrored at runtime into all FATs. + // -- 1 means only one FAT is active; it is the one referenced in bits 0-3. + //Bits 8-15 -- Reserved. + u16 fat32Flags; + //FAT32 version. High byte is major revision number. + //Low byte is minor revision number. Only 0.0 define. + u16 fat32Version; + //Cluster number of the first cluster of the root directory for FAT32. + //This usually 2 but not required to be 2. + u32 fat32RootCluster; + //Sector number of FSINFO structure in the reserved area of the + //FAT32 volume. Usually 1. + u16 fat32FSInfo; + //If non-zero, indicates the sector number in the reserved area + //of the volume of a copy of the boot record. Usually 6. + //No value other than 6 is recommended. + u16 fat32BackBootBlock; + //Reserved for future expansion. Code that formats FAT32 volumes + //should always set all of the bytes of this field to 0. + u8 fat32Reserved[12]; +}; + +//Boot sector for a FAT16 or FAT32 volume. +struct __PACKED TFat32BootSector { + //X86 jmp to boot program + u8 jmpToBootCode[3]; + //informational only - don't depend on it + u8 oemName[8]; + //BIOS Parameter Block + TBiosParmBlock bpb; + //for int0x13 use value 0X80 for hard drive + u8 driveNumber; + //used by Windows NT - should be zero for FAT + u8 reserved1; + //0X29 if next three fields are valid + u8 bootSignature; + //usually generated by combining date and time + u32 volumeSerialNumber; + //should match volume label in root dir + u8 volumeLabel[11]; + //informational only - don't depend on it + u8 fileSystemType[8]; + //X86 boot code + u8 bootCode[420]; + //must be 0X55 + u8 bootSectorSig0; + //must be 0XAA + u8 bootSectorSig1; +}; + +#include "PACKED_END.h" + +// End Of Chain values for FAT entries +//FAT16 end of chain value used by Microsoft. +static const u16 FAT16EOC = 0XFFFF; +//Minimum value for FAT16 EOC. Use to test for EOC. +static const u16 FAT16EOC_MIN = 0XFFF8; +//FAT32 end of chain value used by Microsoft. +static const u32 FAT32EOC = 0X0FFFFFFF; +//Minimum value for FAT32 EOC. Use to test for EOC. +static const u32 FAT32EOC_MIN = 0X0FFFFFF8; +//Mask a for FAT32 entry. Entries are 28 bits. +static const u32 FAT32MASK = 0X0FFFFFFF; + +//------------------------------------------------------------------------------ + + +//\struct directoryEntry +//\brief FAT short directory entry +//Short means short 8.3 name, not the entry size. +// +//Date Format. A FAT directory entry date stamp is a 16-bit field that is +//basically a date relative to the MS-DOS epoch of 01/01/1980. Here is the +//format (bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the +//16-bit word): +// +//Bits 9-15: Count of years from 1980, valid value range 0-127 +//inclusive (1980-2107). +// +//Bits 5-8: Month of year, 1 = January, valid value range 1-12 inclusive. +//Bits 0-4: Day of month, valid value range 1-31 inclusive. +//Time Format. A FAT directory entry time stamp is a 16-bit field that has +//a granularity of 2 seconds. Here is the format (bit 0 is the LSB of the +//16-bit word, bit 15 is the MSB of the 16-bit word). +// +//Bits 11-15: Hours, valid value range 0-23 inclusive. +// +//Bits 5-10: Minutes, valid value range 0-59 inclusive. +// +//Bits 0-4: 2-second count, valid value range 0-29 inclusive (0 - 58 seconds). +// +//The valid time range is from Midnight 00:00:00 to 23:59:58. +struct TDirectoryEntry { + //Short 8.3 name. + //The first eight bytes contain the file name with blank fill. + //The last three bytes contain the file extension with blank fill. + + u8 name[11]; + //Entry attributes. + //The upper two bits of the attribute byte are reserved and should + //always be set to 0 when a file is created and never modified or + //looked at after that. See defines that begin with DIR_ATT_. + + u8 attributes; + + //Reserved for use by Windows NT. Set value to 0 when a file is + //created and never modify or look at it after that. + + u8 reservedNT; + + //The granularity of the seconds part of creationTime is 2 seconds + //so this field is a count of tenths of a second and its valid + //value range is 0-199 inclusive. (WHG note - seems to be hundredths) + + u8 creationTimeTenths; + //Time file was created. + u16 creationTime; + //Date file was created. + u16 creationDate; + + //Last access date. Note that there is no last access time, only + //a date. This is the date of last read or write. In the case of + //a write, this should be set to the same date as lastWriteDate. + + u16 lastAccessDate; + + //High word of this entry's first cluster number (always 0 for a + //FAT12 or FAT16 volume). + + u16 firstClusterHigh; + //Time of last write. File creation is considered a write. + u16 lastWriteTime; + // Date of last write. File creation is considered a write. + u16 lastWriteDate; + // Low word of this entry's first cluster number. + u16 firstClusterLow; + //32-bit unsigned holding this file's size in bytes. + u32 fileSize; +}; + +//escape for name[0] = 0xE5 +static const u8 DIR_NAME_0XE5 = 0x05; +//name[0] value for entry that is free after being "deleted" +static const u8 DIR_NAME_DELETED = 0xE5; +//name[0] value for entry that is free and no allocated entries follow +static const u8 DIR_NAME_FREE = 0x00; +//file is read-only +static const u8 DIR_ATT_READ_ONLY = 0x01; +//File should hidden in directory listings +static const u8 DIR_ATT_HIDDEN = 0x02; +//Entry is for a system file +static const u8 DIR_ATT_SYSTEM = 0x04; +//Directory entry contains the volume label +static const u8 DIR_ATT_VOLUME_ID = 0x08; +//Entry is for a directory +static const u8 DIR_ATT_DIRECTORY = 0x10; +//Old DOS archive bit for backup support +static const u8 DIR_ATT_ARCHIVE = 0x20; +//Test value for long name entry. Test is + //(d->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME. +static const u8 DIR_ATT_LONG_NAME = 0x0F; +//Test mask for long name entry +static const u8 DIR_ATT_LONG_NAME_MASK = 0x3F; +//defined attribute bits +static const u8 DIR_ATT_DEFINED_BITS = 0x3F; +//Directory entry is part of a long name +static inline u8 DIR_IS_LONG_NAME(const TDirectoryEntry* dir) { + return (dir->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME; +} +//Mask for file/subdirectory tests +static const u8 DIR_ATT_FILE_TYPE_MASK = (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY); +//Directory entry is for a file +static inline u8 DIR_IS_FILE(const TDirectoryEntry* dir) { + return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == 0; +} +//Directory entry is for a subdirectory +static inline u8 DIR_IS_SUBDIR(const TDirectoryEntry* dir) { + return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == DIR_ATT_DIRECTORY; +} +//Directory entry is for a file or subdirectory +static inline u8 DIR_IS_FILE_OR_SUBDIR(const TDirectoryEntry* dir) { + return (dir->attributes & DIR_ATT_VOLUME_ID) == 0; +} + +// flags for timestamp +/** set the file's last access date */ +static const u8 T_ACCESS = 1; +/** set the file's creation date and time */ +static const u8 T_CREATE = 2; +/** Set the file's write date and time */ +static const u8 T_WRITE = 4; +// values for type_ +/** This SdFile has not been opened. */ +static const u8 FAT_FILE_TYPE_CLOSED = 0; +/** SdFile for a file */ +static const u8 FAT_FILE_TYPE_NORMAL = 1; +/** SdFile for a FAT16 root directory */ +static const u8 FAT_FILE_TYPE_ROOT16 = 2; +/** SdFile for a FAT32 root directory */ +static const u8 FAT_FILE_TYPE_ROOT32 = 3; +/** SdFile for a subdirectory */ +static const u8 FAT_FILE_TYPE_SUBDIR = 4; +/** Test value for directory type */ +static const u8 FAT_FILE_TYPE_MIN_DIR = FAT_FILE_TYPE_ROOT16; + +/** date field for FAT directory entry */ +static inline u16 FAT_DATE(u16 year, u8 month, u8 day) { + return (year - 1980) << 9 | month << 5 | day; +} +/** year part of FAT directory date field */ +static inline u16 FAT_YEAR(u16 fatDate) { + return 1980 + (fatDate >> 9); +} +/** month part of FAT directory date field */ +static inline u8 FAT_MONTH(u16 fatDate) { + return (fatDate >> 5) & 0XF; +} +/** day part of FAT directory date field */ +static inline u8 FAT_DAY(u16 fatDate) { + return fatDate & 0X1F; +} +/** time field for FAT directory entry */ +static inline u16 FAT_TIME(u8 hour, u8 minute, u8 second) { + return hour << 11 | minute << 5 | second >> 1; +} +/** hour part of FAT directory time field */ +static inline u8 FAT_HOUR(u16 fatTime) { + return fatTime >> 11; +} +/** minute part of FAT directory time field */ +static inline u8 FAT_MINUTE(u16 fatTime) { + return(fatTime >> 5) & 0X3F; +} +/** second part of FAT directory time field */ +static inline u8 FAT_SECOND(u16 fatTime) { + return 2*(fatTime & 0X1F); +} +/** Default date for file timestamps is 1 Jan 2000 */ +static const u16 FAT_DEFAULT_DATE = ((2000 - 1980) << 9) | (1 << 5) | 1; +/** Default time for file timestamp is 1 am */ +static const u16 FAT_DEFAULT_TIME = (1 << 11); + +//------------------------------------------------------ + +class EmuFat; +class EmuFatVolume; +class EmuFatFile; + +union cache_t { + /** Used to access cached file data blocks. */ + u8 data[512]; + /** Used to access cached FAT16 entries. */ + u16 fat16[256]; + /** Used to access cached FAT32 entries. */ + u32 fat32[128]; + /** Used to access cached directory entries. */ + TDirectoryEntry dir[16]; + /** Used to access a cached MasterBoot Record. */ + TMasterBootRecord mbr; + /** Used to access to a cached FAT boot sector. */ + TFat32BootSector fbs; +}; + +class EmuFatFile +{ + public: + /** Create an instance of EmuFatFile. */ + EmuFatFile() : type_(FAT_FILE_TYPE_CLOSED) {} + + bool writeError; + void clearUnbufferedRead(void) { + flags_ &= ~F_FILE_UNBUFFERED_READ; + } + void setUnbufferedRead(void) { + if (isFile()) flags_ |= F_FILE_UNBUFFERED_READ; + } + u8 unbufferedRead(void) const { + return flags_ & F_FILE_UNBUFFERED_READ; + } + + u8 close(void); + u8 contiguousRange(u32* bgnBlock, u32* endBlock); + u8 createContiguous(EmuFatFile* dirFile, const char* fileName, u32 size); + /** \return The current cluster number for a file or directory. */ + u32 curCluster(void) const {return curCluster_;} + /** \return The current position for a file or directory. */ + u32 curPosition(void) const {return curPosition_;} + + u8 rmDir(void); + u8 rmRfStar(void); + s16 read(void) { + u8 b; + return read(&b, 1) == 1 ? b : -1; + } + s32 read(void* buf, u32 nbyte); + s8 readDir(TDirectoryEntry* dir); + s32 write(const void* buf, u32 nbyte); + + u8 openRoot(EmuFatVolume* vol); + u8 timestamp(u8 flag, u16 year, u8 month, u8 day, u8 hour, u8 minute, u8 second); + u8 sync(void); + u8 makeDir(EmuFatFile* dir, const char* dirName); + u8 open(EmuFatFile* dirFile, u16 index, u8 oflag); + u8 open(EmuFatFile* dirFile, const char* fileName, u8 oflag); + u8 remove(EmuFatFile* dirFile, const char* fileName); + u8 remove(void); + u8 dirEntry(TDirectoryEntry* dir); + u8 seekCur(u32 pos) { + return seekSet(curPosition_ + pos); + } + /** + * Set the files current position to end of file. Useful to position + * a file for append. See seekSet(). + */ + u8 seekEnd(void) {return seekSet(fileSize_);} + u8 seekSet(u32 pos); + + u8 type(void) const {return type_;} + u8 truncate(u32 size); + + u32 dirBlock(void) const {return dirBlock_;} + /** \return Index of this file's directory in the block dirBlock. */ + u8 dirIndex(void) const {return dirIndex_;} + static void dirName(const TDirectoryEntry& dir, char* name); + /** \return The total number of bytes in a file or directory. */ + u32 fileSize(void) const {return fileSize_;} + /** \return The first cluster number for a file or directory. */ + u32 firstCluster(void) const {return firstCluster_;} + /** \return True if this is a SdFile for a directory else false. */ + u8 isDir(void) const {return type_ >= FAT_FILE_TYPE_MIN_DIR;} + /** \return True if this is a SdFile for a file else false. */ + u8 isFile(void) const {return type_ == FAT_FILE_TYPE_NORMAL;} + /** \return True if this is a SdFile for an open file/directory else false. */ + u8 isOpen(void) const {return type_ != FAT_FILE_TYPE_CLOSED;} + /** \return True if this is a SdFile for a subdirectory else false. */ + u8 isSubDir(void) const {return type_ == FAT_FILE_TYPE_SUBDIR;} + /** \return True if this is a SdFile for the root directory. */ + u8 isRoot(void) const { + return type_ == FAT_FILE_TYPE_ROOT16 || type_ == FAT_FILE_TYPE_ROOT32; + } + + /** Set the file's current position to zero. */ + void rewind(void) { + curPosition_ = curCluster_ = 0; + } + + +private: + // bits defined in flags_ + // should be 0XF + static const u8 F_OFLAG = (EO_ACCMODE | EO_APPEND | EO_SYNC); + // available bits + static const u8 F_UNUSED = 0x30; + // use unbuffered SD read + static const u8 F_FILE_UNBUFFERED_READ = 0X40; + // sync of directory entry required + static const u8 F_FILE_DIR_DIRTY = 0x80; +// make sure F_OFLAG is ok +#if ((F_UNUSED | F_FILE_UNBUFFERED_READ | F_FILE_DIR_DIRTY) & F_OFLAG) +#error flags_ bits conflict +#endif // flags_ bits + + // private data + u8 flags_; // See above for definition of flags_ bits + u8 type_; // type of file see above for values + u32 curCluster_; // cluster for current file position + u32 curPosition_; // current file position in bytes from beginning + u32 dirBlock_; // SD block that contains directory entry for file + u8 dirIndex_; // index of entry in dirBlock 0 <= dirIndex_ <= 0XF + u32 fileSize_; // file size in bytes + u32 firstCluster_; // first cluster of file + EmuFatVolume* vol_; // volume where file is located + + // private functions + u8 addCluster(void); + u8 addDirCluster(void); + TDirectoryEntry* cacheDirEntry(u8 action); + static u8 make83Name(const char* str, u8* name); + u8 openCachedEntry(u8 cacheIndex, u8 oflags); + TDirectoryEntry* readDirCache(void); +}; + +class EmuFatVolume +{ +public: + EmuFatVolume() :allocSearchStart_(2), fatType_(0) {} + + //Initialize a FAT volume. Try partition one first then try super floppy format. + //dev The Sd2Card where the volume is located. + //return The value one, true, is returned for success and + //the value zero, false, is returned for failure. Reasons for + //failure include not finding a valid partition, not finding a valid + //FAT file system or an I/O error. + bool init(EmuFat* dev) { return init(dev, 1) ? true : init(dev, 0);} + bool init(EmuFat* dev, u8 part); + + bool format(u32 sectors); + + // inline functions that return volume info + //The volume's cluster size in blocks. + u8 blocksPerCluster(void) const {return blocksPerCluster_;} + //The number of blocks in one FAT. + u32 blocksPerFat(void) const {return blocksPerFat_;} + //The total number of clusters in the volume. // + u32 clusterCount(void) const {return clusterCount_;} + //The shift count required to multiply by blocksPerCluster. // + u8 clusterSizeShift(void) const {return clusterSizeShift_;} + //The logical block number for the start of file data. // + u32 dataStartBlock(void) const {return dataStartBlock_;} + //The number of FAT structures on the volume. // + u8 fatCount(void) const {return fatCount_;} + //The logical block number for the start of the first FAT. // + u32 fatStartBlock(void) const {return fatStartBlock_;} + //The FAT type of the volume. Values are 12, 16 or 32. // + u8 fatType(void) const {return fatType_;} + //The number of entries in the root directory for FAT16 volumes. // + u32 rootDirEntryCount(void) const {return rootDirEntryCount_;} + //The logical block number for the start of the root directory on FAT16 volumes or the first cluster number on FAT32 volumes. // + u32 rootDirStart(void) const {return rootDirStart_;} + + EmuFat* dev_; + + +private: + friend class EmuFatFile; + + u32 allocSearchStart_; // start cluster for alloc search + u8 blocksPerCluster_; // cluster size in blocks + u32 blocksPerFat_; // FAT size in blocks + u32 clusterCount_; // clusters in one FAT + u8 clusterSizeShift_; // shift to convert cluster count to block count + u32 dataStartBlock_; // first data block number + u8 fatCount_; // number of FATs on volume + u32 fatStartBlock_; // start block for first FAT + u8 fatType_; // volume type (12, 16, OR 32) + u16 rootDirEntryCount_; // number of entries in FAT16 root dir + u32 rootDirStart_; // root start block for FAT16, cluster for FAT32 + + u8 allocContiguous(u32 count, u32* curCluster); + u8 blockOfCluster(u32 position) const { + return (position >> 9) & (blocksPerCluster_ - 1);} + u32 clusterStartBlock(u32 cluster) const { + return dataStartBlock_ + ((cluster - 2) << clusterSizeShift_);} + u32 blockNumber(u32 cluster, u32 position) const { + return clusterStartBlock(cluster) + blockOfCluster(position);} + u8 fatGet(u32 cluster, u32* value) const; + u8 fatPut(u32 cluster, u32 value); + u8 fatPutEOC(u32 cluster) { + return fatPut(cluster, 0x0FFFFFFF); + } + u8 chainSize(u32 beginCluster, u32* size) const; + u8 freeChain(u32 cluster); + u8 isEOC(u32 cluster) const { + return cluster >= (fatType_ == 16 ? FAT16EOC_MIN : FAT32EOC_MIN); + } + u8 readData(u32 block, u16 offset, u16 count, u8* dst); + u8 writeBlock(u32 block, const u8* dst); + +}; + +class EmuFat +{ +public: + EmuFat(const char* fname, bool readonly=false); + EmuFat(); + EmuFat(EMUFILE* fileNotToDelete); + virtual ~EmuFat(); + +private: + EMUFILE* m_pFile; + bool m_readonly, m_owns; + + friend class EmuFatVolume; + friend class EmuFatFile; + + // value for action argument in cacheRawBlock to indicate read from cache + static const u8 CACHE_FOR_READ = 0; + // value for action argument in cacheRawBlock to indicate cache dirty + static const u8 CACHE_FOR_WRITE = 1; + + struct Cache + { + Cache() + : cacheBlockNumber_(0xFFFFFFFF) + , cacheDirty_(0) // cacheFlush() will write block if true + , cacheMirrorBlock_(0) // mirror block for second FAT + {} + + cache_t cacheBuffer_; // 512 byte cache for device blocks + u32 cacheBlockNumber_; // Logical number of block in the cache + u8 cacheDirty_; // cacheFlush() will write block if true + u32 cacheMirrorBlock_; // block number for mirror FAT + } cache_; + + u8 cacheRawBlock(u32 blockNumber, u8 action); + void cacheSetDirty() {cache_.cacheDirty_ |= CACHE_FOR_WRITE;} + u8 cacheZeroBlock(u32 blockNumber); + u8 cacheFlush(); + + //IO functions: + u8 readBlock(u32 block, u8* dst); + u8 writeBlock(u32 blockNumber, const u8* src); + u8 readData(u32 block, u16 offset, u16 count, u8* dst); + + void truncate(u32 size); +}; + +#endif //EMUFAT_H diff --git a/desmume/src/emufat_types.h b/desmume/src/emufat_types.h new file mode 100644 index 000000000..cd64dfddc --- /dev/null +++ b/desmume/src/emufat_types.h @@ -0,0 +1,6 @@ +#ifndef EMUFAT_TYPES_H +#define EMUFAT_TYPES_H + +#include "types.h" + +#endif //EMUFAT_TYPES_H diff --git a/desmume/src/emufile.cpp b/desmume/src/emufile.cpp index 7068220a1..7a05ba999 100644 --- a/desmume/src/emufile.cpp +++ b/desmume/src/emufile.cpp @@ -1,4 +1,3 @@ -#include "types.h" #include "emufile.h" #include @@ -11,4 +10,18 @@ bool EMUFILE::readAllBytes(std::vector* dstbuf, const std::string& fname) dstbuf->resize(size); file.fread(&dstbuf->at(0),size); return true; +} + +EMUFILE* EMUFILE::memwrap(EMUFILE* fp) +{ + EMUFILE_FILE* file; + EMUFILE_MEMORY* mem; + file = dynamic_cast(fp); + mem = dynamic_cast(fp); + if(mem) return mem; + mem = new EMUFILE_MEMORY(file->size()); + if(file->size()==0) return mem; + file->fread(mem->buf(),file->size()); + delete file; + return mem; } \ No newline at end of file diff --git a/desmume/src/emufile.h b/desmume/src/emufile.h index 66a61e84e..97bca3616 100644 --- a/desmume/src/emufile.h +++ b/desmume/src/emufile.h @@ -1,4 +1,4 @@ - /* Copyright (C) 2009 DeSmuME team + /* Copyright (C) 2009-2010 DeSmuME team * * This file is part of DeSmuME * @@ -17,18 +17,25 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +//don't use emufile for files bigger than 2GB! you have been warned! some day this will be fixed. + #ifndef EMUFILE_H #define EMUFILE_H #include #include #include -#include "types.h" #include #include #include #include +#include "types.h" + +#ifdef _MSC_VER +#include +#endif + #ifdef _XBOX #undef min; #undef max; @@ -43,11 +50,16 @@ public: : failbit(false) {} + + //takes control of the provided EMUFILE and returns a new EMUFILE which is guranteed to be in memory + static EMUFILE* memwrap(EMUFILE* fp); + virtual ~EMUFILE() {} static bool readAllBytes(std::vector* buf, const std::string& fname); - bool fail() { return failbit; } + bool fail(bool unset=false) { bool ret = failbit; if(unset) unfail(); return ret; } + void unfail() { failbit=false; } bool eof() { return size()==ftell(); } @@ -78,6 +90,8 @@ public: virtual int ftell() = 0; virtual int size() = 0; + + virtual void truncate(s32 length) = 0; }; //todo - handle read-only specially? @@ -94,14 +108,29 @@ protected: public: - EMUFILE_MEMORY(std::vector *underlying) : vec(underlying), ownvec(false), pos(0), len(underlying->size()) { } - EMUFILE_MEMORY(u32 preallocate) : vec(new std::vector()), ownvec(true), pos(0), len(0) { vec->reserve(preallocate); } + EMUFILE_MEMORY(std::vector *underlying) : vec(underlying), ownvec(false), pos(0), len((s32)underlying->size()) { } + EMUFILE_MEMORY(u32 preallocate) : vec(new std::vector()), ownvec(true), pos(0), len(0) { + vec->resize(preallocate); + len = preallocate; + } EMUFILE_MEMORY() : vec(new std::vector()), ownvec(true), pos(0), len(0) { vec->reserve(1024); } + EMUFILE_MEMORY(void* buf, s32 size) : vec(new std::vector()), ownvec(true), pos(0), len(size) { + vec->resize(size); + if(size != 0) + memcpy(&vec[0],buf,size); + } ~EMUFILE_MEMORY() { if(ownvec) delete vec; } + virtual void truncate(s32 length) + { + vec->resize(length); + len = length; + if(pos>length) pos=length; + } + u8* buf() { return &(*vec)[0]; } std::vector* get_vec() { return vec; }; @@ -124,9 +153,19 @@ public: virtual int fgetc() { u8 temp; - if(_fread(&temp,1) != 1) - return EOF; - else return temp; + + //need an optimized codepath + //if(_fread(&temp,1) != 1) + // return EOF; + //else return temp; + u32 remain = len-pos; + if(remain<1) { + failbit = true; + return -1; + } + temp = buf()[pos]; + pos++; + return temp; } virtual int fputc(int c) { u8 temp = (u8)c; @@ -151,9 +190,9 @@ public: //they handle the return values correctly virtual void fwrite(const void *ptr, size_t bytes){ - reserve(pos+bytes); + reserve(pos+(s32)bytes); memcpy(buf()+pos,ptr,bytes); - pos += bytes; + pos += (s32)bytes; len = std::max(pos,len); } @@ -180,6 +219,11 @@ public: return pos; } + void trim() + { + vec->resize(len); + } + virtual int size() { return (int)len; } }; @@ -187,14 +231,18 @@ class EMUFILE_FILE : public EMUFILE { protected: FILE* fp; -public: - - EMUFILE_FILE(const char* fname, const char* mode) +private: + void open(const char* fname, const char* mode) { fp = fopen(fname,mode); if(!fp) failbit = true; - }; + } + +public: + + EMUFILE_FILE(const std::string& fname, const char* mode) { open(fname.c_str(),mode); } + EMUFILE_FILE(const char* fname, const char* mode) { open(fname,mode); } virtual ~EMUFILE_FILE() { if(NULL != fp) @@ -205,6 +253,17 @@ public: return fp; } + bool is_open() { return fp != NULL; } + + virtual void truncate(s32 length) + { + #ifdef _MSC_VER + _chsize(_fileno(fp),length); + #else + ftruncate(fileno(fp),length); + #endif + } + virtual int fprintf(const char *format, ...) { va_list argptr; va_start(argptr, format); diff --git a/desmume/src/fs.h b/desmume/src/fs.h index c48bb0da1..c105559db 100644 --- a/desmume/src/fs.h +++ b/desmume/src/fs.h @@ -1,20 +1,20 @@ /* Copyright (C) 2006 Guillaume Duhamel - This file is part of DeSmuME +This file is part of DeSmuME - DeSmuME is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. +DeSmuME is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. - DeSmuME is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. +DeSmuME is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with DeSmuME; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +You should have received a copy of the GNU General Public License +along with DeSmuME; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef FS_H @@ -33,7 +33,7 @@ typedef struct { char cFileName[256]; char cAlternateFileName[14]; u32 flags; - u32 fileSize; + u32 fileSize; } FsEntry; void * FsReadFirst(const char * path, FsEntry * entry); diff --git a/desmume/src/windows/DeSmuME_2005.vcproj b/desmume/src/windows/DeSmuME_2005.vcproj index 8fe81c105..575bdec7b 100644 --- a/desmume/src/windows/DeSmuME_2005.vcproj +++ b/desmume/src/windows/DeSmuME_2005.vcproj @@ -1842,6 +1842,18 @@ RelativePath="..\driver.h" > + + + + + + diff --git a/desmume/src/windows/DeSmuME_2008.vcproj b/desmume/src/windows/DeSmuME_2008.vcproj index 51fbb59ba..90eada807 100644 --- a/desmume/src/windows/DeSmuME_2008.vcproj +++ b/desmume/src/windows/DeSmuME_2008.vcproj @@ -228,6 +228,7 @@ /> + + + + + + diff --git a/desmume/src/windows/DeSmuME_2010.vcxproj b/desmume/src/windows/DeSmuME_2010.vcxproj index d3efd9b05..7b6ef0074 100644 --- a/desmume/src/windows/DeSmuME_2010.vcxproj +++ b/desmume/src/windows/DeSmuME_2010.vcxproj @@ -445,6 +445,7 @@ + @@ -559,6 +560,8 @@ + + diff --git a/desmume/src/windows/DeSmuME_2010.vcxproj.filters b/desmume/src/windows/DeSmuME_2010.vcxproj.filters index 3aa8aa5b1..d4816aa7c 100644 --- a/desmume/src/windows/DeSmuME_2010.vcxproj.filters +++ b/desmume/src/windows/DeSmuME_2010.vcxproj.filters @@ -396,6 +396,9 @@ Core\addons + + Core + @@ -728,6 +731,10 @@ + + Core + +