diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 7cda16dc0f..14975ca50a 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -1954,6 +1954,10 @@
#define DEFAULT_SHARED_VOLUME SV_USB_FLASH_DRIVE
#endif
+ // Emulate RepRapFirmware with macro files stored in /sys and /macros
+ // Provide the M98 command to run a macro file as a sub-program
+ //#define MACHINE_COMMAND_MACROS
+
#endif // HAS_MEDIA
/**
diff --git a/Marlin/src/gcode/sdcard/M98.cpp b/Marlin/src/gcode/sdcard/M98.cpp
new file mode 100644
index 0000000000..14fb38307c
--- /dev/null
+++ b/Marlin/src/gcode/sdcard/M98.cpp
@@ -0,0 +1,49 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(MACHINE_COMMAND_MACROS)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M98: Select file and run as sub-procedure
+ *
+ * P - The plain (DOS 8.3) filepath
+ *
+ * Example:
+ * M98 P/macros/home.g ; Run home.g (as a procedure)
+ *
+ */
+void GcodeSuite::M98() {
+ if (card.isMounted() && parser.seen('P')) {
+ char *path = parser.value_string();
+ char *lb = strchr(p, ' ');
+ if (!lb) lb = strchr(p, ';');
+ if (lb) *lb = '\0';
+ card.runMacro(path);
+ }
+}
+
+#endif // MACHINE_COMMAND_MACROS
diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp
index 56daa369fc..55d05360ef 100644
--- a/Marlin/src/sd/cardreader.cpp
+++ b/Marlin/src/sd/cardreader.cpp
@@ -806,6 +806,15 @@ bool CardReader::fileExists(const char * const path) {
return success;
}
+#if ENABLED(MACHINE_COMMAND_MACROS)
+
+ void CardReader::runMacro(const char * const path) {
+ openFileRead(path, 2);
+ startFileprint();
+ }
+
+#endif
+
//
// Delete a file by name in the working directory
//
diff --git a/Marlin/src/sd/cardreader.h b/Marlin/src/sd/cardreader.h
index 1d2ba3ae6f..20456769f1 100644
--- a/Marlin/src/sd/cardreader.h
+++ b/Marlin/src/sd/cardreader.h
@@ -142,6 +142,10 @@ public:
static bool selectNewestFile();
#endif
+ #if ENABLED(MACHINE_COMMAND_MACROS)
+ static void runMacro(const char * const path);
+ #endif
+
// Basic file ops
static void openFileRead(const char * const path, const uint8_t subcall=0);
static void openFileWrite(const char * const path);
@@ -150,6 +154,7 @@ public:
static void removeFile(const char * const name);
static char* longest_filename() { return longFilename[0] ? longFilename : filename; }
+
#if ENABLED(LONG_FILENAME_HOST_SUPPORT)
static void printLongPath(char * const path); // Used by M33
static void getLongPath(char * const pathLong, char * const pathShort); // Used by anycubic_vyper