Added unicode support

This commit is contained in:
Michael Chisholm 2007-11-01 06:00:30 +00:00
parent ccb080a071
commit 9deb154adb
2 changed files with 365 additions and 181 deletions

View File

@ -49,10 +49,16 @@
2007-09-01 - Chishm 2007-09-01 - Chishm
* Use CLUSTER_ERROR when an error occurs with the FAT, not CLUSTER_FREE * Use CLUSTER_ERROR when an error occurs with the FAT, not CLUSTER_FREE
2007-11-01 - Chishm
* Added unicode support
*/ */
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <wchar.h>
#include <wctype.h>
#include <stdlib.h>
#include "directory.h" #include "directory.h"
#include "common.h" #include "common.h"
@ -66,8 +72,9 @@
#define DIR_ENTRY_FREE 0xE5 #define DIR_ENTRY_FREE 0xE5
#define LAST_LFN_POS (19*13) #define LAST_LFN_POS (19*13)
#define LAST_LFN_POS_CORRECTION (MAX_FILENAME_LENGTH-15) #define LAST_LFN_POS_CORRECTION (MAX_LFN_LENGTH-15)
typedef unsigned short ucs2_t;
// Long file name directory entry // Long file name directory entry
enum LFN_offset { enum LFN_offset {
@ -98,75 +105,130 @@ const int LFN_offset_table[13]={0x01,0x03,0x05,0x07,0x09,0x0E,0x10,0x12,0x14,0x1
const char ILLEGAL_ALIAS_CHARACTERS[] = "\\/:;*?\"<>|&+,=[] "; const char ILLEGAL_ALIAS_CHARACTERS[] = "\\/:;*?\"<>|&+,=[] ";
const char ILLEGAL_LFN_CHARACTERS[] = "\\/:*?\"<>|"; const char ILLEGAL_LFN_CHARACTERS[] = "\\/:*?\"<>|";
bool _FAT_directory_isValidLfn (const char* name) { /*
Returns number of UCS-2 characters needed to encode an LFN
Returns -1 if it is an invalid LFN
*/
#define ABOVE_UCS_RANGE 0xF0
static int _FAT_directory_lfnLength (const char* name) {
u32 i; u32 i;
u32 nameLength; u32 nameLength;
int ucsLength;
const char* tempName = name;
nameLength = strnlen(name, MAX_FILENAME_LENGTH);
// Make sure the name is short enough to be valid // Make sure the name is short enough to be valid
if ( strnlen(name, MAX_FILENAME_LENGTH) >= MAX_FILENAME_LENGTH) { if ( nameLength >= MAX_FILENAME_LENGTH) {
return false; return -1;
} }
// Make sure it doesn't contain any invalid characters // Make sure it doesn't contain any invalid characters
if (strpbrk (name, ILLEGAL_LFN_CHARACTERS) != NULL) { if (strpbrk (name, ILLEGAL_LFN_CHARACTERS) != NULL) {
return false; return -1;
} }
nameLength = strnlen(name, MAX_FILENAME_LENGTH); // Make sure the name doesn't contain any control codes or codes not representable in UCS-2
// Make sure the name doesn't contain any control codes
for (i = 0; i < nameLength; i++) { for (i = 0; i < nameLength; i++) {
if (name[i] < 0x20) { if (name[i] < 0x20 || name[i] >= ABOVE_UCS_RANGE) {
return false; return -1;
} }
} }
// Convert to UCS-2 and get the resulting length
ucsLength = mbsrtowcs(NULL, &tempName, MAX_LFN_LENGTH, NULL);
if (ucsLength < 0 || ucsLength >= MAX_LFN_LENGTH) {
return -1;
}
// Otherwise it is valid // Otherwise it is valid
return true; return ucsLength;
} }
bool _FAT_directory_isValidAlias (const char* name) { /*
u32 i; Convert a multibyte encoded string into a NUL-terminated UCS-2 string, storing at most len characters
u32 nameLength; return number of characters stored
const char* dot; */
static size_t _FAT_directory_mbstoucs2 (ucs2_t* dst, const char* src, size_t len) {
mbstate_t ps = {0};
wchar_t tempChar;
int bytes;
size_t count = 0;
// Make sure the name is short enough to be valid while (count < len-1 && src != '\0') {
if ( strnlen(name, MAX_ALIAS_LENGTH) >= MAX_ALIAS_LENGTH) { bytes = mbrtowc (&tempChar, src, MB_CUR_MAX, &ps);
return false; if (bytes > 0) {
} *dst = (ucs2_t)tempChar;
// Make sure it doesn't contain any invalid characters src += bytes;
if (strpbrk (name, ILLEGAL_ALIAS_CHARACTERS) != NULL) { dst++;
return false; count++;
} } else if (bytes == 0) {
nameLength = strnlen(name, MAX_ALIAS_LENGTH); break;
// Make sure the name doesn't contain any control codes } else {
for (i = 0; i < nameLength; i++) { return -1;
if (name[i] < 0x20) {
return false;
} }
} }
*dst = '\0';
dot = strchr ( name, '.'); return count;
// Make sure there is only one '.'
if ((dot != NULL) && (strrchr ( name, '.') != dot)) {
return false;
}
// If there is a '.':
if (dot != NULL) {
// Make sure the filename portion is 1-8 characters long
if (((dot - 1 - name) > 8) || ((dot - 1 - name) < 1)) {
return false;
}
// Make sure the extension is 1-3 characters long, if it exists
if ((strnlen(dot + 1, MAX_ALIAS_LENGTH) > 3) || (strnlen(dot + 1, MAX_ALIAS_LENGTH) < 1)) {
return false;
}
} else {
// Make sure the entire file name is 1-8 characters long
if ((nameLength > 8) || (nameLength < 1)) {
return false;
}
}
// Since we made it through all those tests, it must be valid
return true;
} }
/*
Convert a UCS-2 string into a NUL-terminated multibyte string, storing at most len chars
return number of chars stored
*/
static size_t _FAT_directory_ucs2tombs (char* dst, const ucs2_t* src, size_t len) {
mbstate_t ps = {0};
size_t count = 0;
int bytes;
char buff[MB_CUR_MAX];
int i;
while (count < len - 1 && src != '\0') {
bytes = wcrtomb (buff, *src, &ps);
if (bytes < 0) {
return -1;
}
if (count + bytes < len && bytes > 0) {
for (i = 0; i < bytes; i++) {
*dst++ = buff[i];
}
src++;
count += bytes;
} else {
break;
}
}
*dst = L'\0';
return count;
}
/*
Case-independent comparison of two multibyte encoded strings
*/
static int _FAT_directory_mbsncasecmp (const char* s1, const char* s2, size_t len1) {
wchar_t wc1, wc2;
mbstate_t ps1 = {0};
mbstate_t ps2 = {0};
size_t b1 = 0;
size_t b2 = 0;
if (len1 == 0) {
return 0;
}
do {
s1 += b1;
s2 += b2;
b1 = mbrtowc(&wc1, s1, MB_CUR_MAX, &ps1);
b2 = mbrtowc(&wc2, s2, MB_CUR_MAX, &ps2);
if (b1 < 0 || b2 < 0) {
break;
}
len1 -= b1;
} while (len1 > 0 && towlower(wc1) == towlower(wc2) && wc1 != 0);
return towlower(wc1) - towlower(wc2);
}
static bool _FAT_directory_entryGetAlias (const u8* entryData, char* destName) { static bool _FAT_directory_entryGetAlias (const u8* entryData, char* destName) {
int i=0; int i=0;
int j=0; int j=0;
@ -245,11 +307,12 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
u8 entryData[0x20]; u8 entryData[0x20];
ucs2_t lfn[MAX_LFN_LENGTH];
bool notFound, found; bool notFound, found;
u32 maxSectors; u32 maxSectors;
int lfnPos; int lfnPos;
u8 lfnChkSum, chkSum; u8 lfnChkSum, chkSum;
char* filename;
bool lfnExists; bool lfnExists;
int i; int i;
@ -264,7 +327,6 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
} }
entryEnd = entryStart; entryEnd = entryStart;
filename = entry->filename;
// Can only be FAT16_ROOT_DIR_CLUSTER if it is the root directory on a FAT12 or FAT16 partition // Can only be FAT16_ROOT_DIR_CLUSTER if it is the root directory on a FAT12 or FAT16 partition
if (entryStart.cluster == FAT16_ROOT_DIR_CLUSTER) { if (entryStart.cluster == FAT16_ROOT_DIR_CLUSTER) {
@ -294,10 +356,10 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
entryStart = entryEnd; // This is the start of a directory entry entryStart = entryEnd; // This is the start of a directory entry
lfnExists = true; lfnExists = true;
lfnPos = (entryData[LFN_offset_ordinal] & ~LFN_END) * 13; lfnPos = (entryData[LFN_offset_ordinal] & ~LFN_END) * 13;
if (lfnPos > MAX_FILENAME_LENGTH - 1) { if (lfnPos > MAX_LFN_LENGTH - 1) {
lfnPos = MAX_FILENAME_LENGTH - 1; lfnPos = MAX_LFN_LENGTH - 1;
} }
filename[lfnPos] = '\0'; // Set end of lfn to null character lfn[lfnPos] = '\0'; // Set end of lfn to null character
lfnChkSum = entryData[LFN_offset_checkSum]; lfnChkSum = entryData[LFN_offset_checkSum];
} if (lfnChkSum != entryData[LFN_offset_checkSum]) { } if (lfnChkSum != entryData[LFN_offset_checkSum]) {
lfnExists = false; lfnExists = false;
@ -309,7 +371,7 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
lfnPos = LAST_LFN_POS; lfnPos = LAST_LFN_POS;
} }
for (i = 0; i < 13; i++) { for (i = 0; i < 13; i++) {
filename[lfnPos + i] = entryData[LFN_offset_table[i]]; // modify this for unicode support; lfn[lfnPos + i] = entryData[LFN_offset_table[i]] | (entryData[LFN_offset_table[i]+1] << 8);
} }
} }
} else if (entryData[DIR_ENTRY_attributes] & ATTRIB_VOL) { } else if (entryData[DIR_ENTRY_attributes] & ATTRIB_VOL) {
@ -326,12 +388,18 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
} }
if (chkSum != lfnChkSum) { if (chkSum != lfnChkSum) {
lfnExists = false; lfnExists = false;
filename[0] = '\0'; entry->filename[0] = '\0';
} }
} }
if (!lfnExists) {
if (lfnExists) {
if (_FAT_directory_ucs2tombs (entry->filename, lfn, MAX_FILENAME_LENGTH) < 0) {
// Failed to convert the file name to UTF-8. Maybe the wrong locale is set?
return false;
}
} else {
entryStart = entryEnd; entryStart = entryEnd;
_FAT_directory_entryGetAlias (entryData, filename); _FAT_directory_entryGetAlias (entryData, entry->filename);
} }
found = true; found = true;
} }
@ -389,6 +457,8 @@ bool _FAT_directory_entryFromPosition (PARTITION* partition, DIR_ENTRY* entry) {
bool entryStillValid; bool entryStillValid;
bool finished; bool finished;
ucs2_t lfn[MAX_LFN_LENGTH];
int i; int i;
int lfnPos; int lfnPos;
@ -418,7 +488,7 @@ bool _FAT_directory_entryFromPosition (PARTITION* partition, DIR_ENTRY* entry) {
lfnPos = LAST_LFN_POS_CORRECTION; lfnPos = LAST_LFN_POS_CORRECTION;
} }
for (i = 0; i < 13; i++) { for (i = 0; i < 13; i++) {
entry->filename[lfnPos + i] = entryData[LFN_offset_table[i]]; // modify this for unicode support; lfn[lfnPos + i] = entryData[LFN_offset_table[i]] | (entryData[LFN_offset_table[i]+1] << 8);
} }
} }
} }
@ -434,6 +504,11 @@ bool _FAT_directory_entryFromPosition (PARTITION* partition, DIR_ENTRY* entry) {
if (!_FAT_directory_entryGetAlias (entry->entryData, entry->filename)) { if (!_FAT_directory_entryGetAlias (entry->entryData, entry->filename)) {
return false; return false;
} }
} else {
// Encode the long file name into a multibyte string
if (_FAT_directory_ucs2tombs (entry->filename, lfn, MAX_FILENAME_LENGTH) < 0) {
return false;
}
} }
return true; return true;
@ -481,7 +556,7 @@ bool _FAT_directory_entryFromPath (PARTITION* partition, DIR_ENTRY* entry, const
// If the path is only specifying a directory in the form "." // If the path is only specifying a directory in the form "."
// and this is the root directory, return it // and this is the root directory, return it
if ((dirCluster == partition->rootDirCluster) && (strncasecmp(".", pathPosition, 2) == 0)) { if ((dirCluster == partition->rootDirCluster) && (strcmp(".", pathPosition) == 0)) {
_FAT_directory_getRootEntry (partition, entry); _FAT_directory_getRootEntry (partition, entry);
found = true; found = true;
} }
@ -506,14 +581,14 @@ bool _FAT_directory_entryFromPath (PARTITION* partition, DIR_ENTRY* entry, const
while (foundFile && !found && !notFound) { // It hasn't already found the file while (foundFile && !found && !notFound) { // It hasn't already found the file
// Check if the filename matches // Check if the filename matches
if ((dirnameLength == strnlen(entry->filename, MAX_FILENAME_LENGTH)) if ((dirnameLength == strnlen(entry->filename, MAX_FILENAME_LENGTH))
&& (strncasecmp(entry->filename, pathPosition, dirnameLength) == 0)) { && (_FAT_directory_mbsncasecmp(pathPosition, entry->filename, dirnameLength) == 0)) {
found = true; found = true;
} }
// Check if the alias matches // Check if the alias matches
_FAT_directory_entryGetAlias (entry->entryData, alias); _FAT_directory_entryGetAlias (entry->entryData, alias);
if ((dirnameLength == strnlen(alias, MAX_ALIAS_LENGTH)) if ((dirnameLength == strnlen(alias, MAX_ALIAS_LENGTH))
&& (strncasecmp(alias, pathPosition, dirnameLength) == 0)) { && (_FAT_directory_mbsncasecmp(pathPosition, alias, dirnameLength) == 0)) {
found = true; found = true;
} }
@ -680,14 +755,14 @@ static bool _FAT_directory_entryExists (PARTITION* partition, const char* name,
while (foundFile) { // It hasn't already found the file while (foundFile) { // It hasn't already found the file
// Check if the filename matches // Check if the filename matches
if ((dirnameLength == strnlen(tempEntry.filename, MAX_FILENAME_LENGTH)) if ((dirnameLength == strnlen(tempEntry.filename, MAX_FILENAME_LENGTH))
&& (strcasecmp(tempEntry.filename, name) == 0)) { && (_FAT_directory_mbsncasecmp(name, tempEntry.filename, dirnameLength) == 0)) {
return true; return true;
} }
// Check if the alias matches // Check if the alias matches
_FAT_directory_entryGetAlias (tempEntry.entryData, alias); _FAT_directory_entryGetAlias (tempEntry.entryData, alias);
if ((dirnameLength == strnlen(alias, MAX_ALIAS_LENGTH)) if ((dirnameLength == strnlen(alias, MAX_ALIAS_LENGTH))
&& (strcasecmp(alias, name) == 0)) { && (_FAT_directory_mbsncasecmp(name, alias, dirnameLength) == 0)) {
return true; return true;
} }
foundFile = _FAT_directory_getNextEntry (partition, &tempEntry); foundFile = _FAT_directory_getNextEntry (partition, &tempEntry);
@ -695,7 +770,113 @@ static bool _FAT_directory_entryExists (PARTITION* partition, const char* name,
return false; return false;
} }
/*
Creates an alias for a long file name. If the alias is not an exact match for the
filename, it returns the number of characters in the alias. If the two names match,
it returns 0. If there was an error, it returns -1.
*/
static int _FAT_directory_createAlias (char* alias, const char* lfn) {
bool lossyConversion = false; // Set when the alias had to be modified to be valid
int lfnPos = 0;
int aliasPos = 0;
wchar_t lfnChar;
int oemChar;
mbstate_t ps = {0};
int bytesUsed = 0;
const char* lfnExt;
int aliasExtLen;
// Strip leading periods
while (lfn[lfnPos] == '.') {
lfnPos ++;
lossyConversion = true;
}
// Primary portion of alias
while (aliasPos < 8 && lfn[lfnPos] != '.' && lfn[lfnPos] != '\0') {
bytesUsed = mbrtowc(&lfnChar, lfn + lfnPos, MAX_FILENAME_LENGTH - lfnPos, &ps);
if (bytesUsed < 0) {
return -1;
}
oemChar = wctob(towupper((wint_t)lfnChar));
if (wctob((wint_t)lfnChar) != oemChar) {
// Case of letter was changed
lossyConversion = true;
}
if (oemChar == ' ') {
// Skip spaces in filename
lossyConversion = true;
lfnPos += bytesUsed;
continue;
}
if (oemChar == WEOF) {
oemChar = '_'; // Replace unconvertable characters with underscores
lossyConversion = true;
}
if (strchr (ILLEGAL_ALIAS_CHARACTERS, oemChar) != NULL) {
// Invalid Alias character
oemChar = '_'; // Replace illegal characters with underscores
lossyConversion = true;
}
alias[aliasPos] = (char)oemChar;
aliasPos++;
lfnPos += bytesUsed;
}
// Alias extension
lfnExt = strrchr (lfn, '.');
if (lfnExt != NULL && lfnExt != strchr (lfn, '.')) {
// More than one period in name
lossyConversion = true;
}
if (lfnExt != NULL && lfnExt[1] != '\0') {
alias[aliasPos] = '.';
aliasPos++;
memset (&ps, 0, sizeof(ps));
for (aliasExtLen = 0; aliasExtLen < MAX_ALIAS_EXT_LENGTH && *lfnExt != '\0'; aliasExtLen++) {
bytesUsed = mbrtowc(&lfnChar, lfnExt, MAX_FILENAME_LENGTH - lfnPos, &ps);
if (bytesUsed < 0) {
return -1;
}
oemChar = wctob(towupper((wint_t)lfnChar));
if (wctob((wint_t)lfnChar) != oemChar) {
// Case of letter was changed
lossyConversion = true;
}
if (oemChar == ' ') {
// Skip spaces in alias
lossyConversion = true;
lfnExt += bytesUsed;
continue;
}
if (oemChar == WEOF) {
oemChar = '_'; // Replace unconvertable characters with underscores
lossyConversion = true;
}
if (strchr (ILLEGAL_ALIAS_CHARACTERS, oemChar) != NULL) {
// Invalid Alias character
oemChar = '_'; // Replace illegal characters with underscores
lossyConversion = true;
}
alias[aliasPos] = (char)oemChar;
aliasPos++;
lfnExt += bytesUsed;
}
if (*lfnExt != '\0') {
// Extension was more than 3 characters long
lossyConversion = true;
}
}
alias[aliasPos] = '\0';
if (lossyConversion) {
return aliasPos;
} else {
return 0;
}
}
bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirCluster) { bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirCluster) {
u32 entrySize; u32 entrySize;
@ -706,6 +887,8 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
bool entryStillValid; bool entryStillValid;
u8 aliasCheckSum = 0; u8 aliasCheckSum = 0;
char alias [MAX_ALIAS_LENGTH]; char alias [MAX_ALIAS_LENGTH];
int aliasLen;
int lfnLen;
// Make sure the filename is not 0 length // Make sure the filename is not 0 length
if (strnlen (entry->filename, MAX_FILENAME_LENGTH) < 1) { if (strnlen (entry->filename, MAX_FILENAME_LENGTH) < 1) {
@ -713,7 +896,8 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
} }
// Make sure the filename is at least a valid LFN // Make sure the filename is at least a valid LFN
if ( !(_FAT_directory_isValidLfn (entry->filename))) { lfnLen = _FAT_directory_lfnLength (entry->filename);
if (lfnLen < 0) {
return false; return false;
} }
@ -748,97 +932,84 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
entry->entryData[0] = '.'; entry->entryData[0] = '.';
entry->entryData[1] = '.'; entry->entryData[1] = '.';
entrySize = 1; entrySize = 1;
} else if ( _FAT_directory_isValidAlias (entry->filename)) {
// Short filename
strupr (entry->filename);
entrySize = 1;
// Copy into alias
for (i = 0, j = 0; (j < 8) && (entry->filename[i] != '.') && (entry->filename[i] != '\0'); i++, j++) {
entry->entryData[j] = entry->filename[i];
}
while (j < 8) {
entry->entryData[j] = ' ';
++ j;
}
if (entry->filename[i] == '.') {
// Copy extension
++ i;
while ((entry->filename[i] != '\0') && (j < 11)) {
entry->entryData[j] = entry->filename[i];
++ i;
++ j;
}
}
while (j < 11) {
entry->entryData[j] = ' ';
++ j;
}
} else { } else {
// Long filename needed // Normal file name
entrySize = ((strnlen (entry->filename, MAX_FILENAME_LENGTH) + LFN_ENTRY_LENGTH - 1) / LFN_ENTRY_LENGTH) + 1; aliasLen = _FAT_directory_createAlias (alias, entry->filename);
// Generate alias if (aliasLen < 0) {
tmpCharPtr = strrchr (entry->filename, '.');
if (tmpCharPtr == NULL) {
tmpCharPtr = strrchr (entry->filename, '\0');
}
for (i = 0, j = 0; (j < 6) && (entry->filename + i < tmpCharPtr); i++) {
if ( isalnum(entry->filename[i])) {
alias[j] = entry->filename[i];
++ j;
}
}
while (j < 8) {
alias[j] = '_';
++ j;
}
tmpCharPtr = strrchr (entry->filename, '.');
if (tmpCharPtr != NULL) {
alias[8] = '.';
// Copy extension
while ((tmpCharPtr != '\0') && (j < 12)) {
alias[j] = tmpCharPtr[0];
++ tmpCharPtr;
++ j;
}
alias[j] = '\0';
} else {
for (j = 8; j < MAX_ALIAS_LENGTH; j++) {
alias[j] = '\0';
}
}
// Get a valid tail number
alias[5] = '~';
i = 0;
do {
i++;
alias[6] = '0' + ((i / 10) % 10); // 10's digit
alias[7] = '0' + (i % 10); // 1's digit
} while (_FAT_directory_entryExists (partition, alias, dirCluster) && (i < 100));
if (i == 100) {
// Couldn't get a tail number
return false; return false;
} } else if (aliasLen == 0) {
// It's a normal short filename
entrySize = 1;
// Copy into alias
for (i = 0, j = 0; (j < 8) && (alias[i] != '.') && (alias[i] != '\0'); i++, j++) {
entry->entryData[j] = alias[i];
}
while (j < 8) {
entry->entryData[j] = ' ';
++ j;
}
if (entry->filename[i] == '.') {
// Copy extension
++ i;
while ((alias[i] != '\0') && (j < 11)) {
entry->entryData[j] = alias[i];
++ i;
++ j;
}
}
while (j < 11) {
entry->entryData[j] = ' ';
++ j;
}
} else {
// It's a long filename with an alias
entrySize = ((lfnLen + LFN_ENTRY_LENGTH - 1) / LFN_ENTRY_LENGTH) + 1;
// expand primary part to 8 characters long by padding the end with underscores
i = MAX_ALIAS_LENGTH - MAX_ALIAS_EXT_LENGTH - 2; // 1 char for '.', one for NUL, 3 for extension
// Move extension to last 3 characters
while (alias[i] != '.' && i > 0) i--;
if (i > 0) {
j = MAX_ALIAS_LENGTH - MAX_ALIAS_EXT_LENGTH - 2; // 1 char for '.', one for NUL, 3 for extension
memmove (alias + j, alias + i, strlen(alias) - i);
// Pad primary component
memset (alias + i, '_', j - i);
}
// Make it upper case // Generate numeric tail
strupr (alias); for (i = 1; i <= MAX_NUMERIC_TAIL; i++) {
j = i;
tmpCharPtr = alias + MAX_ALIAS_PRI_LENGTH - 1;
while (j > 0) {
*tmpCharPtr = j % 10;
tmpCharPtr--;
j /= 10;
}
*tmpCharPtr = '~';
if (!_FAT_directory_entryExists (partition, alias, dirCluster)) {
break;
}
}
if (i > MAX_NUMERIC_TAIL) {
// Couldn't get a valid alias
return false;
}
// Now copy it into the directory entry data // Now copy it into the directory entry data
memcpy (entry->entryData, alias, 8); memcpy (entry->entryData, alias, MAX_ALIAS_PRI_LENGTH);
memcpy (entry->entryData + 8, alias + 9, 3); memcpy (entry->entryData + MAX_ALIAS_PRI_LENGTH, alias + MAX_ALIAS_PRI_LENGTH + 1, MAX_ALIAS_EXT_LENGTH);
for (i = 0; i < 10; i++) { for (i = 0; i < MAX_ALIAS_PRI_LENGTH+MAX_ALIAS_EXT_LENGTH; i++) {
if (entry->entryData[i] < 0x20) { if (entry->entryData[i] < ' ') {
// Replace null and control characters with spaces // Replace null and control characters with spaces
entry->entryData[i] = 0x20; entry->entryData[i] = ' ';
}
}
// Generate alias checksum
for (i=0; i < MAX_ALIAS_PRI_LENGTH+MAX_ALIAS_EXT_LENGTH; i++)
{
// NOTE: The operation is an unsigned char rotate right
aliasCheckSum = ((aliasCheckSum & 1) ? 0x80 : 0) + (aliasCheckSum >> 1) + entry->entryData[i];
} }
} }
// Generate alias checksum
for (i=0; i < 11; i++)
{
// NOTE: The operation is an unsigned char rotate right
aliasCheckSum = ((aliasCheckSum & 1) ? 0x80 : 0) + (aliasCheckSum >> 1) + entry->entryData[i];
}
} }
// Find or create space for the entry // Find or create space for the entry
@ -849,32 +1020,38 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
// Write out directory entry // Write out directory entry
curEntryPos = entry->dataStart; curEntryPos = entry->dataStart;
for (entryStillValid = true, i = entrySize; entryStillValid && i > 0;
entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &curEntryPos, false), -- i )
{ {
if (i > 1) { // lfn is only pushed onto the stack here, reducing overall stack usage
// Long filename entry ucs2_t lfn[MAX_LFN_LENGTH];
lfnEntry[LFN_offset_ordinal] = (i - 1) | (i == entrySize ? LFN_END : 0); _FAT_directory_mbstoucs2 (lfn, entry->filename, MAX_LFN_LENGTH);
for (j = 0; j < 13; j++) {
if (entry->filename [(i - 2) * 13 + j] == '\0') {
if ((j > 1) && (entry->filename [(i - 2) * 13 + (j-1)] == '\0')) {
u16_to_u8array (lfnEntry, LFN_offset_table[j], 0xffff); // Padding
} else {
u16_to_u8array (lfnEntry, LFN_offset_table[j], 0x0000); // Terminating null character
}
} else {
u16_to_u8array (lfnEntry, LFN_offset_table[j], entry->filename [(i - 2) * 13 + j]);
}
}
lfnEntry[LFN_offset_checkSum] = aliasCheckSum; for (entryStillValid = true, i = entrySize; entryStillValid && i > 0;
lfnEntry[LFN_offset_flag] = ATTRIB_LFN; entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &curEntryPos, false), -- i )
lfnEntry[LFN_offset_reserved1] = 0; {
u16_to_u8array (lfnEntry, LFN_offset_reserved2, 0); if (i > 1) {
_FAT_cache_writePartialSector (partition->cache, lfnEntry, _FAT_fat_clusterToSector(partition, curEntryPos.cluster) + curEntryPos.sector, curEntryPos.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); // Long filename entry
} else { lfnEntry[LFN_offset_ordinal] = (i - 1) | (i == entrySize ? LFN_END : 0);
// Alias & file data for (j = 0; j < 13; j++) {
_FAT_cache_writePartialSector (partition->cache, entry->entryData, _FAT_fat_clusterToSector(partition, curEntryPos.cluster) + curEntryPos.sector, curEntryPos.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); if (entry->filename [(i - 2) * 13 + j] == '\0') {
if ((j > 1) && (entry->filename [(i - 2) * 13 + (j-1)] == '\0')) {
u16_to_u8array (lfnEntry, LFN_offset_table[j], 0xffff); // Padding
} else {
u16_to_u8array (lfnEntry, LFN_offset_table[j], 0x0000); // Terminating null character
}
} else {
u16_to_u8array (lfnEntry, LFN_offset_table[j], lfn [(i - 2) * 13 + j]);
}
}
lfnEntry[LFN_offset_checkSum] = aliasCheckSum;
lfnEntry[LFN_offset_flag] = ATTRIB_LFN;
lfnEntry[LFN_offset_reserved1] = 0;
u16_to_u8array (lfnEntry, LFN_offset_reserved2, 0);
_FAT_cache_writePartialSector (partition->cache, lfnEntry, _FAT_fat_clusterToSector(partition, curEntryPos.cluster) + curEntryPos.sector, curEntryPos.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE);
} else {
// Alias & file data
_FAT_cache_writePartialSector (partition->cache, entry->entryData, _FAT_fat_clusterToSector(partition, curEntryPos.cluster) + curEntryPos.sector, curEntryPos.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE);
}
} }
} }

View File

@ -28,6 +28,9 @@
2006-07-11 - Chishm 2006-07-11 - Chishm
* Original release * Original release
2007-11-01 - Chishm
* Added unicode support
*/ */
#ifndef _DIRECTORY_H #ifndef _DIRECTORY_H
@ -39,9 +42,13 @@
#include "partition.h" #include "partition.h"
#define DIR_ENTRY_DATA_SIZE 0x20 #define DIR_ENTRY_DATA_SIZE 0x20
#define MAX_FILENAME_LENGTH 256 #define MAX_LFN_LENGTH 256
#define MAX_FILENAME_LENGTH 768 // 256 UCS-2 characters encoded into UTF-8 can use up to 768 UTF-8 chars
#define MAX_ALIAS_LENGTH 13 #define MAX_ALIAS_LENGTH 13
#define LFN_ENTRY_LENGTH 13 #define LFN_ENTRY_LENGTH 13
#define MAX_ALIAS_EXT_LENGTH 3
#define MAX_ALIAS_PRI_LENGTH 8
#define MAX_NUMERIC_TAIL 999999
#define FAT16_ROOT_DIR_CLUSTER 0 #define FAT16_ROOT_DIR_CLUSTER 0
#define DIR_SEPARATOR '/' #define DIR_SEPARATOR '/'