diff --git a/Marlin/src/sd/SdBaseFile.cpp b/Marlin/src/sd/SdBaseFile.cpp index ac73a5a80..b357495a3 100644 --- a/Marlin/src/sd/SdBaseFile.cpp +++ b/Marlin/src/sd/SdBaseFile.cpp @@ -1049,6 +1049,20 @@ int16_t SdBaseFile::read(void *buf, uint16_t nbyte) { return nbyte; } +/** + * Calculate a checksum for an 8.3 filename + * + * \param name The 8.3 file name to calculate + * + * \return The checksum byte + */ +uint8_t lfn_checksum(const uint8_t *name) { + uint8_t sum = 0; + for (uint8_t i = 11; i; i--) + sum = ((sum & 1) << 7) + (sum >> 1) + *name++; + return sum; +} + /** * Read the next entry in a directory. * @@ -1065,51 +1079,68 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) { // if not a directory file or miss-positioned return an error if (!isDir() || (0x1F & curPosition_)) return -1; + #define INVALIDATE_LONGNAME() (longFilename[0] = longFilename[1] = '\0') + // If we have a longFilename buffer, mark it as invalid. // If a long filename is found it will be filled automatically. - if (longFilename) { longFilename[0] = '\0'; longFilename[1] = '\0'; } + if (longFilename) INVALIDATE_LONGNAME(); + + uint8_t checksum_error = 0xFF, checksum = 0; while (1) { n = read(dir, sizeof(dir_t)); if (n != sizeof(dir_t)) return n ? -1 : 0; - // last entry if DIR_NAME_FREE + // Last entry if DIR_NAME_FREE if (dir->name[0] == DIR_NAME_FREE) return 0; - // skip deleted entry and entry for . and .. + // Skip deleted entry and entry for . and .. if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { - if (longFilename) { longFilename[0] = '\0'; longFilename[1] = '\0'; } // Invalidate erased file long name, if any + if (longFilename) INVALIDATE_LONGNAME(); // Invalidate erased file long name, if any continue; } - // Fill the long filename if we have a long filename entry. - // Long filename entries are stored before the short filename. - if (longFilename && DIR_IS_LONG_NAME(dir)) { - vfat_t *VFAT = (vfat_t*)dir; - // Sanity-check the VFAT entry. The first cluster is always set to zero. And the sequence number should be higher than 0 - if (VFAT->firstClusterLow == 0) { - const uint8_t seq = VFAT->sequenceNumber & 0x1F; - if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) { - // TODO: Store the filename checksum to verify if a long-filename-unaware system modified the file table. - n = (seq - 1) * (FILENAME_LENGTH); - LOOP_L_N(i, FILENAME_LENGTH) { - uint16_t utf16_ch = (i < 5) ? VFAT->name1[i] : (i < 11) ? VFAT->name2[i - 5] : VFAT->name3[i - 11]; - #if ENABLED(UTF_FILENAME_SUPPORT) - // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks - // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later. - uint16_t idx = (n + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding - longFilename[idx] = utf16_ch & 0xFF; - longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF; - #else - // Replace all multibyte characters to '_' - longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF); - #endif + if (longFilename) { + // Fill the long filename if we have a long filename entry. + // Long filename entries are stored before the short filename. + if (DIR_IS_LONG_NAME(dir)) { + vfat_t *VFAT = (vfat_t*)dir; + // Sanity-check the VFAT entry. The first cluster is always set to zero. And the sequence number should be higher than 0 + if (VFAT->firstClusterLow == 0) { + const uint8_t seq = VFAT->sequenceNumber & 0x1F; + if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) { + n = (seq - 1) * (FILENAME_LENGTH); + if (n == 0) { + checksum = VFAT->checksum; + checksum_error = 0; + } + else if (checksum != VFAT->checksum) // orphan detected + checksum_error = 1; + + LOOP_L_N(i, FILENAME_LENGTH) { + const uint16_t utf16_ch = (i >= 11) ? VFAT->name3[i - 11] : (i >= 5) ? VFAT->name2[i - 5] : VFAT->name1[i]; + #if ENABLED(UTF_FILENAME_SUPPORT) + // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks + // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later. + uint16_t idx = (n + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding + longFilename[idx] = utf16_ch & 0xFF; + longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF; + #else + // Replace all multibyte characters to '_' + longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF); + #endif + } + // If this VFAT entry is the last one, add a NUL terminator at the end of the string + if (VFAT->sequenceNumber & 0x40) + longFilename[(n + FILENAME_LENGTH) * LONG_FILENAME_CHARSIZE] = '\0'; } - // If this VFAT entry is the last one, add a NUL terminator at the end of the string - if (VFAT->sequenceNumber & 0x40) longFilename[(n + FILENAME_LENGTH) * LONG_FILENAME_CHARSIZE] = '\0'; } } + else { + if (!checksum_error && lfn_checksum(dir->name) != checksum) checksum_error = 1; // orphan detected + if (checksum_error) INVALIDATE_LONGNAME(); + } } // Post-process normal file or subdirectory longname, if any