diff --git a/package.json b/package.json
index 9d16a99..89f50c4 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,8 @@
   "scripts": {
     "clean-build": "yarn clean && yarn build",
     "build": "tsc --build",
-    "parse-dump": "yarn build && node utils/parse-dump.mjs",
+    "parse-wireshark-dump": "yarn build && node utils/parse-wireshark-dump.mjs",
+    "parse-text-dump": "yarn build && node utils/parse-text-dump.mjs",
     "clean": "node clean-dist.mjs",
     "gen-printer-models": "node utils/gen-printer-models.js > src/printer_models.ts"
   },
diff --git a/utils/parse-text-dump.mjs b/utils/parse-text-dump.mjs
new file mode 100644
index 0000000..bc163a0
--- /dev/null
+++ b/utils/parse-text-dump.mjs
@@ -0,0 +1,80 @@
+import { Utils, NiimbotPacket, RequestCommandId, ResponseCommandId } from "../dist/index.js";
+import fs from "fs";
+
+// usage: yarn parse-text-dump <path> [data | min | min-out | print-task]
+//
+// input file example:
+//
+// >> 555540010849AAAA
+// << 55554801024BAAAA
+// >> 555540010948AAAA
+// << 55554902016822AAAA
+
+const path = process.argv[2];
+const display = process.argv.length > 3 ? process.argv[3] : "";
+const data = fs.readFileSync(path, "utf8");
+
+const lines = data.split(/\r?\n/);
+let printStarted = false;
+
+for (const line of lines) {
+  const splitted = line.split(" ");
+
+  if (splitted.length !== 2) {
+    continue;
+  }
+
+  let [direction, hexData] = splitted;
+
+  let comment = "";
+
+  try {
+    const data = Utils.hexToBuf(hexData);
+    const packets = NiimbotPacket.fromBytesMultiPacket(data);
+
+    if (packets.length === 0) {
+      comment = "Parse error (no packets found)";
+    } else if (packets.length > 1) {
+      comment = `Multi-packet (x${packets.length}); `;
+    }
+
+    for (const packet of packets) {
+      if (direction === ">>") {
+        comment += RequestCommandId[packet.command] ?? "???";
+        if (packet.command === RequestCommandId.PrintStart) {
+          printStarted = true;
+          const versions = { 1: "v1", 2: "v3", 7: "v4", 8: "v5" };
+          comment += "_" + versions[packet.dataLength];
+        } else if (packet.command === RequestCommandId.SetPageSize) {
+          const versions = { 2: "v1", 4: "v2", 6: "v3", 8: "v5" };
+          comment += "_" + versions[packet.dataLength];
+        } else if (packet.command === RequestCommandId.PrintEnd) {
+          printStarted = false;
+        }
+      } else {
+        comment += ResponseCommandId[packet.command] ?? "???";
+      }
+      if (display === "data") {
+        comment += `(${packet.dataLength}: ${Utils.bufToHex(packet.data)}); `;
+      } else {
+        comment += `(${packet.dataLength}); `;
+      }
+    }
+  } catch (e) {
+    comment = "Invalid packet";
+  }
+
+  if (display === "min") {
+    console.log(`${direction} ${comment}`);
+  } else if (display === "min-out") {
+    if (direction === ">>") {
+      console.log(`${direction} ${comment}`);
+    }
+  } else if (display === "print-task") {
+    if (direction === ">>" && printStarted) {
+      console.log(`${direction} ${comment}`);
+    }
+  } else {
+    console.log(`${direction} ${hexData}\t// ${comment}`);
+  }
+}
diff --git a/utils/parse-dump.mjs b/utils/parse-wireshark-dump.mjs
similarity index 93%
rename from utils/parse-dump.mjs
rename to utils/parse-wireshark-dump.mjs
index 1c8df3f..9b17f4c 100644
--- a/utils/parse-dump.mjs
+++ b/utils/parse-wireshark-dump.mjs
@@ -79,7 +79,12 @@ spawned.on("close", (code) => {
         } else {
           comment += ResponseCommandId[packet.command] ?? "???";
         }
-        comment += `(${packet.dataLength}); `;
+        
+        if (display === "data") {
+          comment += `(${packet.dataLength}: ${Utils.bufToHex(packet.data)}); `;
+        } else {
+          comment += `(${packet.dataLength}); `;
+        }
       }
     } catch (e) {
       comment = "Invalid packet";