Skip to content

Commit 62b059d

Browse files
eduard-sukharevEvilGremlin
authored andcommitted
🐛 Fix M23 long filename support (MarlinFirmware#25540)
1 parent 0d6cc0c commit 62b059d

File tree

2 files changed

+57
-40
lines changed

2 files changed

+57
-40
lines changed

Marlin/src/sd/SdBaseFile.cpp

+51-36
Original file line numberDiff line numberDiff line change
@@ -703,14 +703,18 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11]
703703
// Get VFat dir entry
704704
pvFat = (vfat_t *) p;
705705
// Get checksum from the last entry of the sequence
706-
if (pvFat->sequenceNumber & 0x40) lfnChecksum = pvFat->checksum;
706+
if (pvFat->sequenceNumber & 0x40) {
707+
lfnChecksum = pvFat->checksum;
708+
ZERO(lfnName);
709+
}
707710
// Get LFN sequence number
708711
lfnSequenceNumber = pvFat->sequenceNumber & 0x1F;
709712
if WITHIN(lfnSequenceNumber, 1, reqEntriesNum) {
710713
// Check checksum for all other entries with the starting checksum fetched before
711714
if (lfnChecksum == pvFat->checksum) {
712715
// Set chunk of LFN from VFAT entry into lfnName
713716
getLFNName(pvFat, (char *)lfnName, lfnSequenceNumber);
717+
TERN_(UTF_FILENAME_SUPPORT, convertUtf16ToUtf8((char *)lfnName));
714718
// LFN found?
715719
if (!strncasecmp((char*)dlname, (char*)lfnName, lfnNameLength)) lfnFileFound = true;
716720
}
@@ -1132,13 +1136,13 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) {
11321136
* Get the LFN filename block from a dir. Get the block in lname at startOffset
11331137
*/
11341138
void SdBaseFile::getLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) {
1135-
uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
1139+
const uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
11361140
LOOP_L_N(i, FILENAME_LENGTH) {
11371141
const uint16_t utf16_ch = (i >= 11) ? pFatDir->name3[i - 11] : (i >= 5) ? pFatDir->name2[i - 5] : pFatDir->name1[i];
11381142
#if ENABLED(UTF_FILENAME_SUPPORT)
11391143
// We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks
11401144
// needs static bytes addressing. So here just store full UTF-16LE words to re-convert later.
1141-
uint16_t idx = (startOffset + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding
1145+
const uint16_t idx = (startOffset + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding
11421146
lname[idx] = utf16_ch & 0xFF;
11431147
lname[idx + 1] = (utf16_ch >> 8) & 0xFF;
11441148
#else
@@ -1152,8 +1156,8 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) {
11521156
* Set the LFN filename block lname to a dir. Put the block based on sequence number
11531157
*/
11541158
void SdBaseFile::setLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) {
1155-
uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
1156-
uint8_t nameLength = strlen(lname);
1159+
const uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH,
1160+
nameLength = strlen(lname);
11571161
LOOP_L_N(i, FILENAME_LENGTH) {
11581162
uint16_t ch = 0;
11591163
if ((startOffset + i) < nameLength)
@@ -1424,7 +1428,7 @@ int16_t SdBaseFile::read(void *buf, uint16_t nbyte) {
14241428
* readDir() called before a directory has been opened, this is not
14251429
* a directory file or an I/O error occurred.
14261430
*/
1427-
int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
1431+
int8_t SdBaseFile::readDir(dir_t *dir, char * const longFilename) {
14281432
int16_t n;
14291433
// if not a directory file or miss-positioned return an error
14301434
if (!isDir() || (0x1F & curPosition_)) return -1;
@@ -1506,44 +1510,55 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
15061510
// Post-process normal file or subdirectory longname, if any
15071511
if (DIR_IS_FILE_OR_SUBDIR(dir)) {
15081512
#if ENABLED(UTF_FILENAME_SUPPORT)
1509-
#if LONG_FILENAME_CHARSIZE > 2
1510-
// Add warning for developers for unsupported 3-byte cases.
1511-
// (Converting 2-byte codepoints to 3-byte in-place would break the rest of filename.)
1512-
#error "Currently filename re-encoding is done in-place. It may break the remaining chars to use 3-byte codepoints."
1513-
#endif
1514-
15151513
// Is there a long filename to decode?
15161514
if (longFilename) {
1517-
// Reset n to the start of the long name
1518-
n = 0;
1519-
for (uint16_t idx = 0; idx < (LONG_FILENAME_LENGTH); idx += 2) { // idx is fixed since FAT LFN always contains UTF-16LE encoding
1520-
const uint16_t utf16_ch = longFilename[idx] | (longFilename[idx + 1] << 8);
1521-
if (0xD800 == (utf16_ch & 0xF800)) // Surrogate pair - encode as '_'
1522-
longFilename[n++] = '_';
1523-
else if (0 == (utf16_ch & 0xFF80)) // Encode as 1-byte UTF-8 char
1524-
longFilename[n++] = utf16_ch & 0x007F;
1525-
else if (0 == (utf16_ch & 0xF800)) { // Encode as 2-byte UTF-8 char
1526-
longFilename[n++] = 0xC0 | ((utf16_ch >> 6) & 0x1F);
1527-
longFilename[n++] = 0x80 | ( utf16_ch & 0x3F);
1528-
}
1529-
else {
1530-
#if LONG_FILENAME_CHARSIZE > 2 // Encode as 3-byte UTF-8 char
1531-
longFilename[n++] = 0xE0 | ((utf16_ch >> 12) & 0x0F);
1532-
longFilename[n++] = 0xC0 | ((utf16_ch >> 6) & 0x3F);
1533-
longFilename[n++] = 0xC0 | ( utf16_ch & 0x3F);
1534-
#else // Encode as '_'
1535-
longFilename[n++] = '_';
1536-
#endif
1537-
}
1538-
if (0 == utf16_ch) break; // End of filename
1539-
} // idx
1540-
} // longFilename
1515+
n = convertUtf16ToUtf8(longFilename);
1516+
}
15411517
#endif
15421518
return n;
15431519
} // DIR_IS_FILE_OR_SUBDIR
15441520
}
15451521
}
15461522

1523+
#if ENABLED(UTF_FILENAME_SUPPORT)
1524+
1525+
uint8_t SdBaseFile::convertUtf16ToUtf8(char * const longFilename) {
1526+
#if LONG_FILENAME_CHARSIZE > 2
1527+
// Add warning for developers for unsupported 3-byte cases.
1528+
// (Converting 2-byte codepoints to 3-byte in-place would break the rest of filename.)
1529+
#error "Currently filename re-encoding is done in-place. It may break the remaining chars to use 3-byte codepoints."
1530+
#endif
1531+
1532+
int16_t n;
1533+
// Reset n to the start of the long name
1534+
n = 0;
1535+
for (uint16_t idx = 0; idx < (LONG_FILENAME_LENGTH); idx += 2) { // idx is fixed since FAT LFN always contains UTF-16LE encoding
1536+
const uint16_t utf16_ch = longFilename[idx] | (longFilename[idx + 1] << 8);
1537+
if (0xD800 == (utf16_ch & 0xF800)) // Surrogate pair - encode as '_'
1538+
longFilename[n++] = '_';
1539+
else if (0 == (utf16_ch & 0xFF80)) // Encode as 1-byte UTF-8 char
1540+
longFilename[n++] = utf16_ch & 0x007F;
1541+
else if (0 == (utf16_ch & 0xF800)) { // Encode as 2-byte UTF-8 char
1542+
longFilename[n++] = 0xC0 | ((utf16_ch >> 6) & 0x1F);
1543+
longFilename[n++] = 0x80 | ( utf16_ch & 0x3F);
1544+
}
1545+
else {
1546+
#if LONG_FILENAME_CHARSIZE > 2 // Encode as 3-byte UTF-8 char
1547+
longFilename[n++] = 0xE0 | ((utf16_ch >> 12) & 0x0F);
1548+
longFilename[n++] = 0xC0 | ((utf16_ch >> 6) & 0x3F);
1549+
longFilename[n++] = 0xC0 | ( utf16_ch & 0x3F);
1550+
#else // Encode as '_'
1551+
longFilename[n++] = '_';
1552+
#endif
1553+
}
1554+
if (0 == utf16_ch) break; // End of filename
1555+
} // idx
1556+
1557+
return n;
1558+
}
1559+
1560+
#endif // UTF_FILENAME_SUPPORT
1561+
15471562
// Read next directory entry into the cache
15481563
// Assumes file is correctly positioned
15491564
dir_t* SdBaseFile::readDirCache() {

Marlin/src/sd/SdBaseFile.h

+6-4
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ class SdBaseFile {
298298
bool printName();
299299
int16_t read();
300300
int16_t read(void *buf, uint16_t nbyte);
301-
int8_t readDir(dir_t *dir, char *longFilename);
301+
int8_t readDir(dir_t *dir, char * const longFilename);
302302
static bool remove(SdBaseFile *dirFile, const char *path);
303303
bool remove();
304304

@@ -392,14 +392,16 @@ class SdBaseFile {
392392
bool openCachedEntry(uint8_t cacheIndex, uint8_t oflags);
393393
dir_t* readDirCache();
394394

395+
#if ENABLED(UTF_FILENAME_SUPPORT)
396+
uint8_t convertUtf16ToUtf8(char * const longFilename);
397+
#endif
398+
395399
// Long Filename create/write support
396400
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
397401
static bool isDirLFN(const dir_t* dir);
398402
static bool isDirNameLFN(const char *dirname);
399403
static bool parsePath(const char *str, uint8_t *name, uint8_t *lname, const char **ptr);
400-
/**
401-
* Return the number of entries needed in the FAT for this LFN
402-
*/
404+
// Return the number of entries needed in the FAT for this LFN
403405
static inline uint8_t getLFNEntriesNum(const char *lname) { return (strlen(lname) + 12) / 13; }
404406
static void getLFNName(vfat_t *vFatDir, char *lname, uint8_t startOffset);
405407
static void setLFNName(vfat_t *vFatDir, char *lname, uint8_t lfnSequenceNumber);

0 commit comments

Comments
 (0)