Bocfel 2.4 (new debug commands overwrite and dump raw bytes)

Hi, while toying with Bocfel 2.4 I added two extra /debug commands that I found really useful.

dump [address] [number_of_bytes]: outputs raw bytes from memory
overwrite [address] [values…]: writes successive bytes starting at [address]

I’ve been using overwrite to modify Z-machine opcodes at runtime, and dump to check the original data or confirm the overwrites. Both seem to work fine.

I couldn’t, for the life of me, find Bocfel’s GitHub repo, so I’ll just drop the diff for meta.cpp here. :wink:

--- "meta - original.cpp"	2025-08-26 01:58:09.000000000 +0200
+++ meta.cpp	2025-09-04 17:01:20.143515100 +0200
@@ -213,6 +213,111 @@
     return true;
 }
 
+
+static bool meta_debug_dump(const std::string &string)
+{
+    // Syntax: dump [address] [number_of_bytes]
+    std::istringstream ss(rtrim(string));
+    std::string addrstr, num_bytes_str;
+
+    if (!(ss >> addrstr >> num_bytes_str)) {
+        return false;
+    }
+
+    std::string extra;
+    if (ss >> extra) {
+        return false;
+    }
+
+    bool valid;
+    long addr_long = parse_address(addrstr, valid);
+    if (!valid) {
+        return false;
+    }
+
+    if (addr_long < 0 || static_cast<uint32_t>(addr_long) >= memory_size) {
+        screen_printf("[Address out of range: must be [0, 0x%lx]]\n", static_cast<unsigned long>(memory_size) - 1);
+        return true;
+    }
+
+    long num_bytes = parseint(num_bytes_str, 0, valid);
+    if (!valid || num_bytes <= 0) {
+        screen_puts("[Number of bytes must be a positive integer]");
+        return true;
+    }
+
+    uint32_t addr = static_cast<uint32_t>(addr_long);
+    if (static_cast<uint64_t>(addr) + static_cast<uint64_t>(num_bytes) > memory_size) {
+        num_bytes = memory_size - addr;
+        screen_printf("[Warning: range exceeds memory. Dumping %ld byte%s instead.]\n", num_bytes, num_bytes == 1 ? "" : "s");
+    }
+
+    for (long i = 0; i < num_bytes; i++) {
+        screen_printf("0x%02x%s", memory[addr + static_cast<uint32_t>(i)], (i == num_bytes - 1) ? "" : " ");
+    }
+    screen_puts("");
+    return true;
+}
+
+static bool meta_debug_overwrite(const std::string &string)
+{
+    // Syntax: overwrite [address] [values...]
+    // [address]: absolute (hex, optional 0x) or global variable Gxx
+    // [values]: one or more byte values (decimal, hex with 0x, or octal with leading 0)
+    std::istringstream ss(rtrim(string));
+    std::string addrstr;
+
+    if (!(ss >> addrstr)) {
+        return false;
+    }
+
+    bool valid;
+    long addr_long = parse_address(addrstr, valid);
+    if (!valid) {
+        return false;
+    }
+
+    if (addr_long < 0 || static_cast<uint32_t>(addr_long) >= memory_size) {
+        screen_printf("[Address out of range: must be [0, 0x%lx]]\n", static_cast<unsigned long>(memory_size) - 1);
+        return true;
+    }
+
+    std::vector<uint8_t> bytes;
+    for (std::string tok; ss >> tok; ) {
+        long v = parseint(tok, 0, valid);
+        if (!valid) {
+            return false;
+        }
+        if (v < 0 || v > 0xff) {
+            screen_puts("[Values must be in the range [0, 255]]");
+            return true;
+        }
+        bytes.push_back(static_cast<uint8_t>(v));
+    }
+
+    if (bytes.empty()) {
+        screen_puts("[No values]");
+        return true;
+    }
+
+    uint32_t addr = static_cast<uint32_t>(addr_long);
+    uint32_t end = addr + static_cast<uint32_t>(bytes.size());
+    if (end > memory_size) {
+        // Truncate to end of memory; this mirrors other debug commands' forgiving behavior
+        bytes.resize(memory_size - addr);
+    }
+
+    for (size_t i = 0; i < bytes.size(); i++) {
+        store_byte(addr + static_cast<uint32_t>(i), bytes[i]);
+    }
+
+    screen_printf("[Overwrote %lu byte%s starting at %s]\n",
+                  static_cast<unsigned long>(bytes.size()),
+                  bytes.size() == 1 ? "" : "s",
+                  addrstring(static_cast<uint16_t>(addr)).c_str());
+
+    return true;
+}
 static bool meta_debug_print(const std::string &string)
 {
     bool valid;
@@ -453,6 +558,9 @@
             "scan [n]: update scan list with all words equal to [n]; if [n] starts with 0x it is hexadecimal, 0 it is octal, decimal otherwise\n"
             "scan show: print all locations matching scan criteria\n"
             "print [address]: print the word at address [address]\n"
+            "dump [address] [number_of_bytes]: dump raw bytes from memory\n"
+            "overwrite [address] [values...]: overwrite successive bytes starting at [address]\n"
+
 #ifndef ZTERP_NO_CHEAT
             "freeze [address] [value]: freeze the 16-bit [value] at [address]; [value] can be decimal, hexadecimal, or octal, with a leading 0x signifying hexadecimal and a leading 0 signifying octal\n"
             "unfreeze [address]: unfreeze the value currently frozen at [address]\n"
@@ -485,6 +593,12 @@
     } else if (cmd == "print") {
         ok = meta_debug_print(args);
     }
+    else if (cmd == "dump") {
+        ok = meta_debug_dump(args);
+    }
+    else if (cmd == "overwrite") {
+        ok = meta_debug_overwrite(args);
+    }
 #ifndef ZTERP_NO_CHEAT
     else if (cmd == "freeze") {
         ok = meta_debug_freeze(args);
@@ -856,4 +970,4 @@
     if (first_run) {
         stash_register(std::make_unique<NotesStasher>());
     }
-}
+}
\ No newline at end of file

2 Likes

I didn’t use the Git-compatible, unified format, lol… :melting_face: Fixed.

I believe Bocfel updates are handled via the Gargoyle repository, so that’s probably the best place to submit this. Nice idea!

1 Like

Ah, of course… now that I was scrolling Overview - Bocfel I saw the very last line saying “Please report bugs to Gargoyle’s issue tracker.” Can’t believe I missed that. Thanks!

So yeah, Bocfel development is basically all done behind closed doors because, well, just because that’s how I’ve always done it. I track things via Gargoyle, as noted, which does include feature requests, etc. You’re 100% free to submit patches there via a PR, and we can go through a normal iterative review process, though in the end I’d apply the accepted stuff locally and bring it into Gargoyle with the next official Bocfel release.

I’m also happy to take the patch as-is here and integrate it myself (potentially with updates/changes as necessary).

In short, it’s a perfectly reasonable feature to add, and I’m happy to bring it in; but the "development process”, such as it is, is pretty archaic compared to modern expectations. Maybe one day I’ll move into the 21st century…

4 Likes

Thanks… Heh, I’m with you, I like the 20th century, and I’m a bit of a loner. The patch was just something I concocted on a whim and thought, “hey, maybe someone could use this.” :slightly_smiling_face: