diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index e46a3af2de..ed92e08568 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -2036,6 +2036,10 @@
#define DEFAULT_SHARED_VOLUME USB_FLASH_DRIVE // :[ 'SD_ONBOARD', '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 b6a0c73c09..e7fad2fd1b 100644
--- a/Marlin/src/sd/cardreader.cpp
+++ b/Marlin/src/sd/cardreader.cpp
@@ -919,6 +919,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 5956ffd091..ed6cf7030e 100644
--- a/Marlin/src/sd/cardreader.h
+++ b/Marlin/src/sd/cardreader.h
@@ -218,6 +218,10 @@ public:
// The root directory of the current mounted drive
static MediaFile getroot() { return root; }
+ #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);
@@ -226,6 +230,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