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
* Use CLUSTER_ERROR when an error occurs with the FAT, not CLUSTER_FREE
2007-11-01 - Chishm
* Added unicode support
*/
#include <string.h>
#include <ctype.h>
#include <wchar.h>
#include <wctype.h>
#include <stdlib.h>
#include "directory.h"
#include "common.h"
@ -66,8 +72,9 @@
#define DIR_ENTRY_FREE 0xE5
#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
enum LFN_offset {
@ -98,74 +105,129 @@ const int LFN_offset_table[13]={0x01,0x03,0x05,0x07,0x09,0x0E,0x10,0x12,0x14,0x1
const char ILLEGAL_ALIAS_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 nameLength;
int ucsLength;
const char* tempName = name;
nameLength = strnlen(name, MAX_FILENAME_LENGTH);
// Make sure the name is short enough to be valid
if ( strnlen(name, MAX_FILENAME_LENGTH) >= MAX_FILENAME_LENGTH) {
return false;
if ( nameLength >= MAX_FILENAME_LENGTH) {
return -1;
}
// Make sure it doesn't contain any invalid characters
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
// Make sure the name doesn't contain any control codes or codes not representable in UCS-2
for (i = 0; i < nameLength; i++) {
if (name[i] < 0x20) {
return false;
if (name[i] < 0x20 || name[i] >= ABOVE_UCS_RANGE) {
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
return true;
return ucsLength;
}
bool _FAT_directory_isValidAlias (const char* name) {
u32 i;
u32 nameLength;
const char* dot;
/*
Convert a multibyte encoded string into a NUL-terminated UCS-2 string, storing at most len characters
return number of characters stored
*/
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
if ( strnlen(name, MAX_ALIAS_LENGTH) >= MAX_ALIAS_LENGTH) {
return false;
}
// Make sure it doesn't contain any invalid characters
if (strpbrk (name, ILLEGAL_ALIAS_CHARACTERS) != NULL) {
return false;
}
nameLength = strnlen(name, MAX_ALIAS_LENGTH);
// Make sure the name doesn't contain any control codes
for (i = 0; i < nameLength; i++) {
if (name[i] < 0x20) {
return false;
}
}
dot = strchr ( name, '.');
// 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;
}
while (count < len-1 && src != '\0') {
bytes = mbrtowc (&tempChar, src, MB_CUR_MAX, &ps);
if (bytes > 0) {
*dst = (ucs2_t)tempChar;
src += bytes;
dst++;
count++;
} else if (bytes == 0) {
break;
} else {
// Make sure the entire file name is 1-8 characters long
if ((nameLength > 8) || (nameLength < 1)) {
return false;
return -1;
}
}
*dst = '\0';
return count;
}
// 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) {
int i=0;
@ -245,11 +307,12 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
u8 entryData[0x20];
ucs2_t lfn[MAX_LFN_LENGTH];
bool notFound, found;
u32 maxSectors;
int lfnPos;
u8 lfnChkSum, chkSum;
char* filename;
bool lfnExists;
int i;
@ -264,7 +327,6 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
}
entryEnd = entryStart;
filename = entry->filename;
// 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) {
@ -294,10 +356,10 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
entryStart = entryEnd; // This is the start of a directory entry
lfnExists = true;
lfnPos = (entryData[LFN_offset_ordinal] & ~LFN_END) * 13;
if (lfnPos > MAX_FILENAME_LENGTH - 1) {
lfnPos = MAX_FILENAME_LENGTH - 1;
if (lfnPos > MAX_LFN_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];
} if (lfnChkSum != entryData[LFN_offset_checkSum]) {
lfnExists = false;
@ -309,7 +371,7 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
lfnPos = LAST_LFN_POS;
}
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) {
@ -326,12 +388,18 @@ bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
}
if (chkSum != lfnChkSum) {
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;
_FAT_directory_entryGetAlias (entryData, filename);
_FAT_directory_entryGetAlias (entryData, entry->filename);
}
found = true;
}
@ -389,6 +457,8 @@ bool _FAT_directory_entryFromPosition (PARTITION* partition, DIR_ENTRY* entry) {
bool entryStillValid;
bool finished;
ucs2_t lfn[MAX_LFN_LENGTH];
int i;
int lfnPos;
@ -418,7 +488,7 @@ bool _FAT_directory_entryFromPosition (PARTITION* partition, DIR_ENTRY* entry) {
lfnPos = LAST_LFN_POS_CORRECTION;
}
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)) {
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;
@ -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 "."
// 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);
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
// Check if the filename matches
if ((dirnameLength == strnlen(entry->filename, MAX_FILENAME_LENGTH))
&& (strncasecmp(entry->filename, pathPosition, dirnameLength) == 0)) {
&& (_FAT_directory_mbsncasecmp(pathPosition, entry->filename, dirnameLength) == 0)) {
found = true;
}
// Check if the alias matches
_FAT_directory_entryGetAlias (entry->entryData, alias);
if ((dirnameLength == strnlen(alias, MAX_ALIAS_LENGTH))
&& (strncasecmp(alias, pathPosition, dirnameLength) == 0)) {
&& (_FAT_directory_mbsncasecmp(pathPosition, alias, dirnameLength) == 0)) {
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
// Check if the filename matches
if ((dirnameLength == strnlen(tempEntry.filename, MAX_FILENAME_LENGTH))
&& (strcasecmp(tempEntry.filename, name) == 0)) {
&& (_FAT_directory_mbsncasecmp(name, tempEntry.filename, dirnameLength) == 0)) {
return true;
}
// Check if the alias matches
_FAT_directory_entryGetAlias (tempEntry.entryData, alias);
if ((dirnameLength == strnlen(alias, MAX_ALIAS_LENGTH))
&& (strcasecmp(alias, name) == 0)) {
&& (_FAT_directory_mbsncasecmp(name, alias, dirnameLength) == 0)) {
return true;
}
foundFile = _FAT_directory_getNextEntry (partition, &tempEntry);
@ -695,7 +770,113 @@ static bool _FAT_directory_entryExists (PARTITION* partition, const char* name,
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) {
u32 entrySize;
@ -706,6 +887,8 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
bool entryStillValid;
u8 aliasCheckSum = 0;
char alias [MAX_ALIAS_LENGTH];
int aliasLen;
int lfnLen;
// Make sure the filename is not 0 length
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
if ( !(_FAT_directory_isValidLfn (entry->filename))) {
lfnLen = _FAT_directory_lfnLength (entry->filename);
if (lfnLen < 0) {
return false;
}
@ -748,13 +932,17 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
entry->entryData[0] = '.';
entry->entryData[1] = '.';
entrySize = 1;
} else if ( _FAT_directory_isValidAlias (entry->filename)) {
// Short filename
strupr (entry->filename);
} else {
// Normal file name
aliasLen = _FAT_directory_createAlias (alias, entry->filename);
if (aliasLen < 0) {
return false;
} else if (aliasLen == 0) {
// It's a normal short 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];
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] = ' ';
@ -763,8 +951,8 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
if (entry->filename[i] == '.') {
// Copy extension
++ i;
while ((entry->filename[i] != '\0') && (j < 11)) {
entry->entryData[j] = entry->filename[i];
while ((alias[i] != '\0') && (j < 11)) {
entry->entryData[j] = alias[i];
++ i;
++ j;
}
@ -774,71 +962,54 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
++ j;
}
} else {
// Long filename needed
entrySize = ((strnlen (entry->filename, MAX_FILENAME_LENGTH) + LFN_ENTRY_LENGTH - 1) / LFN_ENTRY_LENGTH) + 1;
// Generate alias
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';
}
// 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);
}
// 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
// Generate numeric tail
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;
}
// Make it upper case
strupr (alias);
// Now copy it into the directory entry data
memcpy (entry->entryData, alias, 8);
memcpy (entry->entryData + 8, alias + 9, 3);
for (i = 0; i < 10; i++) {
if (entry->entryData[i] < 0x20) {
memcpy (entry->entryData, alias, MAX_ALIAS_PRI_LENGTH);
memcpy (entry->entryData + MAX_ALIAS_PRI_LENGTH, alias + MAX_ALIAS_PRI_LENGTH + 1, MAX_ALIAS_EXT_LENGTH);
for (i = 0; i < MAX_ALIAS_PRI_LENGTH+MAX_ALIAS_EXT_LENGTH; i++) {
if (entry->entryData[i] < ' ') {
// Replace null and control characters with spaces
entry->entryData[i] = 0x20;
entry->entryData[i] = ' ';
}
}
// Generate alias checksum
for (i=0; i < 11; i++)
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];
}
}
}
// Find or create space for the entry
@ -849,6 +1020,11 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
// Write out directory entry
curEntryPos = entry->dataStart;
{
// lfn is only pushed onto the stack here, reducing overall stack usage
ucs2_t lfn[MAX_LFN_LENGTH];
_FAT_directory_mbstoucs2 (lfn, entry->filename, MAX_LFN_LENGTH);
for (entryStillValid = true, i = entrySize; entryStillValid && i > 0;
entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &curEntryPos, false), -- i )
{
@ -863,7 +1039,7 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
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]);
u16_to_u8array (lfnEntry, LFN_offset_table[j], lfn [(i - 2) * 13 + j]);
}
}
@ -877,6 +1053,7 @@ bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, u32 dirClu
_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);
}
}
}
return true;
}

View File

@ -28,6 +28,9 @@
2006-07-11 - Chishm
* Original release
2007-11-01 - Chishm
* Added unicode support
*/
#ifndef _DIRECTORY_H
@ -39,9 +42,13 @@
#include "partition.h"
#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 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 DIR_SEPARATOR '/'