From e704de9bb05ac57011ac80700f7bd97c9d45eaff Mon Sep 17 00:00:00 2001
From: GHGiampy <83699429+GHGiampy@users.noreply.github.com>
Date: Tue, 18 Jan 2022 07:56:11 +0100
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Long=20filename=20open/create/write?=
 =?UTF-8?q?=20(#23526)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Scott Lahteine <github@thinkyhead.com>
---
 Marlin/Configuration_adv.h            |  28 +-
 Marlin/src/gcode/eeprom/M500-M504.cpp |   2 +
 Marlin/src/gcode/host/M115.cpp        |   8 +-
 Marlin/src/sd/SdBaseFile.cpp          | 584 ++++++++++++++++++++++----
 Marlin/src/sd/SdBaseFile.h            |  22 +-
 Marlin/src/sd/cardreader.cpp          |   6 +-
 buildroot/share/scripts/upload.py     |  78 ++--
 buildroot/tests/mega2560              |   2 +-
 8 files changed, 582 insertions(+), 148 deletions(-)

diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 0bcd41fd52..ba396431c9 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -1525,33 +1525,23 @@
   // LCD's font must contain the characters. Check your selected LCD language.
   //#define UTF_FILENAME_SUPPORT
 
-  // This allows hosts to request long names for files and folders with M33
-  //#define LONG_FILENAME_HOST_SUPPORT
+  //#define LONG_FILENAME_HOST_SUPPORT    // Get the long filename of a file/folder with 'M33 <dosname>' and list long filenames with 'M20 L'
+  //#define LONG_FILENAME_WRITE_SUPPORT   // Create / delete files with long filenames via M28, M30, and Binary Transfer Protocol
 
-  // Enable this option to scroll long filenames in the SD card menu
-  //#define SCROLL_LONG_FILENAMES
+  //#define SCROLL_LONG_FILENAMES         // Scroll long filenames in the SD card menu
 
-  // Leave the heaters on after Stop Print (not recommended!)
-  //#define SD_ABORT_NO_COOLDOWN
+  //#define SD_ABORT_NO_COOLDOWN          // Leave the heaters on after Stop Print (not recommended!)
 
   /**
-   * This option allows you to abort SD printing when any endstop is triggered.
-   * This feature must be enabled with "M540 S1" or from the LCD menu.
-   * To have any effect, endstops must be enabled during SD printing.
+   * Abort SD printing when any endstop is triggered.
+   * This feature is enabled with 'M540 S1' or from the LCD menu.
+   * Endstops must be activated for this option to work.
    */
   //#define SD_ABORT_ON_ENDSTOP_HIT
 
-  /**
-   * This option makes it easier to print the same SD Card file again.
-   * On print completion the LCD Menu will open with the file selected.
-   * You can just click to start the print, or navigate elsewhere.
-   */
-  //#define SD_REPRINT_LAST_SELECTED_FILE
+  //#define SD_REPRINT_LAST_SELECTED_FILE // On print completion open the LCD Menu and select the same file
 
-  /**
-   * Auto-report SdCard status with M27 S<seconds>
-   */
-  //#define AUTO_REPORT_SD_STATUS
+  //#define AUTO_REPORT_SD_STATUS         // Auto-report media status with 'M27 S<seconds>'
 
   /**
    * Support for USB thumb drives using an Arduino USB Host Shield or
diff --git a/Marlin/src/gcode/eeprom/M500-M504.cpp b/Marlin/src/gcode/eeprom/M500-M504.cpp
index a1f295ebde..412d003355 100644
--- a/Marlin/src/gcode/eeprom/M500-M504.cpp
+++ b/Marlin/src/gcode/eeprom/M500-M504.cpp
@@ -56,6 +56,8 @@ void GcodeSuite::M502() {
   /**
    * M503: print settings currently in memory
    *
+   *   S<bool> : Include / exclude header comments in the output. (Default: S1)
+   *
    * With CONFIGURATION_EMBEDDING:
    *   C<flag> : Save the full Marlin configuration to SD Card as "mc.zip"
    */
diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp
index 08943ed5f2..45e0061a5b 100644
--- a/Marlin/src/gcode/host/M115.cpp
+++ b/Marlin/src/gcode/host/M115.cpp
@@ -154,6 +154,12 @@ void GcodeSuite::M115() {
     // LONG_FILENAME_HOST_SUPPORT (M33)
     cap_line(F("LONG_FILENAME"), ENABLED(LONG_FILENAME_HOST_SUPPORT));
 
+    // LONG_FILENAME_WRITE_SUPPORT (M23, M28, M30...)
+    cap_line(F("LFN_WRITE"), ENABLED(LONG_FILENAME_WRITE_SUPPORT));
+
+    // CUSTOM_FIRMWARE_UPLOAD (M20 F)
+    cap_line(F("CUSTOM_FIRMWARE_UPLOAD"), ENABLED(CUSTOM_FIRMWARE_UPLOAD));
+
     // EXTENDED_M20 (M20 L)
     cap_line(F("EXTENDED_M20"), ENABLED(LONG_FILENAME_HOST_SUPPORT));
 
@@ -179,7 +185,7 @@ void GcodeSuite::M115() {
     cap_line(F("MEATPACK"), SERIAL_IMPL.has_feature(port, SerialFeature::MeatPack));
 
     // CONFIG_EXPORT
-    cap_line(F("CONFIG_EXPORT"), ENABLED(CONFIG_EMBED_AND_SAVE_TO_SD));
+    cap_line(F("CONFIG_EXPORT"), ENABLED(CONFIGURATION_EMBEDDING));
 
     // Machine Geometry
     #if ENABLED(M115_GEOMETRY_REPORT)
diff --git a/Marlin/src/sd/SdBaseFile.cpp b/Marlin/src/sd/SdBaseFile.cpp
index b357495a3e..64d0ad68bd 100644
--- a/Marlin/src/sd/SdBaseFile.cpp
+++ b/Marlin/src/sd/SdBaseFile.cpp
@@ -89,6 +89,7 @@ bool SdBaseFile::addDirCluster() {
 }
 
 // cache a file's directory entry
+// cache the current "dirBlock_" and return the entry at index "dirIndex_"
 // return pointer to cached entry or null for failure
 dir_t* SdBaseFile::cacheDirEntry(uint8_t action) {
   if (!vol_->cacheRawBlock(dirBlock_, action)) return nullptr;
@@ -384,6 +385,20 @@ int8_t SdBaseFile::lsPrintNext(uint8_t flags, uint8_t indent) {
   return DIR_IS_FILE(&dir) ? 1 : 2;
 }
 
+/**
+ * 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;
+}
+
 // Format directory name field from a 8.3 name string
 bool SdBaseFile::make83Name(const char *str, uint8_t *name, const char **ptr) {
   uint8_t n = 7,                      // Max index until a dot is found
@@ -430,6 +445,10 @@ bool SdBaseFile::mkdir(SdBaseFile *parent, const char *path, bool pFlag) {
   SdBaseFile *sub = &dir1;
   SdBaseFile *start = parent;
 
+  #if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
+    uint8_t dlname[LONG_FILENAME_LENGTH];
+  #endif
+
   if (!parent || isOpen()) return false;
 
   if (*path == '/') {
@@ -439,28 +458,31 @@ bool SdBaseFile::mkdir(SdBaseFile *parent, const char *path, bool pFlag) {
       parent = &dir2;
     }
   }
-  while (1) {
-    if (!make83Name(path, dname, &path)) return false;
+
+  for (;;) {
+    if (!TERN(LONG_FILENAME_WRITE_SUPPORT, parsePath(path, dname, dlname, &path), make83Name(path, dname, &path))) return false;
     while (*path == '/') path++;
     if (!*path) break;
-    if (!sub->open(parent, dname, O_READ)) {
-      if (!pFlag || !sub->mkdir(parent, dname))
+    if (!sub->open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), O_READ)) {
+      if (!pFlag || !sub->mkdir(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname)))
         return false;
     }
     if (parent != start) parent->close();
     parent = sub;
     sub = parent != &dir1 ? &dir1 : &dir2;
   }
-  return mkdir(parent, dname);
+  return mkdir(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname));
 }
 
-bool SdBaseFile::mkdir(SdBaseFile *parent, const uint8_t dname[11]) {
+bool SdBaseFile::mkdir(SdBaseFile *parent, const uint8_t dname[11]
+  OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH])
+) {
   if (ENABLED(SDCARD_READONLY)) return false;
 
   if (!parent->isDir()) return false;
 
   // create a normal file
-  if (!open(parent, dname, O_CREAT | O_EXCL | O_RDWR)) return false;
+  if (!open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), O_CREAT | O_EXCL | O_RDWR)) return false;
 
   // convert file to directory
   flags_ = O_READ;
@@ -578,6 +600,10 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) {
   SdBaseFile dir1, dir2;
   SdBaseFile *parent = dirFile, *sub = &dir1;
 
+  #if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
+    uint8_t dlname[LONG_FILENAME_LENGTH];
+  #endif
+
   if (!dirFile || isOpen()) return false;
 
   if (*path == '/') {                                         // Path starts with '/'
@@ -589,90 +615,244 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) {
   }
 
   for (;;) {
-    if (!make83Name(path, dname, &path)) return false;
+    if (!TERN(LONG_FILENAME_WRITE_SUPPORT, parsePath(path, dname, dlname, &path), make83Name(path, dname, &path))) return false;
     while (*path == '/') path++;
     if (!*path) break;
-    if (!sub->open(parent, dname, O_READ)) return false;
+    if (TERN0(LONG_FILENAME_WRITE_SUPPORT, !sub->open(parent, dname, dlname, O_READ))) return false;
     if (parent != dirFile) parent->close();
     parent = sub;
     sub = parent != &dir1 ? &dir1 : &dir2;
   }
-  return open(parent, dname, oflag);
+  return open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), oflag);
 }
 
-// open with filename in dname
-bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], uint8_t oflag) {
+// open with filename in dname and long filename in dlname
+bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11]
+    OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH])
+  , uint8_t oflag
+) {
   bool emptyFound = false, fileFound = false;
-  uint8_t index;
+  uint8_t index = 0;
   dir_t *p;
 
+  #if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
+    // LFN - Long File Name support
+    const bool useLFN = dlname[0] != 0;
+    bool lfnFileFound = false;
+    vfat_t *pvFat;
+    uint8_t emptyCount = 0,
+            emptyIndex = 0,
+            reqEntriesNum = useLFN ? getLFNEntriesNum((char*)dlname) + 1 : 1,
+            lfnNameLength = useLFN ? strlen((char*)dlname) : 0,
+            lfnName[LONG_FILENAME_LENGTH],
+            lfnSequenceNumber = 0,
+            lfnChecksum = 0;
+  #endif
+
+  // Rewind this dir
   vol_ = dirFile->vol_;
-
   dirFile->rewind();
+
   // search for file
-
   while (dirFile->curPosition_ < dirFile->fileSize_) {
-    index = 0xF & (dirFile->curPosition_ >> 5);
-    p = dirFile->readDirCache();
-    if (!p) return false;
+    // Get absolute index position
+    index = (dirFile->curPosition_ >> 5) IF_DISABLED(LONG_FILENAME_WRITE_SUPPORT, & 0x0F);
 
+    // Get next entry
+    if (!(p = dirFile->readDirCache())) return false;
+
+    // Check empty status: Is entry empty?
     if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) {
-      // remember first empty slot
+      // Count the contiguous available entries in which (eventually) fit the new dir entry, if it's a write operation
       if (!emptyFound) {
-        dirBlock_ = dirFile->vol_->cacheBlockNumber();
-        dirIndex_ = index;
-        emptyFound = true;
+        #if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
+          if (emptyCount == 0) emptyIndex = index;
+          // Incr empty entries counter
+          // If found the required empty entries, mark it
+          if (++emptyCount == reqEntriesNum) {
+            dirBlock_ = dirFile->vol_->cacheBlockNumber();
+            dirIndex_ = index & 0xF;
+            emptyFound = true;
+          }
+        #else
+          dirBlock_ = dirFile->vol_->cacheBlockNumber();
+          dirIndex_ = index;
+          emptyFound = true;
+        #endif
       }
-      // done if no entries follow
+      // Done if no entries follow
       if (p->name[0] == DIR_NAME_FREE) break;
     }
-    else if (!memcmp(dname, p->name, 11)) {
-      fileFound = true;
-      break;
+    else {  // Entry not empty
+      #if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
+        // Reset empty counter
+        if (!emptyFound) emptyCount = 0;
+        // Search for SFN or LFN?
+        if (!useLFN) {
+          // Check using SFN: file found?
+          if (!memcmp(dname, p->name, 11)) {
+            fileFound = true;
+            break;
+          }
+        }
+        else {
+          // Check using LFN: LFN not found? continue search for LFN
+          if (!lfnFileFound) {
+            // Is this dir a LFN?
+            if (isDirLFN(p)) {
+              // Get VFat dir entry
+              pvFat = (vfat_t *) p;
+              // Get checksum from the last entry of the sequence
+              if (pvFat->sequenceNumber & 0x40) lfnChecksum = pvFat->checksum;
+              // Get LFN sequence number
+              lfnSequenceNumber = pvFat->sequenceNumber & 0x1F;
+              if WITHIN(lfnSequenceNumber, 1, reqEntriesNum) {
+                // Check checksum for all other entries with the starting checksum fetched before
+                if (lfnChecksum == pvFat->checksum) {
+                  // Set chunk of LFN from VFAT entry into lfnName
+                  getLFNName(pvFat, (char *)lfnName, lfnSequenceNumber);
+                  // LFN found?
+                  if (!strncasecmp((char*)dlname, (char*)lfnName, lfnNameLength)) lfnFileFound = true;
+                }
+              }
+            }
+          }
+          else {    // Complete LFN found, check for related SFN
+            // Check if only the SFN checksum match because the filename may be different due to different truncation methods
+            if (!isDirLFN(p) && (lfnChecksum == lfn_checksum(p->name))) {
+              fileFound = true;
+              break;
+            }
+            else lfnFileFound = false;    // SFN not valid for the LFN found, reset LFN FileFound
+          }
+        }
+      #else
+
+        if (!memcmp(dname, p->name, 11)) {
+          fileFound = true;
+          break;
+        }
+
+      #endif // LONG_FILENAME_WRITE_SUPPORT
     }
   }
+
   if (fileFound) {
     // don't open existing file if O_EXCL
     if (oflag & O_EXCL) return false;
+    TERN_(LONG_FILENAME_WRITE_SUPPORT, index &= 0xF);
   }
   else {
     // don't create unless O_CREAT and O_WRITE
     if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) return false;
-    if (emptyFound) {
-      index = dirIndex_;
-      p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
-      if (!p) return false;
-    }
-    else {
-      if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false;
 
-      // add and zero cluster for dirFile - first cluster is in cache for write
-      if (!dirFile->addDirCluster()) return false;
+    #if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
 
-      // use first entry in cluster
-      p = dirFile->vol_->cache()->dir;
-      index = 0;
-    }
-    // initialize as empty file
-    memset(p, 0, sizeof(*p));
-    memcpy(p->name, dname, 11);
+      // Use bookmark index if found empty entries
+      if (emptyFound) index = emptyIndex;
 
-    // set timestamps
-    if (dateTime_) {
-      // call user date/time function
-      dateTime_(&p->creationDate, &p->creationTime);
-    }
-    else {
-      // use default date/time
-      p->creationDate = FAT_DEFAULT_DATE;
-      p->creationTime = FAT_DEFAULT_TIME;
-    }
-    p->lastAccessDate = p->creationDate;
-    p->lastWriteDate = p->creationDate;
-    p->lastWriteTime = p->creationTime;
+      // Make room for needed entries
+      while (emptyCount < reqEntriesNum) {
+        p = dirFile->readDirCache();
+        if (!p) break;
+        emptyCount++;
+      }
+      while (emptyCount < reqEntriesNum) {
+        if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false;
+        // add and zero cluster for dirFile - first cluster is in cache for write
+        if (!dirFile->addDirCluster()) return false;
+        emptyCount += dirFile->vol_->blocksPerCluster() * 16;
+      }
+
+      // Move to 1st entry to write
+      if (!dirFile->seekSet(32 * index)) return false;
+
+      // Dir entries write loop: [LFN] + SFN(1)
+      LOOP_L_N(dirWriteIdx, reqEntriesNum) {
+        index = (dirFile->curPosition_ / 32) & 0xF;
+        p = dirFile->readDirCache();
+        // LFN or SFN Entry?
+        if (dirWriteIdx < reqEntriesNum - 1) {
+          // Write LFN Entries
+          pvFat = (vfat_t *) p;
+          // initialize as empty file
+          memset(pvFat, 0, sizeof(*pvFat));
+          lfnSequenceNumber = (reqEntriesNum - dirWriteIdx - 1) & 0x1F;
+          pvFat->attributes = DIR_ATT_LONG_NAME;
+          pvFat->checksum = lfn_checksum(dname);
+          // Set sequence number and mark as last LFN entry if it's the 1st loop
+          pvFat->sequenceNumber = lfnSequenceNumber | (dirWriteIdx == 0 ? 0x40 : 0);
+          // Set LFN name block
+          setLFNName(pvFat, (char*)dlname, lfnSequenceNumber);
+        }
+        else {
+          // Write SFN Entry
+          // initialize as empty file
+          memset(p, 0, sizeof(*p));
+          memcpy(p->name, dname, 11);
+
+          // set timestamps
+          if (dateTime_) {
+            // call user date/time function
+            dateTime_(&p->creationDate, &p->creationTime);
+          }
+          else {
+            // use default date/time
+            p->creationDate = FAT_DEFAULT_DATE;
+            p->creationTime = FAT_DEFAULT_TIME;
+          }
+          p->lastAccessDate = p->creationDate;
+          p->lastWriteDate = p->creationDate;
+          p->lastWriteTime = p->creationTime;
+        }
+
+        // write entry to SD
+        dirFile->vol_->cacheSetDirty();
+        if (!dirFile->vol_->cacheFlush()) return false;
+      }
+
+    #else // !LONG_FILENAME_WRITE_SUPPORT
+
+      if (emptyFound) {
+        index = dirIndex_;
+        p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
+        if (!p) return false;
+      }
+      else {
+        if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false;
+
+        // add and zero cluster for dirFile - first cluster is in cache for write
+        if (!dirFile->addDirCluster()) return false;
+
+        // use first entry in cluster
+        p = dirFile->vol_->cache()->dir;
+        index = 0;
+      }
+
+      // initialize as empty file
+      memset(p, 0, sizeof(*p));
+      memcpy(p->name, dname, 11);
+
+      // set timestamps
+      if (dateTime_) {
+        // call user date/time function
+        dateTime_(&p->creationDate, &p->creationTime);
+      }
+      else {
+        // use default date/time
+        p->creationDate = FAT_DEFAULT_DATE;
+        p->creationTime = FAT_DEFAULT_TIME;
+      }
+
+      p->lastAccessDate = p->creationDate;
+      p->lastWriteDate = p->creationDate;
+      p->lastWriteTime = p->creationTime;
+
+      // write entry to SD
+      if (!dirFile->vol_->cacheFlush()) return false;
+
+    #endif // !LONG_FILENAME_WRITE_SUPPORT
 
-    // write entry to SD
-    if (!dirFile->vol_->cacheFlush()) return false;
   }
   // open entry in cache
   return openCachedEntry(index, oflag);
@@ -808,6 +988,191 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) {
   return false;
 }
 
+#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
+
+  /**
+   * Check if dir is a long file name entry (LFN)
+   *
+   * \param[in] dir Parent of this directory will be opened.  Must not be root.
+   * \return true if the dir is a long file name entry (LFN)
+   */
+  bool SdBaseFile::isDirLFN(const dir_t* dir) {
+    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) && WITHIN((VFAT->sequenceNumber & 0x1F), 1, MAX_VFAT_ENTRIES)) return true;
+    }
+    return false;
+  }
+
+  /**
+   * Check if dirname string is a long file name (LFN)
+   *
+   * \param[in] dirname The string to check
+   * \return true if the dirname is a long file name (LFN)
+   * \return false if the dirname is a short file name 8.3 (SFN)
+   */
+  bool SdBaseFile::isDirNameLFN(const char *dirname) {
+    uint8_t length = strlen(dirname);
+    uint8_t idx = length;
+    bool dotFound = false;
+    if (idx > 12) return true;            // LFN due to filename length > 12 ("filename.ext")
+    // Check dot(s) position
+    while (idx) {
+      if (dirname[--idx] == '.') {
+        if (!dotFound) {
+          // Last dot (extension) is allowed only
+          // in position [1..8] from start or [0..3] from end for SFN else it's a LFN
+          // A filename starting with "." is a LFN                (eg. ".file" ->in SFN-> "file~1     ")
+          // A filename ending with "." is a SFN (if length <= 9) (eg. "file." ->in SFN-> "file       ")
+          if (idx > 8 || idx == 0 || (length - idx - 1) > 3) return true;   // LFN due to dot extension position
+          dotFound = true;
+        }
+        else {
+          // Found another dot, is a LFN
+          return true;
+        }
+      }
+    }
+    // If no dots found, the filename must be of max 8 characters
+    if ((!dotFound) && length > 8) return true;         // LFN due to max filename (without extension) length
+    return false;
+  }
+
+  /**
+   * Parse path and return 8.3 format and LFN filenames (if the parsed path is a LFN)
+   * The SFN is without dot ("FILENAMEEXT")
+   * The LFN is complete ("Filename.ext")
+   */
+  bool SdBaseFile::parsePath(const char *path, uint8_t *name, uint8_t *lname, const char **ptrNextPath) {
+    // Init randomizer for SFN generation
+    randomSeed(millis());
+    // Parse the LFN
+    uint8_t ilfn = 0;
+    bool lastDotFound = false;
+    const char *pLastDot = 0;
+    const char *lfnpath = path;
+    uint8_t c;
+
+    while (*lfnpath && *lfnpath != '/') {
+      if (ilfn == LONG_FILENAME_LENGTH - 1) return false;                 // Name too long
+      c = *lfnpath++;                                                     // Get char and advance
+      // Fail for illegal characters
+      PGM_P p = PSTR("|<>^+=?/[];:,*\"\\");
+      while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false;    // Check reserved characters
+      if (c < 0x20 || c == 0x7F) return false;                            // Check non-printable characters
+      if (c == '.' && (lfnpath - 1) > path) {                             // Skip dot '.' check in 1st position
+        // Save last dot pointer (skip if starts with '.')
+        pLastDot = lfnpath - 1;
+        lastDotFound = true;
+      }
+      lname[ilfn++] = c;  // Set LFN character
+    }
+    // Terminate LFN
+    lname[ilfn] = 0;
+
+    // Parse/generate 8.3 SFN. Will take
+    // until 8 characters for the filename part
+    // until 3 characters for the extension part (if exists)
+    // Add 4 more characters if name part < 3
+    // Add '~cnt' characters if it's a LFN
+    const bool isLFN = isDirNameLFN((char*)lname);
+
+    uint8_t n = isLFN ? 5 : 7,  // Max index for each component of the file:
+                                // starting with 7 or 5 (if LFN)
+                                // switch to 10 for extension if the last dot is found
+            i = 11;
+    while (i) name[--i] = ' ';            // Set whole FILENAMEEXT to spaces
+    while (*path && *path != '/') {
+      c = *path++;                                                        // Get char and advance
+      // Skip spaces and dots (if it's not the last dot)
+      if (c == ' ') continue;
+      if (c == '.' && (!lastDotFound || (lastDotFound && path < pLastDot))) continue;
+      // Fail for illegal characters
+      PGM_P p = PSTR("|<>^+=?/[];:,*\"\\");
+      while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false;    // Check reserved characters
+      if (c < 0x21 || c == 0x7F) return false;                            // Check non-printable characters
+      // Is last dot?
+      if (c == '.') {
+        // Switch to extension part
+        n = 10;
+        i = 8;
+      }
+      // If in valid range add the character
+      else if (i <= n)                                          // Check size for 8.3 format
+        name[i++] = c + (WITHIN(c, 'a', 'z') ? 'A' - 'a' : 0);  // Uppercase required for 8.3 name
+    }
+    // If it's a LFN then the SFN always need:
+    // - A minimal of 3 characters (otherwise 4 chars are added)
+    // - The '~cnt' at the end
+    if (isLFN) {
+      // Get the 1st free character
+      uint8_t iFree = 0;
+      while (1) if (name[iFree++] == ' ' || iFree == 11) break;
+      iFree--;
+      // Check minimal length
+      if (iFree < 3) {
+        // Append 4 extra characters
+        name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(0,24) + 'A';
+        name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(0,24) + 'A';
+      }
+      // Append '~cnt' characters
+      if (iFree > 5) iFree = 5; // Force the append in the last 3 characters of name part
+      name[iFree++] = '~';
+      name[iFree++] = random(1,9) + '0';
+      name[iFree++] = random(1,9) + '0';
+    }
+
+    // Check if LFN is needed
+    if (!isLFN) lname[0] = 0;   // Zero LFN
+    *ptrNextPath = path;        // Set passed pointer to the end
+    return name[0] != ' ';      // Return true if any name was set
+  }
+
+  /**
+   * Get the LFN filename block from a dir. Get the block in lname at startOffset
+   */
+  void SdBaseFile::getLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) {
+    uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
+    LOOP_L_N(i, FILENAME_LENGTH) {
+      const uint16_t utf16_ch = (i >= 11) ? pFatDir->name3[i - 11] : (i >= 5) ? pFatDir->name2[i - 5] : pFatDir->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 = (startOffset + 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 '_'
+        lname[startOffset + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF);
+      #endif
+    }
+  }
+
+  /**
+   * Set the LFN filename block lname to a dir. Put the block based on sequence number
+   */
+  void SdBaseFile::setLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) {
+    uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
+    uint8_t nameLength = strlen(lname);
+    LOOP_L_N(i, FILENAME_LENGTH) {
+      uint16_t ch = 0;
+      if ((startOffset + i) < nameLength)
+        ch = lname[startOffset + i];
+      else if ((startOffset + i) > nameLength)
+        ch = 0xFFFF;
+      // Set char
+      if (i < 5)
+        pFatDir->name1[i] = ch;
+      else if (i < 11)
+        pFatDir->name2[i - 5] = ch;
+      else
+        pFatDir->name3[i - 11] = ch;
+    }
+  }
+
+#endif // LONG_FILENAME_WRITE_SUPPORT
+
 #if 0
 /**
  * Open a directory's parent directory.
@@ -1049,20 +1414,6 @@ 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.
  *
@@ -1110,30 +1461,40 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
         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) {
+            if (seq == 1) {
               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 ENABLED(LONG_FILENAME_WRITE_SUPPORT)
+
+              getLFNName(VFAT, longFilename, seq);  // Get chunk of LFN from VFAT entry
+
+            #else // !LONG_FILENAME_WRITE_SUPPORT
+
+              n = (seq - 1) * (FILENAME_LENGTH);
+
+              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
+              }
+
+            #endif // !LONG_FILENAME_WRITE_SUPPORT
+
             // 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';
+              longFilename[LONG_FILENAME_CHARSIZE * TERN(LONG_FILENAME_WRITE_SUPPORT, seq * FILENAME_LENGTH, (n + FILENAME_LENGTH))] = '\0';
           }
         }
       }
@@ -1227,6 +1588,11 @@ bool SdBaseFile::remove() {
   dir_t *d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
   if (!d) return false;
 
+  #if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
+    // get SFN checksum before name rewrite (needed for LFN deletion)
+    const uint8_t sfn_checksum = lfn_checksum(d->name);
+  #endif
+
   // mark entry deleted
   d->name[0] = DIR_NAME_DELETED;
 
@@ -1234,8 +1600,48 @@ bool SdBaseFile::remove() {
   type_ = FAT_FILE_TYPE_CLOSED;
 
   // write entry to SD
-  return vol_->cacheFlush();
-  return true;
+  #if DISABLED(LONG_FILENAME_WRITE_SUPPORT)
+
+    return vol_->cacheFlush();
+
+  #else // LONG_FILENAME_WRITE_SUPPORT
+
+    flags_ = 0;
+
+    if (!vol_->cacheFlush()) return false;
+
+    // Check if the entry has a LFN
+    bool lastEntry = false;
+    // loop back to search for any LFN entries related to this file
+    LOOP_S_LE_N(sequenceNumber, 1, MAX_VFAT_ENTRIES) {
+      dirIndex_ = (dirIndex_ - 1) & 0xF;
+      if (dirBlock_ == 0) break;
+      if (dirIndex_ == 0xF) dirBlock_--;
+      dir_t *dir = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
+      if (!dir) return false;
+
+      // check for valid LFN: not deleted, not top dirs (".", ".."), must be a LFN
+      if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.' || !isDirLFN(dir)) break;
+      // check coherent LFN: checksum and sequenceNumber must match
+      vfat_t* dirlfn = (vfat_t*) dir;
+      if (dirlfn->checksum != sfn_checksum || (dirlfn->sequenceNumber & 0x1F) != sequenceNumber) break;    // orphan entry
+      // is last entry of LFN ?
+      lastEntry = (dirlfn->sequenceNumber & 0x40);
+      // mark as deleted
+      dirlfn->sequenceNumber = DIR_NAME_DELETED;
+      // Flush to SD
+      if (!vol_->cacheFlush()) return false;
+      // exit on last entry of LFN deleted
+      if (lastEntry) break;
+    }
+
+    // Restore current index
+    //if (!seekSet(32UL * dirIndex_)) return false;
+    //dirIndex_ += prevDirIndex;
+
+    return true;
+
+  #endif // LONG_FILENAME_WRITE_SUPPORT
 }
 
 /**
diff --git a/Marlin/src/sd/SdBaseFile.h b/Marlin/src/sd/SdBaseFile.h
index 342edefb70..bda44c6bd5 100644
--- a/Marlin/src/sd/SdBaseFile.h
+++ b/Marlin/src/sd/SdBaseFile.h
@@ -377,8 +377,26 @@ class SdBaseFile {
   dir_t* cacheDirEntry(uint8_t action);
   int8_t lsPrintNext(uint8_t flags, uint8_t indent);
   static bool make83Name(const char *str, uint8_t *name, const char **ptr);
-  bool mkdir(SdBaseFile *parent, const uint8_t dname[11]);
-  bool open(SdBaseFile *dirFile, const uint8_t dname[11], uint8_t oflag);
+  bool mkdir(SdBaseFile *parent, const uint8_t dname[11]
+    OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH])
+  );
+  bool open(SdBaseFile *dirFile, const uint8_t dname[11]
+      OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH])
+    , uint8_t oflag
+  );
   bool openCachedEntry(uint8_t cacheIndex, uint8_t oflags);
   dir_t* readDirCache();
+
+  // Long Filename create/write support
+  #if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
+    static bool isDirLFN(const dir_t* dir);
+    static bool isDirNameLFN(const char *dirname);
+    static bool parsePath(const char *str, uint8_t *name, uint8_t *lname, const char **ptr);
+    /**
+     * Return the number of entries needed in the FAT for this LFN
+     */
+    static inline uint8_t getLFNEntriesNum(const char *lname) { return (strlen(lname) + 12) / 13; }
+    static void getLFNName(vfat_t *vFatDir, char *lname, uint8_t startOffset);
+    static void setLFNName(vfat_t *vFatDir, char *lname, uint8_t lfnSequenceNumber);
+  #endif
 };
diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp
index 25f9d7d802..8c1d08a460 100644
--- a/Marlin/src/sd/cardreader.cpp
+++ b/Marlin/src/sd/cardreader.cpp
@@ -328,7 +328,7 @@ void CardReader::printListing(
         if (includeLongNames) {
           SERIAL_CHAR(' ');
           if (prependLong) { SERIAL_ECHO(prependLong); SERIAL_CHAR('/'); }
-          SERIAL_ECHO(longFilename[0] ? longFilename : "???");
+          SERIAL_ECHO(longFilename[0] ? longFilename : filename);
         }
       #endif
       SERIAL_EOL();
@@ -385,9 +385,9 @@ void CardReader::ls(
       diveDir.rewind();
       selectByName(diveDir, segment);
 
-      // Print /LongNamePart to serial output
+      // Print /LongNamePart to serial output or the short name if not available
       SERIAL_CHAR('/');
-      SERIAL_ECHO(longFilename[0] ? longFilename : "???");
+      SERIAL_ECHO(longFilename[0] ? longFilename : filename);
 
       // If the filename was printed then that's it
       if (!flag.filenameIsDir) break;
diff --git a/buildroot/share/scripts/upload.py b/buildroot/share/scripts/upload.py
index bfce4ea49d..ceb0db4f10 100644
--- a/buildroot/share/scripts/upload.py
+++ b/buildroot/share/scripts/upload.py
@@ -84,21 +84,26 @@ def Upload(source, target, env):
     #----------------#
     # File functions #
     #----------------#
-    def _GetFirmwareFiles():
+    def _GetFirmwareFiles(UseLongFilenames):
         if Debug: print('Get firmware files...')
-        _Send('M20 F')
+        _Send(f"M20 F{'L' if UseLongFilenames else ''}")
         Responses = _Recv()
         if len(Responses) < 3 or not any('file list' in r for r in Responses):
             raise Exception('Error getting firmware files')
         if Debug: print('OK')
         return Responses
 
-    def _FilterFirmwareFiles(FirmwareList):
+    def _FilterFirmwareFiles(FirmwareList, UseLongFilenames):
         Firmwares = []
         for FWFile in FirmwareList:
-            if not '/' in FWFile and '.BIN' in FWFile:
-                idx = FWFile.index('.BIN')
-                Firmwares.append(FWFile[:idx+4])
+            # For long filenames take the 3rd column of the firmwares list
+            if UseLongFilenames:
+                Space = 0
+                Space = FWFile.find(' ')
+                if Space >= 0: Space = FWFile.find(' ', Space + 1)
+                if Space >= 0: FWFile = FWFile[Space + 1:]
+            if not '/' in FWFile and '.BIN' in FWFile.upper():
+                Firmwares.append(FWFile[:FWFile.upper().index('.BIN') + 4])
         return Firmwares
 
     def _RemoveFirmwareFile(FirmwareFile):
@@ -124,6 +129,8 @@ def Upload(source, target, env):
     marlin_board_info_name = _GetMarlinEnv(MarlinEnv, 'BOARD_INFO_NAME')
     marlin_board_custom_build_flags = _GetMarlinEnv(MarlinEnv, 'BOARD_CUSTOM_BUILD_FLAGS')
     marlin_firmware_bin = _GetMarlinEnv(MarlinEnv, 'FIRMWARE_BIN')
+    marlin_long_filename_host_support = _GetMarlinEnv(MarlinEnv, 'LONG_FILENAME_HOST_SUPPORT') is not None
+    marlin_longname_write = _GetMarlinEnv(MarlinEnv, 'LONG_FILENAME_WRITE_SUPPORT') is not None
     marlin_custom_firmware_upload = _GetMarlinEnv(MarlinEnv, 'CUSTOM_FIRMWARE_UPLOAD') is not None
     marlin_short_build_version = _GetMarlinEnv(MarlinEnv, 'SHORT_BUILD_VERSION')
     marlin_string_config_h_author = _GetMarlinEnv(MarlinEnv, 'STRING_CONFIG_H_AUTHOR')
@@ -148,6 +155,10 @@ def Upload(source, target, env):
     # "upload_delete_old_bins": delete all *.bin files in the root of SD Card
     upload_delete_old_bins = marlin_motherboard in ['BOARD_CREALITY_V4',   'BOARD_CREALITY_V4210', 'BOARD_CREALITY_V423', 'BOARD_CREALITY_V427',
                                                     'BOARD_CREALITY_V431', 'BOARD_CREALITY_V452',  'BOARD_CREALITY_V453', 'BOARD_CREALITY_V24S1']
+    # "upload_random_name": generate a random 8.3 firmware filename to upload
+    upload_random_filename = marlin_motherboard in ['BOARD_CREALITY_V4',   'BOARD_CREALITY_V4210', 'BOARD_CREALITY_V423', 'BOARD_CREALITY_V427',
+                                                    'BOARD_CREALITY_V431', 'BOARD_CREALITY_V452',  'BOARD_CREALITY_V453', 'BOARD_CREALITY_V24S1'] and not marlin_long_filename_host_support
+
     try:
 
         # Start upload job
@@ -156,28 +167,34 @@ def Upload(source, target, env):
         # Dump some debug info
         if Debug:
             print('Upload using:')
-            print('---- Marlin --------------------')
-            print(f' PIOENV                 : {marlin_pioenv}')
-            print(f' SHORT_BUILD_VERSION    : {marlin_short_build_version}')
-            print(f' STRING_CONFIG_H_AUTHOR : {marlin_string_config_h_author}')
-            print(f' MOTHERBOARD            : {marlin_motherboard}')
-            print(f' BOARD_INFO_NAME        : {marlin_board_info_name}')
-            print(f' CUSTOM_BUILD_FLAGS     : {marlin_board_custom_build_flags}')
-            print(f' FIRMWARE_BIN           : {marlin_firmware_bin}')
-            print(f' CUSTOM_FIRMWARE_UPLOAD : {marlin_custom_firmware_upload}')
-            print('---- Upload parameters ---------')
-            print(f' Source      : {upload_firmware_source_name}')
-            print(f' Target      : {upload_firmware_target_name}')
-            print(f' Port        : {upload_port} @ {upload_speed} baudrate')
-            print(f' Timeout     : {upload_timeout}')
-            print(f' Block size  : {upload_blocksize}')
-            print(f' Compression : {upload_compression}')
-            print(f' Error ratio : {upload_error_ratio}')
-            print(f' Test        : {upload_test}')
-            print(f' Reset       : {upload_reset}')
-            print('--------------------------------')
+            print('---- Marlin -----------------------------------')
+            print(f' PIOENV                      : {marlin_pioenv}')
+            print(f' SHORT_BUILD_VERSION         : {marlin_short_build_version}')
+            print(f' STRING_CONFIG_H_AUTHOR      : {marlin_string_config_h_author}')
+            print(f' MOTHERBOARD                 : {marlin_motherboard}')
+            print(f' BOARD_INFO_NAME             : {marlin_board_info_name}')
+            print(f' CUSTOM_BUILD_FLAGS          : {marlin_board_custom_build_flags}')
+            print(f' FIRMWARE_BIN                : {marlin_firmware_bin}')
+            print(f' LONG_FILENAME_HOST_SUPPORT  : {marlin_long_filename_host_support}')
+            print(f' LONG_FILENAME_WRITE_SUPPORT : {marlin_longname_write}')
+            print(f' CUSTOM_FIRMWARE_UPLOAD      : {marlin_custom_firmware_upload}')
+            print('---- Upload parameters ------------------------')
+            print(f' Source                      : {upload_firmware_source_name}')
+            print(f' Target                      : {upload_firmware_target_name}')
+            print(f' Port                        : {upload_port} @ {upload_speed} baudrate')
+            print(f' Timeout                     : {upload_timeout}')
+            print(f' Block size                  : {upload_blocksize}')
+            print(f' Compression                 : {upload_compression}')
+            print(f' Error ratio                 : {upload_error_ratio}')
+            print(f' Test                        : {upload_test}')
+            print(f' Reset                       : {upload_reset}')
+            print('-----------------------------------------------')
 
         # Custom implementations based on board parameters
+        # Generate a new 8.3 random filename
+        if upload_random_filename:
+            upload_firmware_target_name = f"fw-{''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=5))}.BIN"
+            print(f"Board {marlin_motherboard}: Overriding firmware filename to '{upload_firmware_target_name}'")
 
         # Delete all *.bin files on the root of SD Card (if flagged)
         if upload_delete_old_bins:
@@ -185,11 +202,6 @@ def Upload(source, target, env):
             if not marlin_custom_firmware_upload:
                 raise Exception(f"CUSTOM_FIRMWARE_UPLOAD must be enabled in 'Configuration_adv.h' for '{marlin_motherboard}'")
 
-            # Generate a new 8.3 random filename
-            # This board remember the last firmware filename and doesn't allow to flash from that filename
-            upload_firmware_target_name = f"fw-{''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=5))}.BIN"
-            print(f"Board {marlin_motherboard}: Overriding firmware filename to '{upload_firmware_target_name}'")
-
             # Init serial port
             port = serial.Serial(upload_port, baudrate = upload_speed, write_timeout = 0, timeout = 0.1)
             port.reset_input_buffer()
@@ -198,13 +210,13 @@ def Upload(source, target, env):
             _CheckSDCard()
 
             # Get firmware files
-            FirmwareFiles = _GetFirmwareFiles()
+            FirmwareFiles = _GetFirmwareFiles(marlin_long_filename_host_support)
             if Debug:
                 for FirmwareFile in FirmwareFiles:
                     print(f'Found: {FirmwareFile}')
 
             # Get all 1st level firmware files (to remove)
-            OldFirmwareFiles = _FilterFirmwareFiles(FirmwareFiles[1:len(FirmwareFiles)-2])   # Skip header and footers of list
+            OldFirmwareFiles = _FilterFirmwareFiles(FirmwareFiles[1:len(FirmwareFiles)-2], marlin_long_filename_host_support)   # Skip header and footers of list
             if len(OldFirmwareFiles) == 0:
                 print('No old firmware files to delete')
             else:
diff --git a/buildroot/tests/mega2560 b/buildroot/tests/mega2560
index bf3290b9d0..5ae9a2dbcf 100755
--- a/buildroot/tests/mega2560
+++ b/buildroot/tests/mega2560
@@ -35,7 +35,7 @@ opt_set MOTHERBOARD BOARD_AZTEEG_X3_PRO LCD_LANGUAGE jp_kana DEFAULT_EJERK 10 \
         EXTRUDERS 5 TEMP_SENSOR_1 1 TEMP_SENSOR_2 5 TEMP_SENSOR_3 20 TEMP_SENSOR_4 1000 TEMP_SENSOR_BED 1
 opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER LIGHTWEIGHT_UI SHOW_CUSTOM_BOOTSCREEN BOOT_MARLIN_LOGO_SMALL \
            LCD_SET_PROGRESS_MANUALLY PRINT_PROGRESS_SHOW_DECIMALS SHOW_REMAINING_TIME STATUS_MESSAGE_SCROLLING SCROLL_LONG_FILENAMES \
-           SDSUPPORT SDCARD_SORT_ALPHA NO_SD_AUTOSTART USB_FLASH_DRIVE_SUPPORT CANCEL_OBJECTS \
+           SDSUPPORT LONG_FILENAME_WRITE_SUPPORT SDCARD_SORT_ALPHA NO_SD_AUTOSTART USB_FLASH_DRIVE_SUPPORT CANCEL_OBJECTS \
            Z_PROBE_SLED AUTO_BED_LEVELING_UBL UBL_HILBERT_CURVE RESTORE_LEVELING_AFTER_G28 DEBUG_LEVELING_FEATURE G26_MESH_VALIDATION ENABLE_LEVELING_FADE_HEIGHT \
            EEPROM_SETTINGS EEPROM_CHITCHAT GCODE_MACROS CUSTOM_MENU_MAIN \
            MULTI_NOZZLE_DUPLICATION CLASSIC_JERK LIN_ADVANCE QUICK_HOME \