/* Copyright 2005, 2006, 2007 Dennis van Weeren Copyright 2008, 2009 Jakub Bednarski This file is part of Minimig Minimig 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. Minimig 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 this program. If not, see . This is a simple FAT16 handler. It works on a sector basis to allow fastest acces on disk images. 11-12-2005 - first version, ported from FAT1618.C JB: 2008-10-11 - added SeekFile() and cluster_mask - limited file create and write support added 2009-05-01 - modified LoadDirectory() and GetDirEntry() to support sub-directories (with limitation of 511 files/subdirs per directory) - added GetFATLink() function - code cleanup 2009-05-03 - modified sorting algorithm in LoadDirectory() to display sub-directories above files 2009-08-23 - modified ScanDirectory() to support page scrolling and parent dir selection 2009-11-22 - modified FileSeek() - added FileReadEx() 2009-12-15 - all entries are now sorted by name with extension - directory short names are displayed with extensions */ #include #include #include #include "mmc.h" #include "fat.h" #include "swap.h" #include "usb.h" #include "fpga.h" // typedef unsigned char (*rw_func_t)(unsigned long, unsigned char *); typedef unsigned char (*rw_multiple_func_t)(unsigned long, unsigned char *, unsigned long); unsigned short directory_cluster; // first cluster of directory (0 if root) unsigned short entries_per_cluster; // number of directory entries per cluster // internal global variables unsigned char fattype; // volume format unsigned char fat32; // volume format is FAT32 uint32_t boot_sector; // partition boot sector uint32_t fat_start; // start LBA of first FAT table uint32_t data_start; // start LBA of data field uint32_t root_directory_cluster; // root directory cluster (used in FAT32) uint32_t root_directory_start; // start LBA of directory table uint32_t root_directory_size; // size of directory region in sectors unsigned char fat_number; // number of FAT tables unsigned char cluster_size; // size of a cluster in sectors uint32_t cluster_mask; // binary mask of cluster number unsigned short dir_entries; // number of entry's in directory table uint32_t fat_size; // size of fat unsigned short info_sector; // fat32 info sector struct PartitionEntry partitions[4]; // lbastart and sectors will be byteswapped as necessary int partitioncount; unsigned char sector_buffer[512*SECTOR_BUFFER_SIZE]; // sector buffer FATBUFFER fat_buffer; // buffer for caching fat entries uint32_t buffered_fat_index; // index of buffered FAT sector char DirEntryLFN[MAXDIRENTRIES][261]; DIRENTRY DirEntry[MAXDIRENTRIES]; unsigned char sort_table[MAXDIRENTRIES]; unsigned char nDirEntries = 0; // entries in DirEntry table unsigned char iSelectedEntry = 0; // selected entry index uint32_t iCurrentDirectory = 0; // cluster number of current directory, 0 for root uint32_t iPreviousDirectory = 0; // cluster number of previous directory // temporary storage buffers char t_DirEntryLFN[MAXDIRENTRIES][261]; DIRENTRY t_DirEntry[MAXDIRENTRIES]; unsigned char t_sort_table[MAXDIRENTRIES]; // external functions extern unsigned long GetTimer(unsigned long); extern void ErrorMessage(const char *message, unsigned char code); // current read/write functions rw_func_t lread = MMC_Read; rw_multiple_func_t lread_multiple = MMC_ReadMultiple; rw_func_t lwrite = MMC_Write; int8_t fat_uses_mmc(void) { return(lread == MMC_Read); } int8_t fat_medium_present() { if(lread == MMC_Read) return MMC_CheckCard(); #ifdef USB_STORAGE return(storage_devices > 0); #else return(0); #endif } #ifdef USB_STORAGE void fat_switch_to_usb(void) { lread = usb_storage_read; // TODO: lread_multiple lwrite = usb_storage_write; } #endif // return fat sector index of a given cluster entry #define CLUSTER2SECTOR(c) ((c) >> (fat32?7:8)) // return byte offset withing sector of a given cluster entry #define CLUSTER2OFFSET(c) ((c) & (fat32?0x7f:0xff)) #pragma section_code_init // read sector of FAT if not already in the buffer RAMFUNC static char FatLoad(uint32_t index) { // load fat sector if(buffered_fat_index != index) { if (!lread(fat_start + index, (unsigned char*)&fat_buffer)) { iprintf("FAT read failed!\n"); return(0); } // remember current buffer index buffered_fat_index = index; } return 1; } RAMFUNC uint32_t NextCluster(uint32_t cluster) { uint32_t fat_index = CLUSTER2SECTOR(cluster); unsigned short buffer_index = CLUSTER2OFFSET(cluster); if(!FatLoad(fat_index)) return(0); if (fat32) return SwapBBBB(fat_buffer.fat32[buffer_index]) & 0x0FFFFFFF; // get FAT32 link return SwapBB(fat_buffer.fat16[buffer_index]); // get FAT16 link } #pragma section_no_code_init // FindDrive() checks if a card is present and contains FAT formatted primary partition unsigned char FindDrive(void) { buffered_fat_index = -1; fat32 = 0; if (!lread(0, sector_buffer)) // read MBR return(0); boot_sector=0; partitioncount=1; // If we can identify a filesystem on block 0 we don't look for partitions if (strncmp((const char*)§or_buffer[0x36], "FAT16 ", 8)==0) // check for FAT16 partitioncount=0; if (strncmp((const char*)§or_buffer[0x52], "FAT32 ", 8)==0) // check for FAT32 partitioncount=0; iprintf("partitioncount = %d\n", partitioncount); if(partitioncount) { // We have at least one partition, parse the MBR. struct MasterBootRecord *mbr=(struct MasterBootRecord *)sector_buffer; memcpy(&partitions[0],&mbr->Partition[0],sizeof(struct PartitionEntry)); memcpy(&partitions[1],&mbr->Partition[1],sizeof(struct PartitionEntry)); memcpy(&partitions[2],&mbr->Partition[2],sizeof(struct PartitionEntry)); memcpy(&partitions[3],&mbr->Partition[3],sizeof(struct PartitionEntry)); if(mbr->Signature == 0xaa55) { // get start of first partition boot_sector = partitions[0].startlba; iprintf("Start: %ld\n",partitions[0].startlba); for(partitioncount=4;(partitions[partitioncount-1].sectors==0) && (partitioncount>1); --partitioncount); iprintf("PartitionCount: %d\n",partitioncount); int i; for(i=0;i> 9; // calculate start of FAT,size of FAT and number of FAT's fat_start = boot_sector + sector_buffer[14] + (sector_buffer[15] << 8); fat_size = sector_buffer[22] + (sector_buffer[23] << 8); fat_number = sector_buffer[16]; // calculate start of directory root_directory_start = fat_start + (fat_number * fat_size); root_directory_cluster = 0; // unused // get cluster_size cluster_size = sector_buffer[13]; // calculate cluster mask cluster_mask = ~(cluster_size - 1); // calculate start of data data_start = root_directory_start + root_directory_size; // fat16 does not have a info sector info_sector = 0; } // some debug output iprintf("fat_size: %lu\n", fat_size); iprintf("fat_number: %u\n", fat_number); iprintf("fat_start: %lu\n", fat_start); iprintf("root_directory_start: %lu\n", root_directory_start); iprintf("dir_entries: %u\n", dir_entries); iprintf("data_start: %lu\n", data_start); iprintf("cluster_size: %u\n", cluster_size); iprintf("cluster_mask: %08lX\n", cluster_mask); return(1); } unsigned char FileOpenDir(fileTYPE *file, const char *name, unsigned long iDirectory) { DIRENTRY *pEntry = NULL; // pointer to current entry in sector buffer unsigned long iDirectorySector; // current sector of directory entries table unsigned long iDirectoryCluster; // start cluster of subdirectory or FAT32 root directory unsigned long iEntry; // entry index in directory cluster or FAT16 root directory unsigned long nEntries; // number of entries per cluster or FAT16 root directory size if (iDirectory) { // subdirectory iDirectoryCluster = iDirectory; iDirectorySector = data_start + cluster_size * (iDirectoryCluster - 2); nEntries = cluster_size << 4; // 16 entries per sector } else { // root directory iDirectoryCluster = root_directory_cluster; iDirectorySector = root_directory_start; nEntries = fat32 ? cluster_size << 4 : root_directory_size << 4; // 16 entries per sector } while (1) { for (iEntry = 0; iEntry < nEntries; iEntry++) { if ((iEntry & 0x0F) == 0) { // first entry in sector, load the sector lread(iDirectorySector++, sector_buffer); // root directory is linear pEntry = (DIRENTRY*)sector_buffer; } else pEntry++; if (pEntry->Name[0] != SLOT_EMPTY && pEntry->Name[0] != SLOT_DELETED) // valid entry?? { if (!(pEntry->Attributes & (ATTR_VOLUME | ATTR_DIRECTORY))) // not a volume nor directory { // iprintf("check %.11s %.11s\n", pEntry->Name, name); if (strncmp((const char*)pEntry->Name, name, sizeof(file->name)) == 0) { strncpy(file->name, (const char*)pEntry->Name, sizeof(file->name)); file->attributes = pEntry->Attributes; // file->size = pEntry->FileSize; file->size = SwapBBBB(pEntry->FileSize); // for 68000 // file->size = ((file->size>>24)&0xFF)|((file->size>>8)&0xFF00)|((file->size<<8)&0xFF0000)|((file->size<<24)&0xFF000000); // for 68000 // file->start_cluster = pEntry->StartCluster + (fat32 ? (pEntry->HighCluster & 0x0FFF) << 16 : 0); // it only works when using little endian long representation file->start_cluster = SwapBB(pEntry->StartCluster) + (fat32 ? (SwapBB(pEntry->HighCluster) & 0x0FFF) << 16 : 0); // it only works when using little endian long representation for 68000 // file->start_cluster = (((pEntry->StartCluster>>8)&0xFF)|((pEntry->StartCluster<<8)&0xFF00)) | (fat32 ? ((pEntry->HighCluster & 0x0F) << 24)|((pEntry->HighCluster & 0xFF00) << 8) : 0); // for 68000 file->cluster = file->start_cluster; file->sector = 0; file->cluster_change = 0; file->entry.sector = iDirectorySector - 1; file->entry.index = iEntry & 0x0F; // iprintf("file \"%s\" found\n", name); return(1); } } } } if (iDirectory || fat32) // subdirectory is a linked cluster chain { iDirectoryCluster = NextCluster(iDirectoryCluster); // get next cluster in chain if (fat32 ? (iDirectoryCluster & 0x0FFFFFF8) == 0x0FFFFFF8 : (iDirectoryCluster & 0xFFF8) == 0xFFF8) // check if end of cluster chain break; // no more clusters in chain iDirectorySector = data_start + cluster_size * (iDirectoryCluster - 2); // calculate first sector address of the new cluster } else break; } iprintf("file \"%s\" not found\n", name); memset(file, 0, sizeof(fileTYPE)); return(0); } unsigned char FileOpen(fileTYPE *file, const char *name) { return FileOpenDir(file, name, 0); } unsigned char lfn_checksum(unsigned char *pName) { unsigned char i = 11; unsigned char checksum = 0; while (i--) checksum = ((checksum & 1) << 7) + (checksum >> 1) + *pName++; return checksum; } int _strnicmp(const char *s1, const char *s2, size_t n) { char c1, c2; int v; do { c1 = *s1++; c2 = *s2++; v = (unsigned int)tolower(c1) - (unsigned int)tolower(c2); } while (v == 0 && c1 != '\0' && --n > 0); return v; } int CompareDirEntries(DIRENTRY *pDirEntry1, char *pLFN1, DIRENTRY *pDirEntry2, char *pLFN2) { const char *pStr1, *pStr2; int len; int rc; if ((pDirEntry2->Attributes & ATTR_DIRECTORY) && !(pDirEntry1->Attributes & ATTR_DIRECTORY) // directories first || (pDirEntry2->Name[0] == '.' && pDirEntry2->Name[1] == '.')) // parent directory entry at top return 1; if ((pDirEntry1->Attributes & ATTR_DIRECTORY) && !(pDirEntry2->Attributes & ATTR_DIRECTORY) // directories first || (pDirEntry1->Name[0] == '.' && pDirEntry1->Name[1] == '.')) // parent directory entry at top return -1; len = 260; if (*pLFN1) pStr1 = pLFN1; else { pStr1 = (const char*)pDirEntry1->Name; len = 11; } if (*pLFN2) pStr2 = pLFN2; else { pStr2 = (const char*)pDirEntry2->Name; len = 11; } rc = _strnicmp(pStr1, pStr2, len); if (rc == 0) // it might happen that both strings are equal when one is a long name and other not { if (*pLFN1) { if (!*pLFN2) // first string long, second short rc = 1; } else { if (*pLFN2) // first string short, second long rc = -1; } } return(rc); } int compareExt(char *fileExt, char *extension) { int found = 0; while(!found && *extension) { found = 1; for (int i = 0; i < 3; i++) { if (extension[i] == '?') continue; if (tolower(extension[i]) != tolower(fileExt[i])) found = 0; } if (strlen(extension) < 3) break; extension += 3; } return found; } char ScanDirectory(unsigned long mode, char *extension, unsigned char options) { DIRENTRY *pEntry = NULL; // pointer to current entry in sector buffer unsigned long iDirectorySector; // current sector of directory entries table unsigned long iDirectoryCluster; unsigned long iEntry; // entry index in directory unsigned long nEntries; char i; unsigned char x; unsigned char nNewEntries = 0; // indicates if a new entry has been found (used in scroll mode) char rc = 0; //return code char find_file = 0; char find_dir = 0; char is_file = 0; unsigned char sequence_number = 0; unsigned char name_checksum = 0; unsigned char prev_sequence_number = 0; unsigned char prev_name_checksum = 0; char *ptr; static char lfn[261]; unsigned char lfn_error = 1; /* unsigned long time; time = GetTimer(0); */ lfn[0] = 0; if (mode == SCAN_INIT) { nDirEntries = 0; iSelectedEntry = 0; for (i = 0; i < MAXDIRENTRIES; i++) sort_table[i] = i; } else { if (nDirEntries == 0) // directory is empty so there is no point in searching for any entry return 0; if (mode == SCAN_NEXT) { if (iSelectedEntry + 1 < nDirEntries) // scroll within visible items { iSelectedEntry++; return 0; } if (nDirEntries < MAXDIRENTRIES) return 0; } else if (mode == SCAN_PREV) { if (iSelectedEntry > 0) // scroll within visible items { iSelectedEntry--; return 0; } } else if (mode ==SCAN_NEXT_PAGE) { if (iSelectedEntry + 1 < nDirEntries) { iSelectedEntry = nDirEntries - 1; return 0; } if (nDirEntries < MAXDIRENTRIES) return 0; } else if (mode == SCAN_PREV_PAGE) { if (iSelectedEntry) { iSelectedEntry = 0; return 0; } } find_file = options & FIND_FILE; find_dir = options & FIND_DIR; } if (iCurrentDirectory) // subdirectory { iDirectoryCluster = iCurrentDirectory; iDirectorySector = data_start + cluster_size * (iDirectoryCluster - 2); nEntries = cluster_size << 4; // 16 entries per sector } else // root directory { iDirectoryCluster = root_directory_cluster; iDirectorySector = root_directory_start; nEntries = fat32 ? cluster_size << 4 : root_directory_size << 4; // 16 entries per sector } while (1) { for (iEntry = 0; iEntry < nEntries; iEntry++) { if ((iEntry & (SECTOR_BUFFER_SIZE * 16 - 1)) == 0) // first entry in sector buffer, load the sector { lread_multiple(iDirectorySector, sector_buffer, SECTOR_BUFFER_SIZE); iDirectorySector += SECTOR_BUFFER_SIZE; pEntry = (DIRENTRY*)sector_buffer; for (i = 0; i < (SECTOR_BUFFER_SIZE * 16); i++) { if (pEntry->Attributes != ATTR_LFN) { pEntry->StartCluster = SwapBB(pEntry->StartCluster); pEntry->HighCluster = SwapBB(pEntry->HighCluster); pEntry->FileSize = SwapBBBB(pEntry->FileSize); } pEntry++; } pEntry = (DIRENTRY*)sector_buffer; } else pEntry++; if (pEntry->Name[0] != SLOT_EMPTY && pEntry->Name[0] != SLOT_DELETED) // valid entry?? { if (pEntry->Attributes == ATTR_LFN) // long file name entry { if (options & SCAN_LFN) { sequence_number = ((unsigned char*)pEntry)[0]; name_checksum = ((unsigned char*)pEntry)[13]; ptr = &lfn[((sequence_number & 0x1F) - 1) * 13]; if (sequence_number & 0x40) lfn_error = 0; else if ((sequence_number & 0x1F) != (prev_sequence_number & 0x1F) - 1 || name_checksum != prev_name_checksum || (sequence_number & 0x1F) > sizeof(lfn) / 13 - 1) lfn_error = 1; prev_sequence_number = sequence_number; prev_name_checksum = name_checksum; if (!lfn_error) { *ptr++ = ((unsigned char*)pEntry)[1]; *ptr++ = ((unsigned char*)pEntry)[3]; *ptr++ = ((unsigned char*)pEntry)[5]; *ptr++ = ((unsigned char*)pEntry)[7]; *ptr++ = ((unsigned char*)pEntry)[9]; // first 5 characters *ptr++ = ((unsigned char*)pEntry)[14]; *ptr++ = ((unsigned char*)pEntry)[16]; *ptr++ = ((unsigned char*)pEntry)[18]; *ptr++ = ((unsigned char*)pEntry)[20]; *ptr++ = ((unsigned char*)pEntry)[22]; *ptr++ = ((unsigned char*)pEntry)[24]; // next 6 characters *ptr++ = ((unsigned char*)pEntry)[28]; *ptr++ = ((unsigned char*)pEntry)[30]; // last 2 characters if (sequence_number & 0x40) // last lfn part *ptr++ = 0; } else iprintf("LFN error!\n"); } } else // if not an LFN entry { is_file = ~pEntry->Attributes & ATTR_DIRECTORY; if (!(pEntry->Attributes & (ATTR_VOLUME | ATTR_HIDDEN)) && (pEntry->Name[0] != '.' || pEntry->Name[1] != ' ')) // if not VOLUME label (also filter current directory entry) { if ((extension[0] == '*') || compareExt(&pEntry->Name[8], extension) || (options & SCAN_DIR && pEntry->Attributes & ATTR_DIRECTORY) || (options & SCAN_SYSDIR && pEntry->Attributes & ATTR_DIRECTORY && ((pEntry->Name[0] == '.' && pEntry->Name[1] == '.') || pEntry->Attributes & ATTR_SYSTEM))) { if (mode == SCAN_INIT) { // scan the directory table and return first MAXDIRENTRIES alphabetically sorted entries if (nDirEntries < MAXDIRENTRIES) // initial directory scan (first 8 entries) { DirEntry[nDirEntries] = *pEntry; // add new entry at first empty slot in storage buffer DirEntryLFN[nDirEntries][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(DirEntryLFN[nDirEntries], lfn, sizeof(lfn)); nDirEntries++; } else { if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[MAXDIRENTRIES-1]], DirEntryLFN[sort_table[MAXDIRENTRIES-1]]) < 0) // compare new entry with the last already found { DirEntry[sort_table[MAXDIRENTRIES-1]] = *pEntry; // replace the last entry with the new one if appropriate DirEntryLFN[sort_table[MAXDIRENTRIES-1]][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(DirEntryLFN[sort_table[MAXDIRENTRIES-1]], lfn, sizeof(lfn)); } } for (i = nDirEntries - 1; i > 0; i--) // one pass bubble-sorting (table is already sorted, only the new item must be placed in order) { if (CompareDirEntries(&DirEntry[sort_table[i]], DirEntryLFN[sort_table[i]], &DirEntry[sort_table[i-1]], DirEntryLFN[sort_table[i-1]])<0) // compare items and swap if necessary { x = sort_table[i]; sort_table[i] = sort_table[i-1]; sort_table[i-1] = x; } else break; // don't check further entries as they are already sorted } } else if (mode == SCAN_INIT_FIRST) { // find a dir entry with given cluster number and store it in the buffer if (pEntry->StartCluster + (fat32 ? (pEntry->HighCluster & 0x0FFF) << 16 : 0) == iPreviousDirectory) { // directory entry found for (i = 0; i< MAXDIRENTRIES; i++) sort_table[i] = i; // init sorting table nDirEntries = 1; iSelectedEntry = 0; DirEntry[0] = *pEntry; // add the entry at the top of the buffer DirEntryLFN[0][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(DirEntryLFN[0], lfn, sizeof(lfn)); rc = 1; // indicate to the caller that the directory entry has been found } } else if (mode == SCAN_INIT_NEXT) { // scan the directory table and return next MAXDIRENTRIES-1 alphabetically sorted entries (first entry is in the buffer) if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[0]], DirEntryLFN[sort_table[0]]) > 0) // compare new entry with the first one { if (nDirEntries < MAXDIRENTRIES) // initial directory scan (first 8 entries) { DirEntry[nDirEntries] = *pEntry; // add new entry at first empty slot in storage buffer DirEntryLFN[nDirEntries][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(DirEntryLFN[nDirEntries], lfn, sizeof(lfn)); nDirEntries++; } else { if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[MAXDIRENTRIES-1]], DirEntryLFN[sort_table[MAXDIRENTRIES-1]]) < 0) // compare new entry with the last already found { DirEntry[sort_table[MAXDIRENTRIES-1]] = *pEntry; // replace the last entry with the new one if appropriate DirEntryLFN[sort_table[MAXDIRENTRIES-1]][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(DirEntryLFN[sort_table[MAXDIRENTRIES-1]], lfn, sizeof(lfn)); } } for (i = nDirEntries - 1; i > 0; i--) // one pass bubble-sorting (table is already sorted, only the new item must be placed in order) { if (CompareDirEntries(&DirEntry[sort_table[i]], DirEntryLFN[sort_table[i]], &DirEntry[sort_table[i-1]], DirEntryLFN[sort_table[i-1]])<0) // compare items and swap if necessary { x = sort_table[i]; sort_table[i] = sort_table[i-1]; sort_table[i-1] = x; } else break; // don't check further entries as they are already sorted } } } else if (mode == SCAN_NEXT) // replace the last dir entry with the new (higher) one { if (nNewEntries == 0) // no entry higher than the last one has been found yet { if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[MAXDIRENTRIES-1]], DirEntryLFN[sort_table[MAXDIRENTRIES-1]]) > 0) // found entry higher than the last one { nNewEntries++; DirEntry[sort_table[0]] = *pEntry; // replace the first entry with the found one DirEntryLFN[sort_table[0]][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(DirEntryLFN[sort_table[0]], lfn, sizeof(lfn)); // scroll entries' indices x = sort_table[0]; for (i = 0; i < MAXDIRENTRIES-1; i++) sort_table[i] = sort_table[i+1]; sort_table[MAXDIRENTRIES-1] = x; // last entry is the found one } } else // higher entry already found but we need to check the remaining ones if any of them is lower then the already found one { // check if the found entry is lower than the last one and higher than the last but one, if so then replace the last one with it if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[MAXDIRENTRIES-1]], DirEntryLFN[sort_table[MAXDIRENTRIES-1]]) < 0) if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[MAXDIRENTRIES-2]], DirEntryLFN[sort_table[MAXDIRENTRIES-2]]) > 0) { DirEntry[sort_table[MAXDIRENTRIES-1]] = *pEntry; DirEntryLFN[sort_table[MAXDIRENTRIES-1]][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(DirEntryLFN[sort_table[MAXDIRENTRIES-1]], lfn, sizeof(lfn)); } } } else if (mode == SCAN_PREV) // replace the first dir entry with the new (lower) one { if (nNewEntries == 0) // no entry lower than the first one has been found yet { if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[0]], DirEntryLFN[sort_table[0]]) < 0) // found entry lower than the first one { nNewEntries++; if (nDirEntries < MAXDIRENTRIES) { //sort_table[nDirEntries] = nDirEntries; // init sorting table nDirEntries++; } DirEntry[sort_table[MAXDIRENTRIES-1]] = *pEntry; // replace the last entry with the found one DirEntryLFN[sort_table[MAXDIRENTRIES-1]][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(DirEntryLFN[sort_table[MAXDIRENTRIES-1]], lfn, sizeof(lfn)); // scroll entries' indices x = sort_table[MAXDIRENTRIES-1]; for (i = MAXDIRENTRIES - 1; i > 0; i--) sort_table[i] = sort_table[i-1]; sort_table[0] = x; // the first entry is the found one } } else // lower entry already found but we need to check the remaining ones if any of them is higher then the already found one { // check if the found entry is higher than the first one and lower than the second one, if so then replace the first one with it if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[0]], DirEntryLFN[sort_table[0]]) > 0) if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[1]], DirEntryLFN[sort_table[1]]) < 0) { DirEntry[sort_table[0]] = *pEntry; DirEntryLFN[sort_table[0]][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(DirEntryLFN[sort_table[0]], lfn, sizeof(lfn)); } } } else if (mode == SCAN_NEXT_PAGE) // find next 8 entries { if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[MAXDIRENTRIES-1]], DirEntryLFN[sort_table[MAXDIRENTRIES-1]]) > 0) // compare with the last visible entry { if (nNewEntries < MAXDIRENTRIES) // initial directory scan (first 8 entries) { t_DirEntry[nNewEntries] = *pEntry; // add new entry at first empty slot in storage buffer t_DirEntryLFN[nNewEntries][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(t_DirEntryLFN[nNewEntries], lfn, sizeof(lfn)); t_sort_table[nNewEntries] = nNewEntries; // init sorting table nNewEntries++; } else { if (CompareDirEntries(pEntry, lfn, &t_DirEntry[t_sort_table[MAXDIRENTRIES-1]], t_DirEntryLFN[t_sort_table[MAXDIRENTRIES-1]]) < 0) // compare new entry with the last already found { t_DirEntry[t_sort_table[MAXDIRENTRIES-1]] = *pEntry; // replace the last entry with the new one if appropriate t_DirEntryLFN[t_sort_table[MAXDIRENTRIES-1]][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(t_DirEntryLFN[t_sort_table[MAXDIRENTRIES-1]], lfn, sizeof(lfn)); } } for (i = nNewEntries - 1; i > 0; i--) // one pass bubble-sorting (table is already sorted, only the new item must be placed in order) { if (CompareDirEntries(&t_DirEntry[t_sort_table[i]], t_DirEntryLFN[t_sort_table[i]], &t_DirEntry[t_sort_table[i-1]], t_DirEntryLFN[t_sort_table[i-1]]) < 0) // compare items and swap if necessary { x = t_sort_table[i]; t_sort_table[i] = t_sort_table[i-1]; t_sort_table[i-1] = x; } else break; // don't check further entries as they are already sorted } } } else if (mode == SCAN_PREV_PAGE) // find next 8 entries { if (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[0]], DirEntryLFN[sort_table[0]]) < 0) // compare with the last visible entry { if (nNewEntries < MAXDIRENTRIES) // initial directory scan (first 8 entries) { t_DirEntry[nNewEntries] = *pEntry; // add new entry at first empty slot in storage buffer t_DirEntryLFN[nNewEntries][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(t_DirEntryLFN[nNewEntries], lfn, sizeof(lfn)); t_sort_table[nNewEntries] = nNewEntries; // init sorting table nNewEntries++; } else { if (CompareDirEntries(pEntry, lfn, &t_DirEntry[t_sort_table[MAXDIRENTRIES-1]], t_DirEntryLFN[t_sort_table[MAXDIRENTRIES-1]]) > 0) // compare new entry with the last already found { t_DirEntry[t_sort_table[MAXDIRENTRIES-1]] = *pEntry; // replace the last entry with the new one if appropriate t_DirEntryLFN[t_sort_table[MAXDIRENTRIES-1]][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(t_DirEntryLFN[t_sort_table[MAXDIRENTRIES-1]], lfn, sizeof(lfn)); } } for (i = nNewEntries - 1; i > 0; i--) // one pass bubble-sorting (table is already sorted, only the new item must be placed in order) { if (CompareDirEntries(&t_DirEntry[t_sort_table[i]], t_DirEntryLFN[t_sort_table[i]], &t_DirEntry[t_sort_table[i-1]], t_DirEntryLFN[t_sort_table[i-1]]) > 0) // compare items and swap if necessary { x = t_sort_table[i]; t_sort_table[i] = t_sort_table[i-1]; t_sort_table[i-1] = x; } else break; // don't check further entries as they are already sorted } } } else if ((mode >= '0' && mode <= '9') || (mode >= 'A' && mode <= 'Z')) // find first entry beginning with given character { if (find_file) x = tolower(pEntry->Name[0]) >= tolower(mode) && is_file; else if (find_dir) x = tolower(pEntry->Name[0]) >= tolower(mode) || is_file; else x = (CompareDirEntries(pEntry, lfn, &DirEntry[sort_table[iSelectedEntry]], DirEntryLFN[sort_table[iSelectedEntry]]) > 0); // compare with the last visible entry if (x) { if (nNewEntries < MAXDIRENTRIES) // initial directory scan (first 8 entries) { t_DirEntry[nNewEntries] = *pEntry; // add new entry at first empty slot in storage buffer t_DirEntryLFN[nNewEntries][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(t_DirEntryLFN[nNewEntries], lfn, sizeof(lfn)); t_sort_table[nNewEntries] = nNewEntries; // init sorting table nNewEntries++; } else { if (CompareDirEntries(pEntry, lfn, &t_DirEntry[t_sort_table[MAXDIRENTRIES-1]], t_DirEntryLFN[t_sort_table[MAXDIRENTRIES-1]]) < 0) // compare new entry with the last already found { t_DirEntry[t_sort_table[MAXDIRENTRIES-1]] = *pEntry; // replace the last entry with the new one if appropriate t_DirEntryLFN[t_sort_table[MAXDIRENTRIES-1]][0] = 0; if (lfn[0]) if (lfn_checksum(pEntry->Name) == name_checksum) strncpy(t_DirEntryLFN[t_sort_table[MAXDIRENTRIES-1]], lfn, sizeof(lfn)); } } for (i = nNewEntries - 1; i > 0; i--) // one pass bubble-sorting (table is already sorted, only the new item must be placed in order) { if (CompareDirEntries(&t_DirEntry[t_sort_table[i]], t_DirEntryLFN[t_sort_table[i]], &t_DirEntry[t_sort_table[i-1]], t_DirEntryLFN[t_sort_table[i-1]]) < 0) // compare items and swap if necessary { x = t_sort_table[i]; t_sort_table[i] = t_sort_table[i-1]; t_sort_table[i-1] = x; } else break; // don't check further entries as they are already sorted } } } } } lfn[0] = 0; } } } if (iCurrentDirectory || fat32) // subdirectory is a linked cluster chain { iDirectoryCluster = NextCluster(iDirectoryCluster); // get next cluster in chain if (fat32 ? (iDirectoryCluster & 0x0FFFFFF8) == 0x0FFFFFF8 : (iDirectoryCluster & 0xFFF8) == 0xFFF8) // check if end of chain break; // no more clusters in chain iDirectorySector = data_start + cluster_size * (iDirectoryCluster - 2); // calculate first sector address of the new cluster } else break; } if (nNewEntries) { if (mode == SCAN_NEXT_PAGE) { unsigned char j = 8 - nNewEntries; // number of remaining old entries to scroll for (i = 0; i < j; i++) { x = sort_table[i]; sort_table[i] = sort_table[i + nNewEntries]; sort_table[i + nNewEntries] = x; } // copy temporary buffer to display for (i = 0; i < nNewEntries; i++) { DirEntry[sort_table[i + j]] = t_DirEntry[t_sort_table[i]]; strcpy(DirEntryLFN[sort_table[i + j]], t_DirEntryLFN[t_sort_table[i]]); } } else if (mode == SCAN_PREV_PAGE) { // note: temporary buffer entries are in reverse order unsigned char j = nNewEntries - 1; for (i = 7; i > j; i--) { x = sort_table[i]; sort_table[i] = sort_table[i - nNewEntries]; sort_table[i - nNewEntries] = x; } // copy temporary buffer to display for (i = 0; i < nNewEntries; i++) { DirEntry[sort_table[j - i]] = t_DirEntry[t_sort_table[i]]; strcpy(DirEntryLFN[sort_table[j - i]], t_DirEntryLFN[t_sort_table[i]]); } nDirEntries += nNewEntries; if (nDirEntries > MAXDIRENTRIES) nDirEntries = MAXDIRENTRIES; } else if ((mode >= '0' && mode <= '9') || (mode >= 'A' && mode <= 'Z')) { if (t_DirEntry[t_sort_table[0]].Name[0] == mode) { x = 1; // if we were looking for a file we couldn't find anything other if (find_dir) { // when looking for a directory we could find a file beginning with the same character as given one x = t_DirEntry[t_sort_table[0]].Attributes & ATTR_DIRECTORY; } else if (!find_file) // find_next { // when looking for a directory we could find a file beginning with the same character as given one x = (t_DirEntry[t_sort_table[0]].Attributes & ATTR_DIRECTORY) == (DirEntry[sort_table[iSelectedEntry]].Attributes & ATTR_DIRECTORY); } if (x) { // first entry is what we were searching for for (i = 0; i < nNewEntries; i++) { DirEntry[sort_table[i]] = t_DirEntry[t_sort_table[i]]; strcpy(DirEntryLFN[sort_table[i]], t_DirEntryLFN[t_sort_table[i]]); } nDirEntries = nNewEntries; iSelectedEntry = 0; rc = 1; // inform the caller that the search succeeded } } } } /* time = GetTimer(0) - time; iprintf("ScanDirectory(): %lu ms\n", time >> 20); */ return rc; } void ChangeDirectory(unsigned long iStartCluster) { iPreviousDirectory = iCurrentDirectory; iCurrentDirectory = iStartCluster; } #pragma section_code_init RAMFUNC unsigned char FileNextSector(fileTYPE *file) { unsigned long sb; unsigned short i; // increment sector index file->sector++; // cluster's boundary crossed? if ((file->sector&~cluster_mask) == 0) { sb = CLUSTER2SECTOR(file->cluster); i = CLUSTER2OFFSET(file->cluster); if(!FatLoad(sb)) return 0; file->cluster = fat32 ? SwapBBBB(fat_buffer.fat32[i]) & 0x0FFFFFFF : SwapBB(fat_buffer.fat16[i]); } return(1); } #pragma section_no_code_init static int FatSave(unsigned long index) { // store FAT sector if (!lwrite(fat_start + index, (unsigned char*)&fat_buffer)) { iprintf("FAT write failed!\n"); return(0); } // update FAT copies char i; for (i = 1; i < fat_number; i++) { if (!lwrite(fat_start + (i * fat_size) + index, (unsigned char*)&fat_buffer)) { iprintf("FAT copy #%d write failed!\n", i); return(0); } } return 1; } unsigned long ClusterAllocate() { // free cluster is marked as 0x0000 // last cluster in chain is 0xFFFF unsigned long fat_index = 0; // first sector of FAT unsigned long buffer_index = 2; // two first entries are reserved while (fat_index < fat_size) { if(!FatLoad(fat_index)) return(0); // one sector can hold 128 fat32 entries or 256 fat 16 entries unsigned long buffer_size = fat32 ? 128 : 256; while (buffer_index < buffer_size) { // search through all entries in current sector if ((fat32 ? fat_buffer.fat32[buffer_index] : fat_buffer.fat16[buffer_index]) == 0) { // empty cluster found unsigned long cluster = (fat_index << (fat32 ? 7 : 8)) + buffer_index; // calculate cluster number // iprintf("Empty cluster: %lu\n", cluster); // mark cluster as used if (fat32) fat_buffer.fat32[buffer_index] = SwapBBBB(0x0FFFFFFF); // FAT32 EOC else fat_buffer.fat16[buffer_index] = SwapBB(0xFFFF); // FAT16 EOC if(!FatSave(fat_index)) return(0); // we have a free cluster in "cluster" return cluster; } buffer_index++; } buffer_index = 0; // go to the start of sector fat_index++; // go to the next sector of FAT } return 0; // this will never be reached } #define EOC (fat32?0x0FFFFFFF:0xFFFF) // Get next sector. Expand cluster chain in required unsigned char FileNextSectorExpand(fileTYPE *file) { unsigned long cur_cluster = file->cluster; unsigned char retval = FileNextSector(file); if(retval) { // check if cluster is end of chain if(file->cluster == EOC) { // allocate a new cluster ... file->cluster = ClusterAllocate(); if(file->cluster) { file->cluster_change++; // ... and append it to chain // save new cluster in previous cluster entry unsigned long fat_index = CLUSTER2SECTOR(cur_cluster); unsigned short buffer_index = CLUSTER2OFFSET(cur_cluster); if(!FatLoad(fat_index)) return(0); if (fat32) fat_buffer.fat32[buffer_index] = file->cluster; else fat_buffer.fat16[buffer_index] = file->cluster; if(!FatSave(fat_index)) return(0); } } } return retval; } unsigned char FileSeek(fileTYPE *file, unsigned long offset, unsigned long origin) { // offset in sectors (512 bytes) // origin can be set to SEEK_SET or SEEK_CUR if (origin == SEEK_CUR) offset += file->sector; if (file->sector > offset) // current filepointer is beyond requested position { // so move it backwards if ((file->sector^offset) & cluster_mask) // moving backwards within current cluster? { // different clusters, so go to the start of file file->cluster = file->start_cluster; file->sector = 0; } else { // same clusters, move filepointer backwards within current cluster file->sector = offset; } } // moving forward while ((file->sector^offset) & cluster_mask) // compare clusters { // different clusters, get next one file->cluster = NextCluster(file->cluster); // end of chain reached if(file->cluster == EOC) return 0; file->sector += cluster_size; // move file pointer to next cluster } file->sector = offset; // same clusters return(1); } #pragma section_code_init RAMFUNC unsigned char FileRead(fileTYPE *file, unsigned char *pBuffer) { unsigned long sb; sb = data_start; // start of data in partition sb += cluster_size * (file->cluster-2); // cluster offset sb += file->sector & ~cluster_mask; // sector offset in cluster if (!lread(sb, pBuffer)) // read sector from drive return(0); else return(1); } #pragma section_no_code_init unsigned char FileReadEx(fileTYPE *file, unsigned char *pBuffer, unsigned long nSize) { unsigned long sb; unsigned long bc; // block count of single multisector read operation while (nSize) { sb = data_start; // start of data in partition sb += cluster_size * (file->cluster-2); // cluster offset sb += file->sector & ~cluster_mask; // sector offset in cluster bc = cluster_size - (file->sector & ~cluster_mask); // sector offset in the cluster if (nSize < bc) bc = nSize; // !!!!!!!! if (!MMC_ReadMultiple(sb, pBuffer, bc)) return 0; if (!FileSeek(file, bc, SEEK_CUR)) return 0; nSize -= bc; } return 1; } unsigned char FileWrite(fileTYPE *file,unsigned char *pBuffer) { unsigned long sector; if(!lwrite) return 0; // error sector = data_start; // start of data in partition sector += cluster_size * (file->cluster-2); // cluster offset sector += file->sector & ~cluster_mask; // sector offset in cluster if (!lwrite(sector, pBuffer)) // write sector from drive return(0); else return(1); } unsigned char FileCreate(unsigned long iDirectory, fileTYPE *file) { // TODO: deleted entries are not empty, they have to be cleared first /* find empty dir entry find empty cluster in fat mark cluster in fat as used update fat copy update dir entry */ if(!lwrite) return 0; // error DIRENTRY *pEntry = NULL; // pointer to current entry in sector buffer unsigned long iDirectorySector; // current sector of directory entries table unsigned long iDirectoryCluster; // start cluster of subdirectory or FAT32 root directory unsigned long iEntry; // entry index in directory cluster or FAT16 root directory unsigned long nEntries; // number of entries per cluster or FAT16 root directory size if (iDirectory) // subdirectory { iDirectoryCluster = iDirectory; iDirectorySector = data_start + cluster_size * (iDirectoryCluster-2); nEntries = cluster_size << 4; // 16 entries per sector } else // root directory { iDirectoryCluster = root_directory_cluster; iDirectorySector = root_directory_start; nEntries = fat32 ? cluster_size << 4 : root_directory_size << 4; // 16 entries per sector } while (1) { for (iEntry = 0; iEntry < nEntries; iEntry++) { if ((iEntry & 0x0F) == 0) // first entry in sector, load the sector { lread(iDirectorySector++, sector_buffer); // read directory sector pEntry = (DIRENTRY*)sector_buffer; } else pEntry++; if (pEntry->Name[0] == SLOT_EMPTY) { // iprintf("Empty entry found in sector %lu at index %lu\n", iDirectorySector-1, iEntry&0x0F); unsigned long cluster = ClusterAllocate(); if(cluster) { // initialize direntry memset((void*)pEntry, 0, sizeof(DIRENTRY)); memcpy((void*)pEntry->Name, file->name, 11); pEntry->Attributes = file->attributes; pEntry->CreateDate = SwapBB(FILEDATE(2009, 9, 1)); pEntry->CreateTime = SwapBB(FILETIME(0, 0, 0)); pEntry->AccessDate = SwapBB(FILEDATE(2009, 9, 1)); pEntry->ModifyDate = SwapBB(FILEDATE(2009, 9, 1)); pEntry->ModifyTime = SwapBB(FILETIME(0, 0, 0)); pEntry->StartCluster = (unsigned short)SwapBB(cluster); // for 68000 pEntry->HighCluster = fat32 ? (unsigned short)SwapBB(cluster >> 16) : 0; // for 68000 pEntry->FileSize = SwapBBBB(file->size); // for 68000 // store dir entry if (!lwrite(iDirectorySector - 1, sector_buffer)) { iprintf("FileCreate(): directory write failed!\n"); return(0); } file->start_cluster = cluster; file->cluster = cluster; file->sector = 0; file->cluster_change = 1; // one data cluster was already allocated file->entry.sector = iDirectorySector - 1; file->entry.index = iEntry & 0x0F; return(1); } } } if (iDirectory || fat32) // subdirectory is a linked cluster chain { iDirectoryCluster = NextCluster(iDirectoryCluster); // get next cluster in chain if (fat32 ? (iDirectoryCluster & 0x0FFFFFF8) == 0x0FFFFFF8 : (iDirectoryCluster & 0xFFF8) == 0xFFF8) // check if end of chain break; // no more clusters in chain iDirectorySector = data_start + cluster_size * (iDirectoryCluster - 2); // calculate first sector address of the new cluster } else break; } ErrorMessage(" Can\'t create config file!", 0); return(0); } // changing of allocated cluster number is not supported - new size must be within current cluster number unsigned char UpdateEntry(fileTYPE *file) { DIRENTRY *pEntry; if(!lwrite) return 0; // error if (!lread(file->entry.sector, sector_buffer)) { iprintf("UpdateEntry(): directory read failed!\n"); return(0); } pEntry = (DIRENTRY*)sector_buffer; pEntry += file->entry.index; memcpy((void*)pEntry->Name, file->name, 11); pEntry->Attributes = file->attributes; pEntry->FileSize = SwapBBBB(file->size); // for 68000 if (!lwrite(file->entry.sector, sector_buffer)) { iprintf("UpdateEntry(): directory write failed!\n"); return(0); } return(1); } unsigned char FileWriteEnd(fileTYPE *file) { // check if current cluster is the end of the chain unsigned long marker = EOC; if(file->cluster != EOC) { do { unsigned long next_cluster = NextCluster(file->cluster); // NextCluster has made sure the right fat sector is already loaded unsigned long fat_index = CLUSTER2SECTOR(file->cluster); unsigned short buffer_index = CLUSTER2OFFSET(file->cluster); // mark cluster as used if(fat32) fat_buffer.fat32[buffer_index] = marker; else fat_buffer.fat16[buffer_index] = marker; if(!FatSave(fat_index)) return(0); // has a cluster been freed? if(!marker) file->cluster_change--; file->cluster = next_cluster; marker = 0; } while(file->cluster != EOC); } // update fat32 info sector if(info_sector && file->cluster_change) { struct InfoSector *isec = (struct InfoSector*)sector_buffer; if (!lread(info_sector, sector_buffer)) { iprintf("info_sector read failed!\n"); return(0); } // check for valid info sector signs and markers if((isec->boot_sign != SwapBB(0xaa55)) || (isec->magic != SwapBBBB(0x41615252)) || // RRaA (isec->signature != SwapBBBB(0x61417272)) || // rrAa (isec->free_clusters == SwapBBBB(0xffffffff))) // free_clusters unused info_sector = 0; // no valid info sector else { isec->free_clusters = SwapBBBB(SwapBBBB(isec->free_clusters) - file->cluster_change); // update info_sector if (!lwrite(info_sector, sector_buffer)) { iprintf("info_sector write failed!\n"); return(0); } } } return(1); } // start a new file or use existing one unsigned char FileNew(fileTYPE *file, char *name, int size) { if(FileOpen(file, name) != 0) { // iprintf("File exists, using it\n"); // adjust file size if required if(file->size != size) { file->size = size; if (!UpdateEntry(file)) { iprintf("File size update failed\n"); return 0; } } } else { // iprintf("File does not exist, creating it\n"); strncpy(file->name, name, 11); file->attributes = 0; file->size = size; if(!FileCreate(0, file)) { iprintf("File creation failed\n"); return 0; } } return 1; }