📄 fat.c
字号:
//++
// This routine will attempt to get the LFN (that's Long File Name, if you've
// been out of touch for a while :-) that corresponds to the directory entry
// specified. This is a slight kludge because it works only for the current
// directory entry - the one just returned by Prev/NextMP3File(). You can't
// save away a directory entry and then call this routine some time later.
// Sorry, but that's not a problem for the player.
//
// LFNs are stored in bogus directory entries that appear just BEFORE the
// real directory entry. Unfortunatly there's no guarantee that the entire
// LFN will appear in one sector, so it may be necessary for us to back up
// and re-read the previous directory sector to complete the LFN.
//
// Finally, LFNs are stored in Unicode with 16 bit characters. We convert
// these to ASCII by the simple expedient of dropping the upper byte - that
// mostly works for all Western character sets...
//--
PRIVATE BOOL GetFileLFN (void) STACKSLOCS
{
BYTE bSequence, bEntry, i, cbString; LONG lOriginalDirectorySector;
BOOL fLFNFound; PXSTRING pxString; LFN_DIRECTORY_ENTRY XDATA *pxLFN;
ASSERT( (m_lCurDirSector != 0), ("Call NextMP3File() first!") );
memset(g_szLongFileName, 0, MAX_LFN_LENGTH); pxString = g_szLongFileName;
cbString = MAX_LFN_LENGTH; bSequence = 1; fLFNFound = FALSE;
lOriginalDirectorySector = m_lCurDirSector; bEntry = m_bCurDirEntry;
//DBGOUT(("GetFileLFN: searching for LFN for file \"%8.8s.%3.3s\"\n",
// &(GetCurDirEntry()->szFileName), &(GetCurDirEntry()->szFileType) ));
// Start with the previous directory entry and keep reading backwards
// (possibly re-reading the previous sector, if required) until we find
// something which is NOT a LFN entry.
while (TRUE) {
if (bEntry == 0) {
// Re-read the previous directory sector...
if (m_lCurDirSector == m_lVolRootOffset) break;
--m_lCurDirSector; bEntry = DIRECTORY_ENTRIES_PER_SECTOR - 1;
if (!ReadDirSector(m_lCurDirSector)) break;
} else
--bEntry;
// See if this entry is another LFN segment. Note that LFN entries
// actually have a checksum, which we could (and probably should!) actually
// check to make sure this entry is valid. We don't bother, however, because
// the other checks are usually enough...
pxLFN = (LFN_DIRECTORY_ENTRY XDATA *) GetDirEntry(bEntry);
//DBGOUT(("GetFileLFN: checking, Attributes=0x%02bX, Sequence=%bd, MBZ1,2=%bd %u\n",
// pxLFN->bAttributes, pxLFN->bSequence, pxLFN->bMBZ1, pxLFN->wMBZ2 ));
if (pxLFN->bAttributes != LFN_ATTRIBUTES) break;
if ((pxLFN->bSequence & 0x3F) != bSequence) break;
if ((pxLFN->bMBZ1 != 0) || (pxLFN->wMBZ2 != 0)) break;
// Ok, this looks like a real LFN entry. Extract the characters from it
// and store them in the caller's buffer. Remember that Unicode character
// 0xFFFF is used to signal the end of the string. BTW, using the sequence
// LOBYTE(WSWAP(x)) is kind of wasteful (and it's a little too much for C51
// to optimize away) but it is portable. You can't just replace it with
// HIBYTE(x) because SDCC is little endian and there WSWAP() is a no-op.
//
// Lastly, you might think it would be more effective to write a sub-
// routine for this, but it would require pointers to awChars, pxString
// (the variable itself, so it can be updated), and cbMaxString (again,
// so that it can be updated) at very least. It actually generates less
// code on the 8051 to just write it out the long way.
for (i = 0; i < 5; ++i) {
if (pxLFN->awChars1[i] == 0xFFFF) goto EndOfLFN;
if (--cbString == 0) goto EndOfLFN;
*pxString++ = LOBYTE(WSWAP(pxLFN->awChars1[i]));
}
for (i = 0; i < 6; ++i) {
if (pxLFN->awChars2[i] == 0xFFFF) goto EndOfLFN;
if (--cbString == 0) goto EndOfLFN;
*pxString++ = LOBYTE(WSWAP(pxLFN->awChars2[i]));
}
for (i = 0; i < 2; ++i) {
if (pxLFN->awChars3[i] == 0xFFFF) goto EndOfLFN;
if (--cbString == 0) goto EndOfLFN;
*pxString++ = LOBYTE(WSWAP(pxLFN->awChars3[i]));
}
// Remember that we found an LFN and bump the sequence for the next one...
fLFNFound = TRUE; ++bSequence;
// If bit 0x40 if the sequence number is set, then this is the last
// entry in this LFN and we can stop.
if ((pxLFN->bSequence & 0x40) != 0) goto EndOfLFN;
}
EndOfLFN:
// If we've changed the directory sector, then be sure and restore the
// original before we leave...
if (m_lCurDirSector != lOriginalDirectorySector) {
m_lCurDirSector = lOriginalDirectorySector; ReadDirSector(m_lCurDirSector);
}
#ifdef DEBUG
if (fLFNFound) DBGOUT(("GetFileLFN: \"%s\"\n", g_szLongFileName));
#endif
return fLFNFound;
}
//++
// This routine will return the first MP3 file found in the root directory.
// Normally it's called once, when the player starts up, and then is followed
// by many calls to NextMP3File() or PrevMP3File(). It goes something like
// this:
//
// PXDIRENT pxFile = FirstMP3File();
// ...
// while (pxFile != NULL) {
// ...
// pxFile = NextMP3File();
// ... or ...
// pxFile = PrevMP3File();
// }
//
// This loop will normally execute endlessly playing the all the MP3 files in
// the root directory over and over again. pxFile will be NULL only in the case
// of severe errors (i.e. a hardware failure reading the directory) - in all other
// cases, NextMP3File() or PrevMP3File() will loop back to the beginning or end of
// the root directory as required. Perhaps not the most useful general purpose
// directory traversal code, but just what we want for an MP3 player!
//--
PUBLIC PXDIRENT FirstMP3File (void)
{
ASSERT((m_lVolRootOffset != 0), ("Call InitializeFAT() first!"));
if (!StartDirScan(TRUE)) return NULL;
return NextMP3File();
}
//++
// This routine searches the current directory for the next MP3 file.
// The m_lCurDirSector and m_bCurDirEntry globals should already be set up
// and optionally, the current directory segment can be in memory in the
// buffer addressed by m_pCurDirBCB. However if the latter is NULL, this
// routine will re-read the current directory sector before proceeding;
// this allows us to return the directory sector buffer to the free list
// while we're actually playing an MP3 file, and that gives us an extra
// buffer for MP3 data.
//
// This routine automatically skips empty files, deleted files, any
// volume name file, any subdirectory file, and any file marked as system
// or hidden. All other files, including read only files, will be returned.
// It's up to the caller to sort out the ones the application wants.
//
// N.B. When the end of the directory is reached, this routine simply
// loops back to the start and returns the same set of files all over again.
// It returns NULL only in the case of gross failure (e.g. CompactFlash read
// errors). It's not that useful for general directory traversal, but it's
// just the thing for an MP3 player!!
//
// Notice also that this will unfortunately loop forever (!!) if there are
// no MP3 files in the root directory of this volume, but we know that's not
// the case because the code has already checked this by calling GetVolumeData().
//
// And one final word - note that return return value is supposed to be a
// pointer to the current entry in the directory buffer. "Supposed to be", but
// it isn't really because we release the directory buffer while we're playing
// MP3s. Instead, this routine makes a copy of just the current directory
// entry into a temporary buffer in XDATA and returns the address of that.
// The caller can't tell the difference either way...
//--
PUBLIC PXDIRENT NextMP3File (void)
{
PXDIRENT pxDirEnt;
// If the directory sector buffer has been released, then re-read the
// current directory sector before proceeding!
if (m_pCurDirBCB == NULL) ReadDirSector(m_lCurDirSector);
while (TRUE) {
// Advance m_bCurDirEntry to the next entry in this sector. If we've
// reached the end, then read the next sector. If that fails (because
// we've reached the end of the root directory) then start over again.
if (++m_bCurDirEntry >= DIRECTORY_ENTRIES_PER_SECTOR)
if (!ReadNextDirSector())
if (!StartDirScan(TRUE)) return NULL;
// We've got a file - see if it's an MP3 file ...
pxDirEnt = GetDirEntry(m_bCurDirEntry);
if (IsMP3File(pxDirEnt)) {
// Read the LFN for this file (if it has one), which we have to do while
// the current directory sector is still in memory, and then cache the
// file's information. Release the directory segment, and return a pointer
// to the cached file information...
GetFileLFN(); memcpy(&m_CurDirEntCache, pxDirEnt, sizeof(DIRECTORY_ENTRY));
ReleaseDirBuffer(); return &m_CurDirEntCache;
}
}
}
//++
// This routine is essentially the same as NextMP3File(), except that it goes
// backwards rather than forwards. It's used by the navigation knob to find the
// previous MP3 file. When we reach the beginning of the directory, it loops
// back to the end and starts returning the same list of files all over again.
//--
PUBLIC PXDIRENT PrevMP3File (void)
{
PXDIRENT pxDirEnt;
if (m_pCurDirBCB == NULL) ReadDirSector(m_lCurDirSector);
while (TRUE) {
if (m_bCurDirEntry-- == 0)
if (!ReadPrevDirSector())
if (!StartDirScan(FALSE)) return NULL;
pxDirEnt = GetDirEntry(m_bCurDirEntry);
if (IsMP3File(pxDirEnt)) {
GetFileLFN(); memcpy(&m_CurDirEntCache, pxDirEnt, sizeof(DIRECTORY_ENTRY));
ReleaseDirBuffer(); return &m_CurDirEntCache;
}
}
}
//++
// Return the starting sector number for a specific cluster...
//--
PRIVATE LONG SectorFromCluster (CLUSTER wCluster)
{
LONG lSector;
lSector = ((LONG) (wCluster-CLUSTER_OFFSET)) * ((LONG) (m_bVolClusterSize)) + m_lVolDataOffset;
//DBGOUT(("SectorFromCluster: Cluster %u mapped to sector %ld ...\n", wCluster, lSector));
return lSector;
}
//++
// The first cluster in a file is easily determined from its directory entry.
// If a file consists of more than one cluster, then the FAT entry for the first
// cluster contains the number of the next cluster; the FAT entry for the second
// cluster points to the third, and so on. Given the current cluster in a file,
// this function will determine the next cluster, reading the FAT as necessary.
// Note that this depends solely on the current cluster; nothing else about the
// file matters (at least not here!).
//
// Notice that the current FAT sector is kept in the buffer pointed to by
// m_pCurFATBCB. Unlike the directory buffer, which is released while we're
// actually playing an MP3 file, the current FAT sector is ALWAYS kept in memory
// even while we're playing. In principle we could release the FAT buffer too,
// but remember that we need to keep calling NextCluster() to read the MP3 file
// even while we're playing, and that would mean that we'd need to keep re-reading
// the current FAT sector while we play. That's just too much overhead!
//--
PRIVATE WORD NextCluster (CLUSTER wCluster)
{
LONG lFATSector; BYTE bFATOffset;
// The first step is to figure out which FAT location, both the FAT sector and
// the offset within that sector, holds the entry for the current cluster.
// IMPORTANT! Note that right now, IDE_SECTOR_SIZE == 512, sizeof(CLUSTER) == 2,
// and CLUSTERS_PER_SECTOR == 256. So long as this is true (and it probably will
// be until somebody implements FAT12 or FAT32), we can save ourselves a lot of
// time here, especially on a microprocessor where long divisions are expensive!
//lFATSector = (WCluster) / CLUSTERS_PER_SECTOR;
//lFATSector = (wCluster) % CLUSTERS_PER_SECTOR;
lFATSector = m_lVolFATOffset + HIBYTE(wCluster);
bFATOffset = LOBYTE(wCluster);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -