Initial commit
authorRobert Pengelly <robertapengelly@hotmail.com>
Sat, 17 Oct 2026 08:56:40 +0000 (09:56 +0100)
committerRobert Pengelly <robertapengelly@hotmail.com>
Sat, 17 Oct 2026 08:56:40 +0000 (09:56 +0100)
19 files changed:
.github/funding.yml [new file with mode: 0644]
.github/workflows/mirror.yml [new file with mode: 0644]
LICENSE [new file with mode: 0755]
Makefile.unix [new file with mode: 0755]
Makefile.w32 [new file with mode: 0755]
README.md [new file with mode: 0755]
cpu.c [new file with mode: 0644]
cpu.h [new file with mode: 0644]
heap.c [new file with mode: 0644]
heap.h [new file with mode: 0644]
interrupts.c [new file with mode: 0644]
interrupts.h [new file with mode: 0644]
loader.c [new file with mode: 0644]
loader.h [new file with mode: 0644]
main.c [new file with mode: 0644]
memory.c [new file with mode: 0644]
memory.h [new file with mode: 0644]
opcodes.c [new file with mode: 0644]
opcodes.h [new file with mode: 0644]

diff --git a/.github/funding.yml b/.github/funding.yml
new file mode 100644 (file)
index 0000000..ea28a94
--- /dev/null
@@ -0,0 +1 @@
+custom: ["https://buymeacoffee.com/robertapengelly", "https://paypal.me/robertapengelly92"]
diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml
new file mode 100644 (file)
index 0000000..085d5d3
--- /dev/null
@@ -0,0 +1,18 @@
+name: Mirror
+
+on:
+  schedule:
+    - cron: "36 */6 * * *"
+  workflow_dispatch:
+
+permissions:
+  contents: write
+
+jobs:
+  mirror1:
+    runs-on: ubuntu-latest
+    steps:
+      - run: git clone --bare https://git.candlhat.org/dosemu .
+      - run: git push --mirror "https://x:$GITHUB_TOKEN@github.com/robertapengelly/dosemu.git"
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/LICENSE b/LICENSE
new file mode 100755 (executable)
index 0000000..f50ef62
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.\r
+\r
+Anyone is free to copy, modify, publish, use, compile, sell, or\r
+distribute this software, either in source code form or as a compiled\r
+binary, for any purpose, commercial or non-commercial, and by any\r
+means.\r
+\r
+In jurisdictions that recognize copyright laws, the author or authors\r
+of this software dedicate any and all copyright interest in the\r
+software to the public domain. We make this dedication for the benefit\r
+of the public at large and to the detriment of our heirs and\r
+successors. We intend this dedication to be an overt act of\r
+relinquishment in perpetuity of all present and future rights to this\r
+software under copyright law.\r
+\r
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\r
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\r
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\r
+OTHER DEALINGS IN THE SOFTWARE.\r
+\r
+For more information, please refer to <https://unlicense.org>\r
diff --git a/Makefile.unix b/Makefile.unix
new file mode 100755 (executable)
index 0000000..655490d
--- /dev/null
@@ -0,0 +1,29 @@
+#******************************************************************************
+# @file             Makefile.unix
+#******************************************************************************
+SRCDIR              ?=  $(CURDIR)
+VPATH               :=  $(SRCDIR)
+
+CC                  :=  gcc
+CFLAGS              :=  -D_FILE_OFFSET_BITS=64 -O2 -Wall -Werror -Wextra
+
+CC_SRC              :=  $(wildcard $(SRCDIR)/*.c)
+
+ifeq ($(OS), Windows_NT)
+all: dosemu.exe
+
+dosemu.exe: $(CC_SRC)
+
+       $(CC) $(CFLAGS) -o $@ $^ -lm
+else
+all: dosemu
+
+dosemu: $(CC_SRC)
+
+       $(CC) $(CFLAGS) -o $@ $^ -lm
+endif
+
+clean:
+
+       if [ -f dosemu.exe ]; then rm -rf dosemu.exe; fi
+       if [ -f dosemu ]; then rm -rf dosemu; fi
diff --git a/Makefile.w32 b/Makefile.w32
new file mode 100755 (executable)
index 0000000..7ff9b1c
--- /dev/null
@@ -0,0 +1,21 @@
+#******************************************************************************
+# @file             Makefile.w32
+#******************************************************************************
+SRCDIR              ?=  $(CURDIR)
+VPATH               :=  $(SRCDIR)
+
+CC                  :=  gcc
+CFLAGS              :=  -D_FILE_OFFSET_BITS=64 -O2 -Wall -Werror -Wextra
+
+CC_SRC              :=  $(wildcard $(SRCDIR)/*.c)
+
+all: dosemu.exe
+
+dosemu.exe: $(CC_SRC)
+
+       $(CC) $(CFLAGS) -o $@ $^
+
+clean:
+
+       if exist dosemu.exe ( del /q dosemu.exe )
+       if exist dosemu ( del /q dosemu )
diff --git a/README.md b/README.md
new file mode 100755 (executable)
index 0000000..c6efbf1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,68 @@
+## Disclaimer
+
+  This project is currently in early development and has
+  only been tested with a handful of DOS applications
+  so far.
+
+## License
+
+  All source code is Public Domain.
+
+## Obtain the source code
+
+  git clone https://git.candlhat.org/dosemu.git
+
+## Building
+
+  BSD:
+  
+      Make sure you have gcc and gmake installed then run
+      gmake -f Makefile.unix.
+  
+  Linux:
+  
+      Make sure you have gcc and make installed then run
+      make -f Makefile.unix.
+  
+  macOS:
+  
+      Make sure you have xcode command line tools installed then run
+      make -f Makefile.unix.
+  
+  Windows:
+  
+      Make sure you have MinGW installed and its bin directory added
+      to your PATH environment variable, then run
+      mingw32-make.exe -f Makefile.w32.
+
+## About
+
+  Run DOS applications as if they are native system ones.  If an
+  application requires a specific DOS version, you can set the
+  DOS_VER environment variable to specify the version reported to
+  the application.  You can set it through the command line with:
+  
+  ### Windows
+  
+    set DOS_VER=xx.xx
+  
+  ### Linux, macOS or BSD
+  
+    export DOS_VER=xx.xx
+  
+  Currently the only programs that have been tested are:
+  
+    COMMAND.COM, EDLIN.COM, MORE.COM and TREE.COM
+  
+  Once you're all set, simply run:
+  
+    dosemu XX.EXE
+  
+  or
+  
+    dosemu XX.COM
+  
+  As you use the project, if you find applications that don't
+  function as they should, or encounter missing interrupt
+  handlers or CPU instructions, please feel free to open an
+  issue and I'll do my best to add support.
diff --git a/cpu.c b/cpu.c
new file mode 100644 (file)
index 0000000..8c03315
--- /dev/null
+++ b/cpu.c
@@ -0,0 +1,4564 @@
+/******************************************************************************
+ * @file            cpu.c
+ *****************************************************************************/
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    "cpu.h"
+#include    "heap.h"
+#include    "interrupts.h"
+#include    "opcodes.h"
+#include    "memory.h"
+
+#define     FLAG_CF     0x0001
+#define     FLAG_PF     0x0004          /* Parity Flag (bit 2) */
+#define     FLAG_AF     0x0010          /* Auxiliary Carry Flag (bit 4) */
+#define     FLAG_ZF     0x0040
+#define     FLAG_SF     0x0080
+#define     FLAG_IF     0x0200
+#define     FLAG_DF     0x0400          /* Direction Flag (bit 10) */
+#define     FLAG_OF     0x0800          /* Overflow Flag (bit 11) */
+
+static uint8_t get_al (CPU8086 *cpu) {
+    return (uint8_t) (cpu->regs[REG_AX] & 0x00FF);
+}
+
+static void set_al (CPU8086 *cpu, uint8_t v) {
+    cpu->regs[REG_AX] = (uint16_t) ((cpu->regs[REG_AX] & 0xFF00) | v);
+}
+
+static int rep_active (const CPU8086 *cpu) {
+    return cpu->rep_type == 1 || cpu->rep_type == 2;
+}
+
+static uint16_t rep_initial_count (const CPU8086 *cpu) {
+    return rep_active (cpu) ? cpu->regs[REG_CX] : 1;
+}
+
+uint32_t get_phys (uint16_t seg, uint16_t off) {
+
+    /* Standard 8086 behavior: wrap the offset at 64KB (16-bit) */
+    /* BEFORE adding it to the segment base.*/
+    return (((uint32_t) seg << 4) + (uint16_t) off) & 0xFFFFF;
+
+}
+
+uint8_t fetch8 (CPU8086 *cpu) {
+
+    uint32_t phys = get_phys (cpu->cs, cpu->ip);
+    
+    uint8_t v = mem_read8 (cpu->mem, cpu->mem_size, phys);
+    cpu->ip = (uint16_t) (cpu->ip + 1); 
+    
+    return v;
+
+}
+
+uint16_t fetch16 (CPU8086 *cpu) {
+
+    /* Byte 1 at CS:IP */
+    uint8_t low = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->cs, cpu->ip));
+    
+    /* Byte 2 at CS:(IP + 1). The +1 wraps 16-bit because IP is uint16_t. */
+    uint8_t high = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->cs, cpu->ip + 1));
+    
+    cpu->ip = (uint16_t) (cpu->ip + 2);
+    return (uint16_t) low | ((uint16_t) high << 8);
+
+}
+
+uint32_t fetch32 (CPU8086 *cpu) {
+
+    uint32_t low = fetch16 (cpu);
+    uint32_t high = fetch16 (cpu);
+    
+    return (high << 16) | low;
+
+}
+
+uint8_t get_reg8 (const CPU8086 *cpu, int reg) {
+
+    reg &= 7;
+    
+    if (reg < 4) {
+        return (uint8_t) (cpu->regs[reg] & 0x00ffu);
+    }
+    
+    return (uint8_t) ((cpu->regs[reg - 4] >> 8) & 0x00ffu);
+
+}
+
+void set_reg8 (CPU8086 *cpu, int reg, uint8_t value) {
+
+    reg &= 7;
+    
+    if (reg < 4) {
+        cpu->regs[reg] = (uint16_t)((cpu->regs[reg] & 0xff00u) | value);
+    } else {
+    
+        int wreg = reg - 4;
+        cpu->regs[wreg] = (uint16_t)((cpu->regs[wreg] & 0x00ffu) | ((uint16_t)value << 8));
+    
+    }
+
+}
+
+static void set_zf_sf_8 (CPU8086 *cpu, uint8_t value) {
+
+    if (value == 0) {
+        cpu->flags |= FLAG_ZF;
+    } else {
+        cpu->flags &= (uint16_t) ~FLAG_ZF;
+    }
+    
+    if ((value & 0x80u) != 0) {
+        cpu->flags |= FLAG_SF;
+    } else {
+        cpu->flags &= (uint16_t) ~FLAG_SF;
+    }
+
+}
+
+static void update_common_flags8 (CPU8086 *cpu, uint8_t result) {
+
+    uint8_t p;
+    
+    /* Zero Flag */
+    if (result == 0) {
+        cpu->flags |= FLAG_ZF;
+    } else {
+        cpu->flags &= ~FLAG_ZF;
+    }
+    
+    /* Sign Flag */
+    if (result & 0x80) {
+        cpu->flags |= FLAG_SF;
+    } else {
+        cpu->flags &= ~FLAG_SF;
+    }
+    
+    /* Parity Flag: set if low byte has even number of 1s */
+    p = result;
+    
+    p ^= p >> 4;
+    p ^= p >> 2;
+    p ^= p >> 1;
+    
+    if (!(p & 1)) {
+        cpu->flags |= FLAG_PF;
+    } else {
+        cpu->flags &= ~FLAG_PF;
+    }
+
+}
+
+static void update_common_flags16 (CPU8086 *cpu, uint16_t result) {
+
+    /* 1. Zero Flag (Full 16 bits) */
+    if (result == 0) {
+        cpu->flags |= FLAG_ZF;
+    } else {
+        cpu->flags &= ~FLAG_ZF;
+    }
+    
+    /* 2. Sign Flag (Bit 15) */
+    if (result & 0x8000) {
+        cpu->flags |= FLAG_SF;
+    } else {
+        cpu->flags &= ~FLAG_SF;
+    }
+    
+    /* 3. Parity Flag (Only low 8 bits) */
+    uint8_t p = (uint8_t) (result & 0xFF);
+    p ^= p >> 4; p ^= p >> 2; p ^= p >> 1;
+    
+    if (!(p & 1)) {
+        cpu->flags |= FLAG_PF;
+    } else {
+        cpu->flags &= ~FLAG_PF;
+    }
+
+}
+
+static uint8_t dec8 (CPU8086 *cpu, uint8_t val) {
+
+    uint8_t res = val - 1;
+    uint16_t old_cf = cpu->flags & FLAG_CF;
+    
+    update_common_flags8 (cpu, res);
+    
+    if (val == 0x80) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    cpu->flags = (cpu->flags & ~FLAG_CF) | old_cf;          /* Preserve Carry */
+    return res;
+
+}
+
+static uint16_t dec16 (CPU8086 *cpu, uint16_t val) {
+
+    uint16_t res = val - 1;
+    uint16_t old_cf = cpu->flags & FLAG_CF;
+    
+    update_common_flags16 (cpu, res);
+    
+    if (val == 0x8000) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    cpu->flags = (cpu->flags & ~FLAG_CF) | old_cf;          /* Preserve Carry */
+    return res;
+
+}
+
+static uint8_t inc8 (CPU8086 *cpu, uint8_t val) {
+
+    uint8_t res = val + 1;
+    uint16_t old_cf = cpu->flags & FLAG_CF;                 /* 1. Save existing Carry */
+    
+    update_common_flags8 (cpu, res);                        /* 2. Update ZF, SF, PF */
+    
+    /* 3. Manually handle Overflow (OF) for 0x7F -> 0x80 */
+    if (val == 0x7F) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    /* 4. Restore the original Carry Flag */
+    cpu->flags = (cpu->flags & ~FLAG_CF) | old_cf; 
+    return res;
+
+}
+
+static uint8_t add8 (CPU8086 *cpu, uint8_t a, uint8_t b) {
+
+    uint16_t res = (uint16_t) a + (uint16_t) b;
+    
+    /* Carry */
+    if (res & 0x100) {
+        cpu->flags |= FLAG_CF;
+    } else {
+        cpu->flags &= ~FLAG_CF;
+    }
+    
+    /* Auxiliary Carry */
+    if ((a ^ b ^ res) & 0x10) {
+        cpu->flags |= FLAG_AF;
+    } else {
+        cpu->flags &= ~FLAG_AF;
+    }
+    
+    /* Overflow: (both inputs same sign) AND (result sign is different) */
+    if (!((a ^ b) & 0x80) && ((a ^ res) & 0x80)) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+
+    update_common_flags8 (cpu, (uint8_t) res);
+    return (uint8_t) res;
+
+}
+
+static uint16_t add16 (CPU8086 *cpu, uint16_t a, uint16_t b) {
+
+    uint32_t res = (uint32_t) a + (uint32_t) b;
+    
+    if (res & 0x10000) {
+        cpu->flags |= FLAG_CF;
+    } else {
+        cpu->flags &= ~FLAG_CF;
+    }
+    
+    if ((a ^ b ^ res) & 0x10) {
+        cpu->flags |= FLAG_AF;
+    } else {
+        cpu->flags &= ~FLAG_AF;
+    }
+    
+    if (!((a ^ b) & 0x8000) && ((a ^ res) & 0x8000)) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    update_common_flags16 (cpu, (uint16_t) res);
+    return (uint16_t) res;
+
+}
+
+static uint8_t sub8 (CPU8086 *cpu, uint8_t a, uint8_t b) {
+
+    uint16_t res = (uint16_t) a - (uint16_t) b;
+    
+    if (a < b) {
+        cpu->flags |= FLAG_CF;
+    } else {
+        cpu->flags &= ~FLAG_CF;
+    }
+    
+    if ((a ^ b ^ res) & 0x10) {
+        cpu->flags |= FLAG_AF;
+    } else {
+        cpu->flags &= ~FLAG_AF;
+    }
+    
+    /* Overflow: (inputs have different signs) AND (result sign doesn't match minuend) */
+    if (((a ^ b) & 0x80) && ((a ^ res) & 0x80)) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    update_common_flags8 (cpu, (uint8_t) res);
+    return (uint8_t) res;
+
+}
+
+static uint16_t sub16 (CPU8086 *cpu, uint16_t a, uint16_t b) {
+
+    uint32_t res = (uint32_t)a - (uint32_t)b;
+    
+    if (a < b) {
+        cpu->flags |= FLAG_CF;
+    } else {
+        cpu->flags &= ~FLAG_CF;
+    }
+    
+    if ((a ^ b ^ res) & 0x10) {
+        cpu->flags |= FLAG_AF;
+    } else {
+        cpu->flags &= ~FLAG_AF;
+    }
+    
+    if (((a ^ b) & 0x8000) && ((a ^ res) & 0x8000)) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+
+    update_common_flags16 (cpu, (uint16_t) res);
+    return (uint16_t) res;
+
+}
+
+static uint8_t xor8 (CPU8086 *cpu, uint8_t a, uint8_t b) {
+
+    uint8_t result = (uint8_t) (a ^ b);
+    
+    cpu->flags &= (uint16_t) ~(FLAG_CF | FLAG_OF);
+    update_common_flags8 (cpu, result);
+    
+    return result;
+
+}
+
+static uint16_t xor16 (CPU8086 *cpu, uint16_t a, uint16_t b) {
+
+    uint16_t result = (uint16_t) (a ^ b);
+    
+    cpu->flags &= (uint16_t) ~(FLAG_CF | FLAG_OF);
+    update_common_flags16 (cpu, result);
+    
+    return result;
+
+}
+
+static uint16_t inc16 (CPU8086 *cpu, uint16_t val) {
+
+    uint16_t res = val + 1;
+    uint16_t old_cf = cpu->flags & FLAG_CF;                 /* Save current CF */
+    
+    update_common_flags16 (cpu, res);                       /* Updates ZF, SF, PF */
+    
+    /* Manually handle OF (Overflow) */
+    if (val == 0x7FFF) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    cpu->flags = (cpu->flags & ~FLAG_CF) | old_cf;      /* Restore saved CF */
+    return res;
+
+}
+
+void push16 (CPU8086 *cpu, uint16_t value) {
+
+    cpu->regs[REG_SP] -= 2;
+    
+    mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, cpu->regs[REG_SP]), value & 0xFF);
+    mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, cpu->regs[REG_SP] + 1), value >> 8);
+
+}
+
+uint16_t pop16 (CPU8086 *cpu) {
+
+    uint16_t sp = cpu->regs[REG_SP];
+    
+    uint8_t lo = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, sp));
+    uint8_t hi = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, sp + 1));
+    
+    uint16_t value = lo | (hi << 8);
+    
+    cpu->regs[REG_SP] += 2;
+    return value;
+
+}
+
+static uint8_t and8 (CPU8086 *cpu, uint8_t a, uint8_t b) {
+
+    uint8_t result = a & b;
+    
+    /* Clear BOTH CF and OF */
+    cpu->flags &= (uint16_t) ~(FLAG_CF | FLAG_OF);
+    
+    update_common_flags8 (cpu, result);
+    return result;
+
+}
+
+static uint16_t and16 (CPU8086 *cpu, uint16_t a, uint16_t b) {
+
+    uint16_t result = a & b;
+    
+    cpu->flags &= (uint16_t) ~(FLAG_CF | FLAG_OF);
+    update_common_flags16 (cpu, result);
+    
+    return result;
+
+}
+
+static uint8_t or8 (CPU8086 *cpu, uint8_t a, uint8_t b) {
+
+    uint8_t result = a | b;
+    
+    cpu->flags &= (uint16_t) ~(FLAG_CF | FLAG_OF);          /* MUST clear both */
+    update_common_flags8 (cpu, result);
+    
+    return result;
+
+}
+
+static uint16_t or16 (CPU8086 *cpu, uint16_t a, uint16_t b) {
+
+    uint16_t result = a | b;
+    
+    cpu->flags &= (uint16_t) ~(FLAG_CF | FLAG_OF);      /* MUST clear both */
+    update_common_flags16 (cpu, result);
+    
+    return result;
+
+}
+
+static uint8_t adc8 (CPU8086 *cpu, uint8_t a, uint8_t b) {
+
+    uint16_t carry = (cpu->flags & FLAG_CF) ? 1 : 0;
+    uint32_t res = (uint32_t) a + (uint32_t) b + carry;
+    
+    /* Carry Flag */
+    if (res & 0x100) {
+        cpu->flags |= FLAG_CF;
+    } else {
+        cpu->flags &= ~FLAG_CF;
+    }
+    
+    /* Auxiliary Carry Flag */
+    if ((a ^ b ^ res) & 0x10) {
+        cpu->flags |= FLAG_AF;
+    } else {
+        cpu->flags &= ~FLAG_AF;
+    }
+    
+    /* Overflow Flag */
+    if (!((a ^ b) & 0x80) && ((a ^ res) & 0x80)) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    update_common_flags8 (cpu, (uint8_t) res);
+    return (uint8_t) res;
+
+}
+
+static uint16_t adc16 (CPU8086 *cpu, uint16_t a, uint16_t b) {
+
+    uint16_t carry = (cpu->flags & FLAG_CF) ? 1 : 0;
+    uint32_t res = (uint32_t) a + (uint32_t) b + carry;
+    
+    if (res & 0x10000) {
+        cpu->flags |= FLAG_CF;
+    } else {
+        cpu->flags &= ~FLAG_CF;
+    }
+    
+    if ((a ^ b ^ res) & 0x10) {
+        cpu->flags |= FLAG_AF;
+    } else {
+        cpu->flags &= ~FLAG_AF;
+    }
+    
+    if (!((a ^ b) & 0x8000) && ((a ^ res) & 0x8000)) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    update_common_flags16 (cpu, (uint16_t) res);
+    return (uint16_t) res;
+
+}
+
+static uint8_t sbb8 (CPU8086 *cpu, uint8_t a, uint8_t b) {
+
+    uint16_t borrow = (cpu->flags & FLAG_CF) ? 1 : 0;
+    uint16_t res = (uint16_t) a - (uint16_t) b - borrow;
+    
+    /* 1. Carry Flag: Borrow required from next higher bit */
+    if (a < (uint16_t) b + borrow) {
+        cpu->flags |= FLAG_CF;
+    } else {
+        cpu->flags &= ~FLAG_CF;
+    }
+    
+    /* 2. Auxiliary Carry: Borrow from bit 4 (nibble boundary) */
+    if ((a ^ b ^ res) & 0x10) {
+        cpu->flags |= FLAG_AF;
+    } else {
+        cpu->flags &= ~FLAG_AF;
+    }
+    
+    /* 3. Overflow: Set if signed overflow occurs */
+    if (((a ^ b) & 0x80) && ((a ^ res) & 0x80)) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    /* 4. Updates ZF, SF, and PF using your existing helper */
+    update_common_flags8 (cpu, (uint8_t) res);
+    
+    return (uint8_t) res;
+
+}
+
+static uint16_t sbb16 (CPU8086 *cpu, uint16_t a, uint16_t b) {
+
+    uint16_t borrow = (cpu->flags & FLAG_CF) ? 1 : 0;
+    uint32_t res = (uint32_t) a - (uint32_t) b - borrow;
+    
+    /* Carry Flag */
+    if (a < (uint32_t) b + borrow) {
+        cpu->flags |= FLAG_CF;
+    } else {
+        cpu->flags &= ~FLAG_CF;
+    }
+    
+    /* Auxiliary Carry Flag */
+    if ((a ^ b ^ (uint16_t) res) & 0x10) {
+        cpu->flags |= FLAG_AF;
+    } else {
+        cpu->flags &= ~FLAG_AF;
+    }
+    
+    /* Overflow Flag */
+    if (((a ^ b) & 0x8000) && ((a ^ (uint16_t) res) & 0x8000)) {
+        cpu->flags |= FLAG_OF;
+    } else {
+        cpu->flags &= ~FLAG_OF;
+    }
+    
+    /* Updates ZF, SF, and PF */
+    update_common_flags16 (cpu, (uint16_t) res);
+    
+    return (uint16_t) res;
+
+}
+
+static void daa (CPU8086 *cpu) {
+
+    uint8_t al = get_reg8 (cpu, REG_AX);
+    uint8_t old_al = al;
+    uint8_t old_cf = (cpu->flags & FLAG_CF) ? 1 : 0;
+    
+    /* Adjust low nibble */
+    if ((al & 0x0F) > 9 || (cpu->flags & FLAG_AF)) {
+    
+        al += 6;
+        cpu->flags |= FLAG_AF;
+    
+    } else {
+        cpu->flags &= (uint16_t) ~FLAG_AF;
+    }
+    
+    /* Adjust high nibble */
+    if (old_al > 0x99 || old_cf) {
+    
+        al += 0x60;
+        cpu->flags |= FLAG_CF;
+    
+    } else {
+        cpu->flags &= (uint16_t) ~FLAG_CF;
+    }
+    
+    set_reg8 (cpu, REG_AX, al);
+    set_zf_sf_8 (cpu, al);
+
+}
+
+static void das (CPU8086 *cpu) {
+
+    uint8_t al = get_reg8 (cpu, REG_AX);
+    uint8_t old_al = al;
+    uint8_t old_cf = (cpu->flags & FLAG_CF) ? 1 : 0;
+    
+    if ((al & 0x0F) > 9 || (cpu->flags & FLAG_AF)) {
+    
+        al -= 6;
+        cpu->flags |= FLAG_AF;
+    
+    } else {
+        cpu->flags &= (uint16_t) ~FLAG_AF;
+    }
+    
+    if (old_al > 0x99 || old_cf) {
+    
+        al -= 0x60;
+        cpu->flags |= FLAG_CF;
+    
+    } else {
+        cpu->flags &= (uint16_t) ~FLAG_CF;
+    }
+    
+    set_reg8 (cpu, REG_AX, al);
+    set_zf_sf_8 (cpu, al);
+
+}
+
+static void aaa (CPU8086 *cpu) {
+
+    uint8_t al = get_reg8 (cpu, REG_AX);
+    
+    /* Check if the low nibble is > 9 or if AF is set */
+    if ((al & 0x0F) > 9 || (cpu->flags & FLAG_AF)) {
+    
+        set_reg8 (cpu, REG_AX, (al + 6) & 0x0F);
+        
+        /* Increment AH (High byte of AX) */
+        uint8_t ah = (uint8_t) (cpu->regs[REG_AX] >> 8);
+        ah++;
+        
+        cpu->regs[REG_AX] = (cpu->regs[REG_AX] & 0x00FF) | ((uint16_t) ah << 8);
+        
+        cpu->flags |= FLAG_AF;
+        cpu->flags |= FLAG_CF;
+    
+    } else {
+    
+        set_reg8 (cpu, REG_AX, al & 0x0F);
+        
+        cpu->flags &= (uint16_t) ~FLAG_AF;
+        cpu->flags &= (uint16_t) ~FLAG_CF;
+    
+    }
+
+}
+
+static uint8_t io_read8 (CPU8086 *cpu, uint16_t port) {
+
+    (void) cpu;
+    
+    switch (port) {
+        
+        case 0x60:             /* keyboard data port */
+        
+            return 0x00;                /* no key available unless you implement keyboard queue */
+        
+        case 0x61:             /* keyboard controller / speaker control */
+        
+            return cpu->port61;
+        
+        case 0x64:             /* keyboard status port */
+        
+            return 0x00;                /* bit0 clear = no output byte ready */
+        
+        case 0x3DA:                     /* CGA/EGA/VGA status */
+        
+            cpu->vga_status_flip ^= 1;
+            return cpu->vga_status_flip ? 0x08 : 0x00;      /* vertical retrace bit */
+        
+        default:
+        
+            fprintf (stderr, "Unhandled IO read8 port %04X at %04X:%04X\n", port, cpu->cs, cpu->ip);
+            exit (1);
+    
+    }
+
+}
+
+static uint16_t io_read16 (CPU8086 *cpu, uint16_t port) {
+
+    uint8_t lo = io_read8 (cpu, port);
+    uint8_t hi = io_read8 (cpu, (uint16_t) (port + 1));
+    
+    return (uint16_t) (lo | ((uint16_t) hi << 8));
+
+}
+
+static void io_write8 (CPU8086 *cpu, uint16_t port, uint8_t value) {
+
+    switch (port) {
+    
+        case 0x61:
+        
+            cpu->port61 = value;
+            break;
+        
+        case 0x3D8:
+        case 0x3D9:
+        case 0x3D4:
+        case 0x3D5:
+        
+            /* video control registers - store later if needed */
+            break;
+        
+        default:
+        
+            fprintf (stderr, "Unhandled IO write8 port %04X value %02X at %04X:%04X\n", port, value, cpu->cs, cpu->ip);
+            exit (1);
+    
+    }
+
+}
+
+static void io_write16 (CPU8086 *cpu, uint16_t port, uint16_t value) {
+
+    io_write8 (cpu, port, (uint8_t) (value & 0xFF));
+    io_write8 (cpu, (uint16_t) (port + 1), (uint8_t) (value >> 8));
+
+}
+
+typedef struct OperandRef {
+
+    int is_mem;
+    int reg;
+    
+    uint16_t addr;
+    uint16_t default_seg;
+    
+    uint16_t seg;
+
+} OperandRef;
+
+static OperandRef decode_rm_operand (CPU8086 *cpu, uint8_t modrm) {
+
+    OperandRef out;
+    
+    uint8_t mod = (modrm >> 6) & 3;
+    uint8_t rm  = modrm & 7;
+    
+    out.reg = rm;
+    out.is_mem = (mod != 3);
+    out.addr = 0;
+    out.default_seg = cpu->ds;
+    out.seg = cpu->ds;
+    
+    if (mod == 3) {
+    
+        out.seg = 0;
+        return out;
+    
+    }
+    
+    {
+    
+        uint16_t base = 0;
+        int uses_bp = 0;
+        
+        switch (rm) {
+        
+            case 0:
+            
+                base = (uint16_t) (cpu->regs[REG_BX] + cpu->regs[REG_SI]);
+                break;
+            
+            case 1:
+            
+                base = (uint16_t) (cpu->regs[REG_BX] + cpu->regs[REG_DI]);
+                break;
+            
+            case 2:
+            
+                base = (uint16_t) (cpu->regs[REG_BP] + cpu->regs[REG_SI]); uses_bp = 1;
+                break;
+            
+            case 3:
+            
+                base = (uint16_t) (cpu->regs[REG_BP] + cpu->regs[REG_DI]); uses_bp = 1;
+                break;
+            
+            case 4:
+            
+                base = cpu->regs[REG_SI];
+                break;
+            
+            case 5:
+            
+                base = cpu->regs[REG_DI];
+                break;
+            
+            case 6:
+            
+                if (mod == 0) {
+                
+                    base = fetch16 (cpu);                   /* [disp16] */
+                    uses_bp = 0;
+                
+                } else {
+                
+                    base = cpu->regs[REG_BP];               /* [BP+disp] */
+                    uses_bp = 1;
+                
+                }
+                
+                break;
+            
+            case 7:
+            
+                base = cpu->regs[REG_BX];
+                break;
+        
+        }
+        
+        if (mod == 1) {
+        
+            int8_t disp8 = (int8_t) fetch8 (cpu);
+            base = (uint16_t) (base + disp8);
+        
+        } else if (mod == 2) {
+        
+            uint16_t disp16 = fetch16 (cpu);
+            base = (uint16_t) (base + disp16);
+        
+        }
+        
+        out.addr = base;
+        out.default_seg = uses_bp ? cpu->ss : cpu->ds;
+        out.seg = cpu->has_seg_override ? cpu->seg_override : out.default_seg;
+    
+    }
+    
+    return out;
+
+}
+
+static uint8_t operand_read8 (CPU8086 *cpu, const OperandRef *op) {
+
+    if (op->is_mem) {
+    
+        uint16_t seg = op->seg;
+        return mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, op->addr));
+    
+    }
+    
+    return get_reg8 (cpu, op->reg);
+
+}
+
+static void operand_write8 (CPU8086 *cpu, const OperandRef *op, uint8_t value) {
+
+    if (op->is_mem) {
+    
+        uint16_t seg = op->seg;;
+        mem_write8 (cpu->mem, cpu->mem_size, get_phys (seg, op->addr), value);
+    
+    } else {
+        set_reg8 (cpu, op->reg, value);
+    }
+
+}
+
+static uint16_t operand_read16 (CPU8086 *cpu, const OperandRef *op) {
+
+    if (op->is_mem) {
+    
+        uint16_t seg = op->seg;
+        uint16_t off = (uint16_t) op->addr;
+        
+        uint32_t phys_lo = get_phys (seg, off);
+        uint32_t phys_hi = get_phys (seg, (uint16_t) (off + 1));
+        
+        uint8_t low = mem_read8 (cpu->mem, cpu->mem_size, phys_lo);
+        uint8_t high = mem_read8 (cpu->mem, cpu->mem_size, phys_hi);
+        
+        uint16_t val = (uint16_t) low | ((uint16_t) high << 8);
+        return val;
+    
+    }
+    
+    return cpu->regs[op->reg & 7];
+
+}
+
+static void operand_write16 (CPU8086 *cpu, const OperandRef *op, uint16_t value) {
+
+    if (op->is_mem) {
+    
+        uint16_t seg = op->seg;
+        uint16_t off = (uint16_t) op->addr;
+        
+        mem_write8 (cpu->mem, cpu->mem_size, get_phys (seg, off), (uint8_t) (value & 0xFF));
+        mem_write8 (cpu->mem, cpu->mem_size, get_phys (seg, (uint16_t) (off + 1)), (uint8_t) (value >> 8));
+        
+        return;
+    
+    }
+    
+    cpu->regs[op->reg & 7] = value;
+
+}
+
+void cpu_reset (CPU8086 *cpu, uint8_t *mem, size_t mem_size) {
+
+    memset (cpu, 0, sizeof (*cpu));
+    
+    cpu->mem = mem;
+    cpu->mem_size = mem_size;
+    
+    /* Initial flags (Interrupts disabled, etc.) */
+    cpu->flags = 0x0002;
+
+}
+
+uint16_t cpu_get_reg (const CPU8086 *cpu, int reg) {
+    return cpu->regs[reg & 7];
+}
+
+void cpu_set_reg (CPU8086 *cpu, int reg, uint16_t value) {
+    cpu->regs[reg & 7] = value;
+}
+
+void cpu_dump (const CPU8086 *cpu) {
+
+    printf ("AX=%04X BX=%04X CX=%04X DX=%04X\n", cpu->regs[REG_AX], cpu->regs[REG_BX], cpu->regs[REG_CX], cpu->regs[REG_DX]);
+    printf ("SP=%04X BP=%04X SI=%04X DI=%04X IP=%04X FL=%04X\n", cpu->regs[REG_SP], cpu->regs[REG_BP], cpu->regs[REG_SI], cpu->regs[REG_DI], cpu->ip, cpu->flags);
+
+}
+
+static void dump_guest_bytes (CPU8086 *cpu, uint16_t seg, uint16_t off, uint16_t n, const char *tag) {
+
+    uint32_t phys = get_phys (seg, off);
+    uint16_t i;
+    
+    printf ("\n[%s] %04X%04X phys=%05X\n", tag, seg, off, (unsigned) phys);
+    
+    printf ("[BYTES] ");
+    
+    for (i = 0; i < n; i++) {
+        printf ("%02X ", mem_read8 (cpu->mem, cpu->mem_size, phys + i));
+    }
+    
+    printf ("\n[TEXT ] ");
+    
+    for (i = 0; i < n; i++) {
+    
+        unsigned char c = mem_read8 (cpu->mem, cpu->mem_size, phys + i);
+        putchar ((c >= 32 && c <= 126) ? c : '.');
+    
+    }
+    
+    printf ("\n");
+
+}
+
+int cpu_step (CPU8086 *cpu) {
+
+    uint8_t op;
+    int old_ip;
+    
+    if (cpu == NULL || cpu->halted) {
+        return 0;
+    }
+    
+    cpu->seg_override = 0;
+    cpu->has_seg_override = 0;
+    
+    cpu->has_addr_override = 0;
+    cpu->has_operand_override = 0;
+    cpu->rep_type = 0;                  /* 0=None, 1=REPE, 2=REPNZ */
+
+fetch_instruction:
+    
+    //printf ("%s: CS:IP: %04X:%04X\n", __FUNCTION__, cpu->cs, cpu->ip);
+    
+    old_ip = cpu->ip;
+    op = fetch8 (cpu);
+    
+    switch (op) {
+    
+        /* Arithmetic operations */
+        /* Arithmetic and Logic (ADD, OR, AND, SUB, XOR, CMP) */
+        case 0x00: case 0x01: case 0x02: case 0x03:         /* ADD */
+        case 0x08: case 0x09: case 0x0A: case 0x0B:         /* OR */
+        case 0x20: case 0x21: case 0x22: case 0x23:         /* AND */
+        case 0x28: case 0x29: case 0x2A: case 0x2B:         /* SUB */
+        case 0x30: case 0x31: case 0x32: case 0x33:         /* XOR */
+        case 0x38: case 0x39: case 0x3A: case 0x3B:         /* CMP */ {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            uint16_t dst, src;
+            
+            if (op == 0x39) {
+            
+                dst = operand_read16 (cpu, &rm);
+                src = cpu->regs[reg];
+            
+            } else {
+            
+                dst = cpu->regs[reg];
+                src = operand_read16 (cpu, &rm);
+            
+            }
+            
+            if (op == 0x00) {
+                operand_write8 (cpu, &rm, add8 (cpu, operand_read8 (cpu, &rm), get_reg8 (cpu, reg)));
+            } else if (op == 0x01) {
+                operand_write16 (cpu, &rm, add16 (cpu, operand_read16 (cpu, &rm), cpu->regs[reg]));
+            } else if (op == 0x02) {
+                set_reg8 (cpu, reg, add8 (cpu, get_reg8 (cpu, reg), operand_read8 (cpu, &rm)));
+            } else if (op == 0x03) {
+                cpu->regs[reg] = add16 (cpu, cpu->regs[reg], operand_read16 (cpu, &rm));
+            } else if (op == 0x08) {
+                operand_write8 (cpu, &rm, or8 (cpu, operand_read8 (cpu, &rm), get_reg8 (cpu, reg)));
+            } else if (op == 0x09) {
+                operand_write16 (cpu, &rm, or16 (cpu, operand_read16 (cpu, &rm), cpu->regs[reg]));
+            } else if (op == 0x0A) {
+                set_reg8 (cpu, reg, or8 (cpu, get_reg8 (cpu, reg), operand_read8 (cpu, &rm)));
+            } else if (op == 0x0B) {
+                cpu->regs[reg] = or16 (cpu, cpu->regs[reg], operand_read16 (cpu, &rm));
+            } else if (op == 0x20) {
+                operand_write8 (cpu, &rm, and8 (cpu, operand_read8 (cpu, &rm), get_reg8 (cpu, reg)));
+            } else if (op == 0x21) {
+                operand_write16 (cpu, &rm, and16 (cpu, operand_read16 (cpu, &rm), cpu->regs[reg]));
+            } else if (op == 0x22) {
+                set_reg8 (cpu, reg, and8 (cpu, get_reg8 (cpu, reg), operand_read8 (cpu, &rm)));
+            } else if (op == 0x23) {
+                cpu->regs[reg] = and16 (cpu, cpu->regs[reg], operand_read16 (cpu, &rm));
+            } else if (op == 0x28) {
+                
+                /* when handling prefix 0x26 */
+                operand_write8 (cpu, &rm, sub8 (cpu, operand_read8 (cpu, &rm), get_reg8 (cpu, reg)));
+            
+            } else if (op == 0x29) {
+                operand_write16 (cpu, &rm, sub16 (cpu, operand_read16 (cpu, &rm), cpu->regs[reg]));
+            } else if (op == 0x2A) {
+                set_reg8 (cpu, reg, sub8 (cpu, get_reg8 (cpu, reg), operand_read8 (cpu, &rm)));
+            } else if (op == 0x2B) {
+                cpu->regs[reg] = sub16 (cpu, cpu->regs[reg], operand_read16 (cpu, &rm));
+            } else if (op == 0x30) {
+                operand_write8 (cpu, &rm, xor8 (cpu, operand_read8 (cpu, &rm), get_reg8 (cpu, reg)));
+            } else if (op == 0x31) {
+                
+                uint16_t left = operand_read16 (cpu, &rm);
+                uint16_t right = cpu->regs[reg];
+                
+                operand_write16 (cpu, &rm, xor16 (cpu, left, right));
+            
+            } else if (op == 0x32) {
+                set_reg8 (cpu, reg, xor8 (cpu, get_reg8 (cpu, reg), operand_read8 (cpu, &rm)));
+            } else if (op == 0x33) {
+                cpu->regs[reg] = xor16 (cpu, cpu->regs[reg], operand_read16 (cpu, &rm));
+            } else if (op == 0x3B) {
+                
+                uint16_t rhs = operand_read16 (cpu, &rm);
+                uint16_t lhs = cpu->regs[reg];
+                
+                sub16 (cpu, lhs, rhs);
+            
+            } else if (op == 0x38) {
+                sub8 (cpu, operand_read8 (cpu, &rm), get_reg8 (cpu, reg));
+            } else if (op == 0x39) {
+                sub16 (cpu, dst, src);
+            } else if (op == 0x3A) {
+                sub8 (cpu, get_reg8 (cpu, reg), operand_read8 (cpu, &rm));
+            } else if (op == 0x3B) {
+                
+                uint16_t rhs = operand_read16 (cpu, &rm);
+                sub16 (cpu, cpu->regs[reg], rhs);
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x0C: {           /* OR AL, imm8 */
+        
+            uint8_t imm8 = fetch8 (cpu);
+            uint8_t al = get_reg8 (cpu, REG_AX);
+            
+            set_reg8 (cpu, REG_AX, or8 (cpu, al, imm8));
+            break;
+        
+        }
+        
+        case 0x0D: {           /* OR AX, imm16 */
+        
+            uint16_t imm16 = fetch16 (cpu);                 /* Fetch the 16-bit immediate value */
+            
+            cpu->regs[REG_AX] = or16 (cpu, cpu->regs[REG_AX], imm16);           /* Perform OR and update flags */
+            break;
+        
+        }
+        
+        case 0x10:  case 0x11:  case 0x12:  case 0x13: {    /* ADC */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            if (op == 0x10) {
+                operand_write8 (cpu, &rm, adc8 (cpu, operand_read8 (cpu, &rm), get_reg8 (cpu, reg)));
+            } else if (op == 0x11) {
+                operand_write16 (cpu, &rm, adc16 (cpu, operand_read16 (cpu, &rm), cpu->regs[reg]));
+            } else if (op == 0x12) {
+                set_reg8 (cpu, reg, adc8 (cpu, get_reg8 (cpu, reg), operand_read8 (cpu, &rm)));
+            } else if (op == 0x13) {
+                cpu->regs[reg] = adc16 (cpu, cpu->regs[reg], operand_read16 (cpu, &rm));
+            }
+            
+            break;
+        
+        }
+        
+        case 0x14: {
+        
+            uint8_t imm8 = fetch8 (cpu);
+            uint8_t al = get_reg8 (cpu, REG_AX);
+            
+            set_reg8 (cpu, REG_AX, adc8 (cpu, al, imm8));
+            break;
+        
+        }
+        
+        case 0x18: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            operand_write8 (cpu, &rm, sbb8 (cpu, operand_read8 (cpu, &rm), get_reg8 (cpu, reg)));
+            
+            break;
+        
+        }
+        
+        case 0x19: {           /* SBB r/m16, r16 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            /* The 'reg' field of ModRM (bits 5-3) selects the source register */
+            uint16_t src = cpu->regs[(modrm >> 3) & 7];
+            uint16_t dst = operand_read16 (cpu, &rm);
+            
+            /* Perform subtraction with borrow and update flags */
+            /* (Assuming you have a helper like sbb16 for flag logic) */
+            uint16_t result = sbb16 (cpu, dst, src);
+            
+            operand_write16 (cpu, &rm, result);
+            break;
+        
+        }
+        
+        case 0x1A: {           /* SBB r8, r/m8 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            set_reg8 (cpu, reg, sbb8 (cpu, get_reg8 (cpu, reg), operand_read8 (cpu, &rm)));
+            
+            break;
+        
+        }
+        
+        case 0x1B: {           /* SBB r16, r/m16 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            cpu->regs[reg] = sbb16 (cpu, cpu->regs[reg], operand_read16 (cpu, &rm));
+            
+            break;
+        
+        }
+        
+        case 0x1C: {           /* SBB AL, imm8 */
+        
+            uint8_t imm8 = fetch8 (cpu);
+            uint8_t al = get_reg8 (cpu, 0);
+            
+            set_reg8 (cpu, 0, sbb8 (cpu, al, imm8));
+            break;
+        
+        }
+        
+        case 0x1D: {           /* SBB AX, imm16 */
+        
+            uint16_t imm16 = fetch16 (cpu);
+            
+            cpu->regs[REG_AX] = sbb16 (cpu, cpu->regs[REG_AX], imm16);
+            break;
+        
+        }
+        
+        /* Additional Arithmetic and Logic Operations */
+        case 0x04:
+        
+            set_reg8 (cpu, 0, add8 (cpu, get_reg8 (cpu, 0), fetch8 (cpu)));
+            break;
+        
+        case 0x05:
+        
+            cpu->regs[REG_AX] = add16 (cpu, cpu->regs[REG_AX], fetch16 (cpu));
+            break;
+        
+        /* --- Segment Registers --- */
+        case 0x06:             /* PUSH ES */
+        
+            push16 (cpu, cpu->es);
+            break;
+        
+        case 0x07: {           /* POP ES */
+        
+            cpu->es = pop16 (cpu);
+            break;
+        
+        }
+        
+        case 0x0E:             /* PUSH CS */
+        
+            push16 (cpu, cpu->cs);
+            break;
+        
+        case 0x0F: {
+        
+            uint8_t op2 = fetch8 (cpu);
+            
+            switch (op2) {
+            
+                case 0xA0:
+                
+                    push16 (cpu, cpu->fs);
+                    break;
+                
+                case 0xA1:
+                
+                    cpu->fs = pop16 (cpu);
+                    break;
+                
+                case 0xA8:
+                
+                    push16 (cpu, cpu->gs);
+                    break;
+                
+                case 0xA9:
+                
+                    cpu->gs = pop16 (cpu);
+                    break;
+                
+                default:
+                
+                    printf ("Unhandled 0F opcode 0x%02X at %04X%04X\n", op2, cpu->cs, old_ip);
+                    return -1;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x15: {           /* ADC AX, imm16 */
+        
+            uint16_t imm16 = fetch16 (cpu);
+            
+            cpu->regs[REG_AX] = adc16 (cpu, cpu->regs[REG_AX], imm16);
+            break;
+        
+        }
+        
+        case 0x16:             /* PUSH SS */
+        
+            push16 (cpu, cpu->ss);
+            break;
+        
+        case 0x17:             /* POP SS */
+        
+            cpu->ss = pop16 (cpu);
+            break;
+        
+        case 0x1E:             /* PUSH DS */
+        
+            push16 (cpu, cpu->ds);
+            break;
+        
+        case 0x1F:             /* POP DS */
+        
+            cpu->ds = pop16 (cpu);
+            break;
+        
+        /* AND AL, imm8 */
+        case 0x24: {
+        
+            uint8_t imm8 = fetch8 (cpu);
+            uint8_t al = get_reg8 (cpu, REG_AX);            /* AL is the low byte of AX */
+            
+            set_reg8 (cpu, REG_AX, and8 (cpu, al, imm8));
+            break;
+        
+        }
+        
+        /* AND AX, imm16 */
+        case 0x25: {
+        
+            uint16_t imm16 = fetch16 (cpu);
+            
+            cpu->regs[REG_AX] = and16 (cpu, cpu->regs[REG_AX], imm16);
+            break;
+        
+        }
+        
+        case 0x26:
+            
+            cpu->seg_override = cpu->es;
+            cpu->has_seg_override = 1;
+            
+            goto fetch_instruction;
+        
+        case 0x2E:
+            
+            cpu->seg_override = cpu->cs;
+            cpu->has_seg_override = 1;
+            
+            goto fetch_instruction;
+        
+        case 0x36:
+            
+            cpu->seg_override = cpu->ss;
+            cpu->has_seg_override = 1;
+            
+            goto fetch_instruction;
+        
+        case 0x3E:
+            
+            cpu->seg_override = cpu->ds;
+            cpu->has_seg_override = 1;
+            
+            goto fetch_instruction;
+        
+        case 0x27:
+        
+            daa (cpu);
+            break;
+        
+        case 0x2F:
+        
+            das (cpu);
+            break;
+        
+        case 0x2C:
+        
+            set_reg8 (cpu, 0, sub8 (cpu, get_reg8 (cpu, 0), fetch8 (cpu)));
+            break;
+        
+        case 0x2D:
+        
+            cpu->regs[REG_AX] = sub16 (cpu, cpu->regs[REG_AX], fetch16 (cpu));
+            break;
+        
+        case 0x34:
+        
+            set_reg8 (cpu, 0, xor8 (cpu, get_reg8 (cpu, 0), fetch8 (cpu)));
+            break;
+        
+        case 0x35:
+        
+            cpu->regs[REG_AX] = xor16 (cpu, cpu->regs[REG_AX], fetch16 (cpu));
+            break;
+        
+        case 0x37:
+        
+            aaa (cpu);
+            break;
+        
+        case 0x3C: {           /* CMP AL, imm8 */
+        
+            uint8_t imm8 = fetch8 (cpu);
+            uint8_t al = get_reg8 (cpu, REG_AX);
+            
+            sub8 (cpu, al, imm8);       /* CMP performs subtraction but discards the result */
+            break;
+        
+        }
+        
+        case 0x3D: {
+        
+            uint16_t imm16 = fetch16 (cpu);
+            sub16 (cpu, cpu->regs[REG_AX], imm16);
+            
+            break;
+        
+        }
+        
+        /* Control Flow Operations */
+        case 0x40:  case 0x41:  case 0x42:  case 0x43:
+        case 0x44:  case 0x45:  case 0x46:  case 0x47: {
+        
+            int reg = op - 0x40;
+            
+            cpu->regs[reg] = inc16 (cpu, cpu->regs[reg]);   /* Use helper that preserves CF */
+            break;
+        
+        }
+        
+        case 0x48:  case 0x49:  case 0x4A:  case 0x4B:
+        case 0x4C:  case 0x4D:  case 0x4E:  case 0x4F: {
+        
+            int reg = op - 0x48;
+            
+            cpu->regs[reg] = dec16 (cpu, cpu->regs[reg]);
+            break;
+        
+        }
+        
+        case 0x50:  case 0x51:  case 0x52:  case 0x53:  case 0x54:  case 0x55:  case 0x57:
+        
+            push16 (cpu, cpu->regs[op - 0x50]);
+            break;
+        
+        case 0x56: {
+            
+            push16 (cpu, cpu->regs[REG_SI]);
+            break;
+        
+        }
+        
+        case 0x58:  case 0x59:  case 0x5A:  case 0x5B:
+        case 0x5C:  case 0x5D:  case 0x5E:  case 0x5F:
+        
+            cpu->regs[op - 0x58] = pop16 (cpu);
+            break;
+        
+        case 0x61: {           /* POPA (Pop All - 80186+) */
+        
+            cpu->regs[REG_DI] = pop16 (cpu);
+            cpu->regs[REG_SI] = pop16 (cpu);
+            cpu->regs[REG_BP] = pop16 (cpu);
+            
+            /* Increment SP past the stored SP value without loading it */
+            pop16 (cpu);
+            
+            cpu->regs[REG_BX] = pop16 (cpu);
+            cpu->regs[REG_DX] = pop16 (cpu);
+            cpu->regs[REG_CX] = pop16 (cpu);
+            cpu->regs[REG_AX] = pop16 (cpu);
+            
+            break;
+        
+        }
+        
+        case 0x60: {           /* PUSHA (80186+) */
+        
+            uint16_t temp_sp = cpu->regs[REG_SP];
+            
+            push16 (cpu, cpu->regs[REG_AX]);
+            push16 (cpu, cpu->regs[REG_CX]);
+            push16 (cpu, cpu->regs[REG_DX]);
+            push16 (cpu, cpu->regs[REG_BX]);
+            push16 (cpu, temp_sp);
+            push16 (cpu, cpu->regs[REG_BP]);
+            push16 (cpu, cpu->regs[REG_SI]);
+            push16 (cpu, cpu->regs[REG_DI]);
+            
+            break;
+        
+        }
+        
+        case 0x62: {           /* BOUND r16, m16&16 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg_idx = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            if (!rm.is_mem) {
+            
+                /* BOUND requires a memory operand for the bounds pair */
+                return -1;
+            
+            }
+            
+            {
+            
+                uint16_t index = cpu->regs[reg_idx];
+                uint16_t seg = rm.seg;
+                
+                /* Read the bounds from memory */
+                int16_t low_bound  = (int16_t) mem_read16 (cpu->mem, cpu->mem_size, get_phys (seg, rm.addr));
+                int16_t upper_bound = (int16_t) mem_read16 (cpu->mem, cpu->mem_size, get_phys (seg, rm.addr + 2));
+                
+                /* Perform signed comparison */
+                if ((int16_t) index < low_bound || (int16_t) index > upper_bound) {
+                
+                    /* Trigger Interrupt 5 (Bound Range Exceeded) */
+                    /* Note: The saved IP should point to the START of the instruction (old_ip) */
+                    cpu->ip = old_ip;
+                    handle_interrupt (cpu, 5);
+                
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x63: {           /* ARPL r/m16, r16 (80286+ Protected Mode instruction) */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg_src = (modrm >> 3) & 7;                 /* The source register */
+            
+            OperandRef rm_dest = decode_rm_operand (cpu, modrm);
+            
+            uint16_t dest_val = operand_read16 (cpu, &rm_dest);
+            uint16_t src_val = cpu->regs[reg_src];
+            
+            /* Check the RPL (bits 0-1) */
+            if ((dest_val & 0x0003) < (src_val & 0x0003)) {
+            
+                /* If dest RPL < src RPL, set dest RPL to src RPL and set ZF */
+                dest_val = (dest_val & 0xFFFC) | (src_val & 0x0003);
+                
+                operand_write16 (cpu, &rm_dest, dest_val);
+                cpu->flags |= FLAG_ZF;
+            
+            } else {
+            
+                /* Otherwise, just clear ZF */
+                cpu->flags &= (uint16_t) ~FLAG_ZF;
+            
+            }
+            
+            break;
+        
+        }
+        
+        /* Change lines 654-655 to: */
+        case 0x64:
+            
+            cpu->seg_override = cpu->fs;
+            cpu->has_seg_override = 1;
+            
+            goto fetch_instruction;
+        
+        case 0x65:
+            
+            cpu->seg_override = cpu->gs;
+            cpu->has_seg_override = 1;
+            
+            goto fetch_instruction;
+        
+        case 0x66:
+        
+            cpu->has_operand_override = 1;
+            goto fetch_instruction;
+        
+        case 0x67:
+        
+            cpu->has_addr_override = 1;
+            goto fetch_instruction;
+        
+        case 0x68: {           /* PUSH imm16 (80186+) */
+        
+            uint16_t imm16 = fetch16 (cpu);
+            
+            push16 (cpu, imm16);
+            break;
+        
+        }
+        
+        case 0x6A: {           /* PUSH imm8 (80186+) */
+        
+            /* Sign-extend the 8-bit immediate to 16-bits before pushing */
+            int16_t imm8 = (int8_t) fetch8 (cpu);
+            
+            push16 (cpu, (uint16_t) imm8);
+            break;
+        
+        }
+        
+        case 0x69: /* IMUL r16, r/m16, imm16 */     case 0x6B: /* IMUL r16, r/m16, imm8 (sign-extended) */ {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg_dest = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            int16_t src_val = (int16_t) operand_read16 (cpu, &rm);
+            int16_t imm;
+            
+            if (op == 0x69) {
+                imm = (int16_t) fetch16 (cpu);
+            } else {
+            
+                /* Sign-extend the 8-bit immediate to 16 bits */
+                imm = (int16_t) (int8_t) fetch8 (cpu);
+            
+            }
+            
+            {
+            
+                /* Perform 32-bit signed multiplication to check for overflow */
+                int32_t result = (int32_t) src_val * (int32_t) imm;
+                
+                /* Store the truncated 16-bit result in the destination register */
+                cpu->regs[reg_dest] = (uint16_t) result;
+                
+                /* Update Flags: CF and OF are set if the result doesn't fit in 16 bits */
+                if (result < -32768 || result > 32767) {
+                    cpu->flags |= (FLAG_CF | FLAG_OF);
+                } else {
+                    cpu->flags &= (uint16_t) ~(FLAG_CF | FLAG_OF);
+                }
+            
+            }
+            
+            /* SF, ZF, AF, and PF are technically undefined for IMUL, */
+            /* but many emulators leave them unchanged or clear them. */
+            break;
+        }
+        
+        case 0x6C: /* INSB */   case 0x6D: /* INSW */ {
+        
+            int step = (cpu->flags & FLAG_DF) ? -1 : 1;
+            uint16_t orig = cpu->regs[REG_DI];
+            
+            while (1) {
+            
+                uint32_t phys;
+                
+                if (cpu->rep_type != 0 && cpu->regs[REG_CX] == 0) {
+                    break;
+                }
+                
+                phys = get_phys (cpu->es, cpu->regs[REG_DI]);
+                
+                if (op == 0x6C) {
+                
+                    uint8_t val = io_read8 (cpu, cpu->regs[REG_DX]);
+                    
+                    mem_write8 (cpu->mem, cpu->mem_size, phys, val);
+                    cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+                
+                } else {
+                
+                    uint16_t val = io_read16 (cpu, cpu->regs[REG_DX]);
+                    
+                    mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, cpu->regs[REG_DI]), (uint8_t) (val & 0xFF));
+                    mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, (uint16_t) (cpu->regs[REG_DI] + 1)), (uint8_t) (val >> 8));
+                    
+                    cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + (step * 2));
+                
+                }
+                
+                if (cpu->rep_type != 0) {
+                    cpu->regs[REG_CX]--;
+                } else {
+                    break;
+                }
+            
+            }
+            
+            dump_guest_bytes (cpu, cpu->es, orig, 96, "POST-IN BUFFER");
+            break;
+        
+        }
+        
+        case 0x6E: /* OUTSB */      case 0x6F: /* OUTSW */ {
+        
+            uint16_t seg = cpu->has_seg_override ? cpu->seg_override : cpu->ds;
+            int step = (cpu->flags & FLAG_DF) ? -1 : 1;
+            
+            uint16_t orig = cpu->regs[REG_SI];
+            
+            while (1) {
+            
+                uint32_t phys;
+                
+                if (cpu->rep_type != 0 && cpu->regs[REG_CX] == 0) {
+                    break;
+                }
+                
+                phys = get_phys (seg, cpu->regs[REG_SI]);
+                
+                if (op == 0x6E) {
+                
+                    uint8_t val = mem_read8 (cpu->mem, cpu->mem_size, phys);
+                    
+                    io_write8 (cpu, cpu->regs[REG_DX], val);
+                    cpu->regs[REG_SI] += step;
+                
+                } else {
+                
+                    /* Explicitly read bytes to enforce segment wrapping */
+                    uint8_t low = mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, cpu->regs[REG_SI]));
+                    uint8_t high = mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, (uint16_t) (cpu->regs[REG_SI] + 1)));
+                    uint16_t val = (uint16_t) low | ((uint16_t) high << 8);
+                    
+                    io_write16 (cpu, cpu->regs[REG_DX], val);
+                    cpu->regs[REG_SI] += (step * 2);
+                
+                }
+                
+                if (cpu->rep_type != 0) {
+                    cpu->regs[REG_CX]--;
+                } else {
+                    break;
+                }
+            
+            }
+            
+            dump_guest_bytes (cpu, seg, orig, 96, "POST-OUT BUFFER");
+            break;
+        
+        }
+        
+        case 0x70:  case 0x71:  case 0x72:  case 0x73:
+        case 0x74:  case 0x75:  case 0x76:  case 0x77:
+        case 0x78:  case 0x79:  case 0x7A:  case 0x7B:
+        case 0x7C:  case 0x7D:  case 0x7E:  case 0x7F: {
+        
+            int8_t rel = (int8_t) fetch8 (cpu);
+            
+            uint16_t next_ip = cpu->ip;
+            uint16_t target = (uint16_t) (next_ip + rel);
+            
+            int take = 0;
+            
+            switch (op) {
+            
+                case 0x70:
+                
+                    take = (cpu->flags & FLAG_OF) != 0;
+                    break;
+                
+                case 0x71:
+                
+                    take = (cpu->flags & FLAG_OF) == 0;
+                    break;
+                
+                case 0x72:
+                
+                    take = (cpu->flags & FLAG_CF) != 0;
+                    break;
+                
+                case 0x73:
+                
+                    take = (cpu->flags & FLAG_CF) == 0;
+                    break;
+                
+                case 0x74:
+                
+                    take = (cpu->flags & FLAG_ZF) != 0;
+                    break;
+                
+                case 0x75:
+                
+                    take = (cpu->flags & FLAG_ZF) == 0;
+                    break;
+                
+                case 0x76:
+                
+                    take = ((cpu->flags & FLAG_CF) != 0) || ((cpu->flags & FLAG_ZF) != 0);
+                    break;
+                
+                case 0x77:
+                
+                    take = ((cpu->flags & FLAG_CF) == 0) && ((cpu->flags & FLAG_ZF) == 0);
+                    break;
+                
+                case 0x78:
+                
+                    take = (cpu->flags & FLAG_SF) != 0;
+                    break;
+                
+                case 0x79:
+                
+                    take = (cpu->flags & FLAG_SF) == 0;
+                    break;
+                
+                case 0x7A:
+                
+                    take = (cpu->flags & FLAG_PF) != 0;
+                    break;
+                
+                case 0x7B:
+                
+                    take = (cpu->flags & FLAG_PF) == 0;
+                    break;
+                
+                case 0x7C:
+                
+                    take = (((cpu->flags & FLAG_SF) != 0) != ((cpu->flags & FLAG_OF) != 0));
+                    break;
+                
+                case 0x7D:
+                
+                    take = (((cpu->flags & FLAG_SF) != 0) == ((cpu->flags & FLAG_OF) != 0));
+                    break;
+                
+                case 0x7E:
+                
+                    take = ((cpu->flags & FLAG_ZF) != 0) ||
+                        (((cpu->flags & FLAG_SF) != 0) != ((cpu->flags & FLAG_OF) != 0));
+                    break;
+                
+                case 0x7F:
+                
+                    take = ((cpu->flags & FLAG_ZF) == 0) &&
+                        (((cpu->flags & FLAG_SF) != 0) == ((cpu->flags & FLAG_OF) != 0));
+                    break;
+            
+            }
+            
+            if (take) {
+                cpu->ip = target;
+            }
+            
+            break;
+        
+        }
+        
+        /* Bitwise and Immediate Operations */
+        case 0x80:  case 0x81:  case 0x82:  case 0x83: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            if (op == 0x80 || op == 0x82) {
+            
+                uint8_t imm8 = fetch8 (cpu);
+                uint8_t left = operand_read8 (cpu, &rm);
+                
+                switch (subop) {
+                
+                    case 0:
+                    
+                        operand_write8 (cpu, &rm, add8 (cpu, left, imm8));
+                        break;
+                    
+                    case 1:
+                    
+                        operand_write8 (cpu, &rm, or8 (cpu, left, imm8));
+                        break;
+                    
+                    case 2:
+                    
+                        operand_write8 (cpu, &rm, adc8 (cpu, left, imm8));
+                        break;
+                    
+                    case 3:
+                    
+                        operand_write8 (cpu, &rm, sbb8 (cpu, left, imm8));
+                        break;
+                    
+                    case 4:
+                    
+                        operand_write8 (cpu, &rm, and8 (cpu, left, imm8));
+                        break;
+                    
+                    case 5:
+                    
+                        operand_write8 (cpu, &rm, sub8 (cpu, left, imm8));
+                        break;
+                    
+                    case 6:
+                    
+                        operand_write8 (cpu, &rm, xor8 (cpu, left, imm8));
+                        break;
+                    
+                    case 7:
+                    
+                        (void) sub8 (cpu, left, imm8);
+                        break;
+                
+                }
+            
+            } else {
+            
+                uint16_t imm16;
+                uint16_t left = operand_read16 (cpu, &rm);
+                
+                if (op == 0x81) {
+                    imm16 = fetch16 (cpu);
+                } else {
+                    imm16 = (uint16_t) (int16_t) (int8_t) fetch8 (cpu);
+                }
+                
+                switch (subop) {
+                
+                    case 0: {
+                    
+                        uint16_t result = add16 (cpu, left, imm16);
+                        operand_write16 (cpu, &rm, result);
+                        
+                        break;
+                    }
+                    
+                    case 1:
+                    
+                        operand_write16 (cpu, &rm, or16 (cpu, left, imm16));
+                        break;
+                    
+                    case 2:
+                    
+                        operand_write16 (cpu, &rm, adc16 (cpu, left, imm16));
+                        break;
+                    
+                    case 3:
+                    
+                        operand_write16 (cpu, &rm, sbb16 (cpu, left, imm16));
+                        break;
+                    
+                    case 4:
+                    
+                        operand_write16 (cpu, &rm, and16 (cpu, left, imm16));
+                        break;
+                    
+                    case 5:
+                    
+                        operand_write16 (cpu, &rm, sub16 (cpu, left, imm16));
+                        break;
+                    
+                    case 6:
+                    
+                        operand_write16 (cpu, &rm, xor16 (cpu, left, imm16));
+                        break;
+                    
+                    /* opcode 83 /7: CMP r/m16, imm8 */
+                    case 7: {
+                    
+                        uint16_t lhs = left;
+                        int16_t rhs = imm16;
+                        
+                        sub16 (cpu, lhs, (uint16_t) rhs);
+                        break;
+                    
+                    }
+                
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x84: {           /* TEST r/m8, r8: */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            if (op == 0x84) {
+            
+                uint8_t val1 = operand_read8 (cpu, &rm);
+                uint8_t val2 = get_reg8 (cpu, reg);
+                
+                and8 (cpu, val1, val2);                     /* Result is discarded, only flags are set */
+            
+            } else {
+            
+                uint16_t val1 = operand_read16 (cpu, &rm);
+                uint16_t val2 = cpu->regs[reg & 7];
+                
+                and16 (cpu, val1, val2); /* Result is discarded */
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x85: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            uint16_t val1 = operand_read16 (cpu, &rm);
+            uint16_t val2 = cpu->regs[reg & 7];
+            
+            and16 (cpu, val1, val2);
+            break;
+        
+        }
+        
+        case 0x86: /* XCHG r/m8, r8 */      case 0x87: /* XCHG r/m16, r16 */ {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            if (op == 0x86) {
+            
+                uint8_t val1 = get_reg8 (cpu, reg);
+                uint8_t val2 = operand_read8 (cpu, &rm);
+                
+                set_reg8 (cpu, reg, val2);
+                operand_write8 (cpu, &rm, val1);
+            
+            } else {
+                uint16_t val1 = cpu->regs[reg];
+                uint16_t val2 = operand_read16 (cpu, &rm);
+                
+                cpu->regs[reg] = val2;
+                operand_write16 (cpu, &rm, val1);
+            
+            }
+            
+            break;
+        
+        }
+        
+        /* Register-Memory and Immediate Operations */
+        case 0x88: {           /* MOV r/m8, r8 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int src = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            uint8_t v = get_reg8 (cpu, src);
+            
+            if (rm.is_mem) {
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (rm.seg, (uint16_t) rm.addr), v);
+            } else {
+                set_reg8 (cpu, rm.reg, v);
+            }
+            
+            break;
+        
+        }
+        
+        case 0x8A: {           /* MOV r8, r/m8 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            uint8_t value;
+            
+            if (rm.is_mem) {
+                value = mem_read8 (cpu->mem, cpu->mem_size, get_phys (rm.seg, rm.addr));
+            } else {
+                value = get_reg8 (cpu, rm.reg);
+            }
+            
+            set_reg8 (cpu, (modrm >> 3) & 7, value);
+            break;
+        
+        }
+        
+        case 0x89:      case 0x8B: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            if (op == 0x89) {
+            
+                uint16_t val = cpu->regs[reg];
+                operand_write16 (cpu, &rm, val);
+            
+            } else {
+            
+                uint16_t val = operand_read16 (cpu, &rm);
+                cpu->regs[reg] = val;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x8C: {           /* MOV r/m16, Sreg */
+        
+            uint8_t modrm = fetch8 (cpu);
+            uint8_t sreg_idx = (modrm >> 3) & 0x07;         /* ONLY 0–3 */
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            uint16_t val = 0;
+            
+            switch (sreg_idx) {
+            
+                case 0:
+                
+                    val = cpu->es;
+                    break;
+                
+                case 1:
+                
+                    val = cpu->cs;
+                    break;
+                
+                case 2:
+                
+                    val = cpu->ss;
+                    break;
+                
+                case 3:
+                
+                    val = cpu->ds;
+                    break;
+                
+                case 4:
+                
+                    val = cpu->fs;
+                    break;
+                
+                case 5:
+                
+                    val = cpu->gs;
+                    break;
+            
+            }
+            
+            operand_write16 (cpu, &rm, val);
+            break;
+        
+        }
+        
+        case 0x8D: {           /* LEA r16, m */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            if (!rm.is_mem) {
+            
+                /* LEA technically requires a memory operand to calculate an address. */
+                /* On a real CPU, if mod=3 (register), the behavior is often undefined */
+                /* or results in an invalid opcode exception. */
+                return -1;
+            
+            }
+            
+            /* Simply load the calculated offset into the register. */
+            /* Note: LEA ignores segment registers; it only cares about the offset. */
+            cpu->regs[reg & 7] = (uint16_t) rm.addr;
+            break;
+        
+        }
+        
+        case 0x8E: {           /* MOV Sreg, r/m16 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int sreg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            uint16_t value = operand_read16 (cpu, &rm);
+            
+            switch (sreg) {
+            
+                case 0:
+                
+                    cpu->es = value;
+                    break;
+                
+                case 2:
+                
+                    cpu->ss = value;
+                    break;
+                
+                case 3:
+                
+                    cpu->ds = value;
+                    break;
+                
+                case 4:
+                
+                    cpu->fs = value;
+                    break;
+                
+                case 5:
+                
+                    cpu->gs = value;
+                    break;
+                
+                case 1:
+                
+                    printf ("[INVALID MOV CS] CS:IP=%04X%04X modrm=%02X value=%04X\n", cpu->cs, old_ip, modrm, value);
+                    return -1;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x8F: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            
+            OperandRef rm;
+            uint16_t value;
+            
+            /* only /0 is valid for POP r/m16 on 8086 */
+            if (((modrm >> 3) & 7) != 0) {
+            
+                printf ("Unhandled 0x8F /%d at %04X\n", (modrm >> 3) & 7, old_ip);
+                return -1;
+            
+            }
+            
+            rm = decode_rm_operand (cpu, modrm);
+            value = pop16 (cpu);
+            
+            operand_write16 (cpu, &rm, value);
+            break;
+        
+        }
+        
+        case 0x90:             /* NOP (XCHG AX, AX) */
+        
+            break;
+        
+        case 0x91:  case 0x92:  case 0x93:  case 0x94:  case 0x95:  case 0x96:  case 0x97: {
+        
+            /* XCHG AX, reg16 */
+            int reg = op - 0x90;
+            uint16_t temp = cpu->regs[REG_AX];
+            
+            cpu->regs[REG_AX] = cpu->regs[reg];
+            cpu->regs[reg] = temp;
+            
+            break;
+        
+        }
+        
+        case 0x98: {           /* CBW - Convert Byte to Word */
+        
+            /* Sign-extend AL into AX */
+            if (cpu->regs[REG_AX] & 0x80) {
+                cpu->regs[REG_AX] |= 0xFF00;
+            } else {
+                cpu->regs[REG_AX] &= 0x00FF;
+            }
+            
+            break;
+        
+        }
+        
+        case 0x99:             /* CWD - Convert Word to Doubleword */
+        
+            if (cpu->regs[REG_AX] & 0x8000) {
+                cpu->regs[REG_DX] = 0xFFFF;
+            } else {
+                cpu->regs[REG_DX] = 0x0000;
+            }
+            
+            break;
+        
+        case 0x9A: {
+        
+            uint16_t new_ip = fetch16 (cpu);
+            uint16_t new_cs = fetch16 (cpu);
+            
+            push16 (cpu, cpu->cs);
+            push16 (cpu, cpu->ip);
+            
+            cpu->cs = new_cs;
+            cpu->ip = new_ip;
+            
+            break;
+        
+        }
+        
+        case 0x9C:             /* PUSHF (Push Flags onto Stack) */
+        
+            push16 (cpu, cpu->flags);
+            break;
+        
+        case 0x9D: {           /* POPF */
+        
+            uint16_t f = pop16 (cpu);
+            cpu->flags = f & (FLAG_CF | FLAG_PF | FLAG_AF | FLAG_ZF | FLAG_SF | FLAG_IF | FLAG_DF | FLAG_OF);
+            
+            break;
+        
+        }
+        
+        case 0x9E: {           /* SAHF */
+        
+            uint8_t ah = get_reg8 (cpu, 4);
+            cpu->flags &= ~(FLAG_CF | FLAG_PF | FLAG_AF | FLAG_ZF | FLAG_SF);
+            
+            if (ah & 0x01) {
+                cpu->flags |= FLAG_CF;
+            }
+            
+            if (ah & 0x04) {
+                cpu->flags |= FLAG_PF;
+            }
+            
+            if (ah & 0x10) {
+                cpu->flags |= FLAG_AF;
+            }
+            
+            if (ah & 0x40) {
+                cpu->flags |= FLAG_ZF;
+            }
+            
+            if (ah & 0x80) {
+                cpu->flags |= FLAG_SF;
+            }
+            
+            cpu->flags |= 0x0002; /* bit 1 always set */
+            break;
+        
+        }
+        
+        case 0x9F: {           /* LAHF */
+        
+            uint8_t ah = 0x02; /* bit 1 always set */
+            
+            if (cpu->flags & FLAG_CF) {
+                ah |= 0x01;
+            }
+            
+            if (cpu->flags & FLAG_PF) {
+                ah |= 0x04;
+            }
+            
+            if (cpu->flags & FLAG_AF) {
+                ah |= 0x10;
+            }
+            
+            if (cpu->flags & FLAG_ZF) {
+                ah |= 0x40;
+            }
+            
+            if (cpu->flags & FLAG_SF) {
+                ah |= 0x80;
+            }
+            
+            set_reg8 (cpu, 4, ah); /* AH */
+            break;
+        
+        }
+        
+        /* Inside cpu_step switch (op) in cpu.c */
+        case 0xA0: /* MOV AL, moffs8 */     case 0xA1: /* MOV AX, moffs16 */ {
+        
+            uint16_t offset = fetch16 (cpu);
+            uint16_t seg = cpu->has_seg_override ? cpu->seg_override : cpu->ds;
+            uint32_t phys = get_phys (seg, offset);
+            
+            if (op == 0xA0) {
+                set_reg8 (cpu, REG_AX, mem_read8 (cpu->mem, cpu->mem_size, phys));
+            } else {
+            
+                /* Read the word as two separate bytes to enforce segment wrapping */
+                uint8_t low = mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, offset));
+                uint8_t high = mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, offset + 1));
+                
+                cpu->regs[REG_AX] = (uint16_t) low | ((uint16_t) high << 8);
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xA2: /* MOV moffs8, AL */     case 0xA3: /* MOV moffs16, AX */ {
+        
+            uint16_t offset = fetch16 (cpu);
+            uint16_t seg = cpu->has_seg_override ? cpu->seg_override : cpu->ds;
+            uint32_t phys = get_phys (seg, offset);
+            
+            if (op == 0xA2) {
+                mem_write8 (cpu->mem, cpu->mem_size, phys, get_reg8 (cpu, REG_AX));
+            } else {
+            
+                /* Write the word as two separate bytes to enforce segment wrapping */
+                uint16_t val = cpu->regs[REG_AX];
+                
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (seg, offset), (uint8_t) (val & 0xFF));
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (seg, offset + 1), (uint8_t) (val >> 8));
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xA4: {           /* MOVSB */
+        
+            uint16_t src_seg = cpu->has_seg_override ? cpu->seg_override : cpu->ds;
+            int step = (cpu->flags & FLAG_DF) ? -1 : 1;
+            
+            uint16_t count = rep_initial_count (cpu);
+            uint16_t i;
+            
+            for (i = 0; i < count; i++) {
+            
+                uint8_t v = mem_read8 (cpu->mem, cpu->mem_size, get_phys (src_seg, cpu->regs[REG_SI]));
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, cpu->regs[REG_DI]), v);
+                
+                cpu->regs[REG_SI] = (uint16_t) (cpu->regs[REG_SI] + step);
+                cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+            
+            }
+            
+            if (rep_active (cpu)) {
+                cpu->regs[REG_CX] = 0;
+            }
+            
+            break;
+        
+        }
+        
+        case 0xA5: {           /* MOVSW */
+        
+            uint16_t src_seg = cpu->has_seg_override ? cpu->seg_override : cpu->ds;
+            int step = (cpu->flags & FLAG_DF) ? -2 : 2;
+            
+            uint16_t count = rep_initial_count (cpu);
+            uint16_t i;
+            
+            for (i = 0; i < count; i++) {
+            
+                uint8_t lo = mem_read8 (cpu->mem, cpu->mem_size, get_phys (src_seg, cpu->regs[REG_SI]));
+                uint8_t hi = mem_read8 (cpu->mem, cpu->mem_size, get_phys (src_seg, (uint16_t) (cpu->regs[REG_SI] + 1)));
+                
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, cpu->regs[REG_DI]), lo);
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, (uint16_t) (cpu->regs[REG_DI] + 1)), hi);
+                
+                cpu->regs[REG_SI] = (uint16_t) (cpu->regs[REG_SI] + step);
+                cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+            
+            }
+            
+            if (rep_active (cpu)) {
+                cpu->regs[REG_CX] = 0;
+            }
+            
+            break;
+        
+        }
+        
+        case 0xA6: {           /* CMPSB */
+        
+            uint16_t src_seg = cpu->has_seg_override ? cpu->seg_override : cpu->ds;
+            int step = (cpu->flags & FLAG_DF) ? -1 : 1;
+            
+            uint16_t count = rep_initial_count (cpu);
+            
+            while (count--) {
+            
+                uint8_t lhs = mem_read8 (cpu->mem, cpu->mem_size, get_phys (src_seg, cpu->regs[REG_SI]));
+                uint8_t rhs = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, cpu->regs[REG_DI]));
+                
+                sub8 (cpu, lhs, rhs);
+                
+                cpu->regs[REG_SI] = (uint16_t) (cpu->regs[REG_SI] + step);
+                cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+                
+                if (rep_active (cpu)) {
+                
+                    cpu->regs[REG_CX]--;
+                    
+                    if (cpu->rep_type == 1) {               /* REPE/REPZ */
+                    
+                        if (!(cpu->flags & FLAG_ZF)) {
+                            break;
+                        }
+                    
+                    } else if (cpu->rep_type == 2) {        /* REPNE/REPNZ */
+                    
+                        if (cpu->flags & FLAG_ZF) {
+                            break;
+                        }
+                    
+                    }
+                    
+                    if (cpu->regs[REG_CX] == 0) {
+                        break;
+                    }
+                
+                } else {
+                    break;
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xA7: {           /* CMPSW */
+        
+            uint16_t src_seg = cpu->has_seg_override ? cpu->seg_override : cpu->ds;
+            int step = (cpu->flags & FLAG_DF) ? -2 : 2;
+            
+            uint16_t count = rep_initial_count (cpu);
+            
+            while (count--) {
+            
+                uint8_t llo = mem_read8 (cpu->mem, cpu->mem_size, get_phys (src_seg, cpu->regs[REG_SI]));
+                uint8_t lhi = mem_read8 (cpu->mem, cpu->mem_size, get_phys (src_seg, (uint16_t) (cpu->regs[REG_SI] + 1)));
+                uint8_t rlo = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, cpu->regs[REG_DI]));
+                uint8_t rhi = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, (uint16_t) (cpu->regs[REG_DI] + 1)));
+                
+                uint16_t lhs = (uint16_t) llo | ((uint16_t) lhi << 8);
+                uint16_t rhs = (uint16_t) rlo | ((uint16_t) rhi << 8);
+                
+                sub16 (cpu, lhs, rhs);
+                
+                cpu->regs[REG_SI] = (uint16_t) (cpu->regs[REG_SI] + step);
+                cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+                
+                if (rep_active (cpu)) {
+                
+                    cpu->regs[REG_CX]--;
+                    
+                    if (cpu->rep_type == 1) {
+                    
+                        if (!(cpu->flags & FLAG_ZF)) {
+                            break;
+                        }
+                    
+                    } else if (cpu->rep_type == 2) {
+                    
+                        if (cpu->flags & FLAG_ZF) {
+                            break;
+                        }
+                    
+                    }
+                    
+                    if (cpu->regs[REG_CX] == 0) {
+                        break;
+                    }
+                
+                } else {
+                    break;
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xA8: {           /* TEST AL, imm8 */
+        
+            uint8_t imm8 = fetch8 (cpu);
+            
+            and8 (cpu, get_reg8 (cpu, REG_AX), imm8);
+            break;
+        
+        }
+        
+        case 0xA9: {           /* TEST AX, imm16 */
+        
+            uint16_t imm16 = fetch16 (cpu);
+            
+            and16 (cpu, cpu->regs[REG_AX], imm16);
+            break;
+        
+        }
+        
+        case 0xAA: {           /* STOSB */
+        
+            int step = (cpu->flags & FLAG_DF) ? -1 : 1;
+            uint16_t val = cpu->regs[REG_AX];
+            
+            if (cpu->rep_type && cpu->regs[REG_CX] == 0) {
+                break;
+            }
+            
+            while (1) {
+            
+                if (cpu->rep_type != 0 && cpu->regs[REG_CX] == 0) {
+                    break;
+                }
+                
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, cpu->regs[REG_DI]), (uint8_t) (val & 0xFF));
+                cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+                
+                if (cpu->rep_type != 0) {
+                    cpu->regs[REG_CX]--;
+                } else {
+                    break;
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xAB: {           /* STOSW */
+        
+            int step = (cpu->flags & FLAG_DF) ? -2 : 2;
+            uint16_t val = cpu->regs[REG_AX];
+            
+            if (cpu->rep_type && cpu->regs[REG_CX] == 0) {
+                break;
+            }
+            
+            while (1) {
+            
+                if (cpu->rep_type != 0 && cpu->regs[REG_CX] == 0) {
+                    break;
+                }
+                
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, cpu->regs[REG_DI]), (uint8_t) (val & 0xFF));
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, (uint16_t) (cpu->regs[REG_DI] + 1)), (uint8_t) (val >> 8));
+                
+                cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+                
+                if (cpu->rep_type != 0) {
+                    cpu->regs[REG_CX]--;
+                } else {
+                    break;
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xAC: /* LODSB */      case 0xAD: /* LODSW */ {
+        
+            uint16_t seg = cpu->has_seg_override ? cpu->seg_override : cpu->ds;
+            int step = (cpu->flags & FLAG_DF) ? ((op == 0xAC) ? -1 : -2) : ((op == 0xAC) ?  1 :  2);
+            
+            uint16_t count = rep_initial_count (cpu);
+            
+            while (count--) {
+            
+                if (op == 0xAC) {
+                
+                    uint8_t v = mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, cpu->regs[REG_SI]));
+                    set_al (cpu, v);
+                
+                } else {
+                
+                    uint8_t lo = mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, cpu->regs[REG_SI]));
+                    uint8_t hi = mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, (uint16_t) (cpu->regs[REG_SI] + 1)));
+                    
+                    cpu->regs[REG_AX] = (uint16_t) lo | ((uint16_t) hi << 8);
+                
+                }
+                
+                cpu->regs[REG_SI] = (uint16_t) (cpu->regs[REG_SI] + step);
+                
+                if (rep_active (cpu)) {
+                
+                    cpu->regs[REG_CX]--;
+                    
+                    if (cpu->regs[REG_CX] == 0) {
+                        break;
+                    }
+                
+                } else {
+                    break;
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xAE: {           /* SCASB */
+        
+            int step = (cpu->flags & FLAG_DF) ? -1 : 1;
+            uint8_t al = get_al (cpu);
+            
+            if (!rep_active (cpu)) {
+            
+                uint8_t v = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, cpu->regs[REG_DI]));
+                sub8 (cpu, al, v);
+                
+                cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+                break;
+            
+            }
+            
+            while (cpu->regs[REG_CX] != 0) {
+            
+                uint8_t v = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, cpu->regs[REG_DI]));
+                sub8 (cpu, al, v);
+                
+                cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+                cpu->regs[REG_CX]--;
+                
+                if (cpu->rep_type == 1) {                   /* REPE/REPZ */
+                
+                    if (!(cpu->flags & FLAG_ZF)) {
+                        break;
+                    }
+                
+                } else if (cpu->rep_type == 2) {            /* REPNE/REPNZ */
+                
+                    if (cpu->flags & FLAG_ZF) {
+                        break;
+                    }
+                
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xAF: {           /* SCASW */
+        
+            int step = (cpu->flags & FLAG_DF) ? -2 : 2;
+            
+            if (cpu->rep_type) {
+            
+                while (cpu->regs[REG_CX] != 0) {
+                
+                    uint16_t off = cpu->regs[REG_DI];
+                    uint16_t v = (uint16_t) mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, off)) | ((uint16_t) mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, (uint16_t) (off + 1))) << 8);
+                    
+                    sub16 (cpu, cpu->regs[REG_AX], v);
+                    
+                    cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+                    cpu->regs[REG_CX]--;
+                    
+                    if (cpu->rep_type == 1 && !(cpu->flags & FLAG_ZF)) {
+                        break;
+                    }
+                    
+                    if (cpu->rep_type == 2 &&  (cpu->flags & FLAG_ZF)) {
+                        break;
+                    }
+                
+                }
+            
+            } else {
+            
+                uint16_t off = cpu->regs[REG_DI];
+                uint16_t v = (uint16_t) mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, off)) | ((uint16_t) mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, (uint16_t) (off + 1))) << 8);
+                
+                sub16 (cpu, cpu->regs[REG_AX], v);
+                cpu->regs[REG_DI] = (uint16_t) (cpu->regs[REG_DI] + step);
+            
+            }
+            
+            break;
+        
+        }
+        
+        /* Data Move Immediate to Register */
+        case 0xB0:  case 0xB1:  case 0xB2:  case 0xB3:  case 0xB5:  case 0xB6:  case 0xB7:
+        
+            set_reg8 (cpu, op - 0xB0, fetch8 (cpu));
+            break;
+        
+        case 0xB4: {
+            
+            uint8_t imm8 = fetch8 (cpu);
+            set_reg8 (cpu, 4, imm8);
+            
+            break;
+        
+        }
+        
+        case 0xB8: case 0xB9: case 0xBA: case 0xBB:
+        case 0xBC: case 0xBD: case 0xBE: case 0xBF:
+        
+            cpu->regs[op - 0xB8] = fetch16 (cpu);
+            break;
+        
+        case 0xC0: {           /*8-bit Shift/Rotate by imm8 (80186+) */
+        
+            uint8_t modrm = fetch8 (cpu), result;
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            uint8_t val = operand_read8 (cpu, &rm);
+            uint8_t count = fetch8 (cpu) & 0x1F;            /* Real CPUs mask count to 5 bits (0-31) */
+            
+            if (count == 0) {
+                break;
+            }
+            
+            result = val;
+            
+            switch (subop) {
+            
+                case 0:             /* ROL */
+                
+                    for (int i = 0; i < count; i++) {
+                    
+                        uint8_t bit7 = (result & 0x80) >> 7;
+                        result = (result << 1) | bit7;
+                        
+                        if (bit7) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                    
+                    }
+                    
+                    if (count == 1) {                    /* OF only defined for single-step rotations */
+                    
+                        if (((result & 0x80) >> 7) ^ ((cpu->flags & FLAG_CF) ? 1 : 0)) {
+                            cpu->flags |= FLAG_OF;
+                        } else {
+                            cpu->flags &= ~FLAG_OF;
+                        }
+                    
+                    }
+                    
+                    break;
+                
+                case 4:             /* SHL / SAL */
+                
+                    if (count > 8) {
+                    
+                        cpu->flags &= ~FLAG_CF;
+                        result = 0;
+                    
+                    } else {
+                    
+                        if ((val << (count - 1)) & 0x80) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                        
+                        result = val << count;
+                    
+                    }
+                    
+                    if (count == 1) {
+                    
+                        if ((result ^ val) & 0x80) {
+                            cpu->flags |= FLAG_OF;
+                        } else {
+                            cpu->flags &= ~FLAG_OF;
+                        }
+                    
+                    }
+                    
+                    update_common_flags8 (cpu, result);
+                    break;
+                
+                case 5:             /* SHR */
+                
+                    if (count > 8) {
+                    
+                        cpu->flags &= ~FLAG_CF;
+                        result = 0;
+                    
+                    } else {
+                    
+                        if ((val >> (count - 1)) & 0x01) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                        
+                        result = val >> count;
+                    
+                    }
+                    
+                    if (count == 1) {
+                    
+                        if (val & 0x80) {
+                            cpu->flags |= FLAG_OF;
+                        } else {
+                            cpu->flags &= ~FLAG_OF;
+                        }
+                    
+                    }
+                    
+                    update_common_flags8 (cpu, result);
+                    break;
+                
+                default:
+                
+                    fprintf (stderr, "Opcode 0xC0 subop /%d not fully implemented at %04X\n", subop, old_ip);
+                    return -1;
+            
+            }
+            
+            operand_write8 (cpu, &rm, result);
+            break;
+        
+        }
+        
+        case 0xC1: {           /*16-bit Shift/Rotate by imm8 (80186+) */
+        
+            uint8_t modrm = fetch8 (cpu), result;
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            uint16_t val = operand_read16 (cpu, &rm);
+            uint8_t count = fetch8 (cpu) & 0x1F; /* 186+ masks count */
+            
+            if (count == 0) {
+                break;
+            }
+            
+            result = val;
+            
+            switch (subop) {
+            
+                case 0: {           /* ROL */
+                
+                    for (int i = 0; i < count; i++) {
+                    
+                        uint16_t bit15 = (result >> 15) & 1;
+                        result = (uint16_t) ((result << 1) | bit15);
+                        
+                        if (bit15) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_CF;
+                        }
+                    
+                    }
+                    
+                    if (count == 1) {
+                    
+                        if ((((result >> 15) & 1) ^ ((cpu->flags & FLAG_CF) ? 1 : 0))) {
+                            cpu->flags |= FLAG_OF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_OF;
+                        }
+                    
+                    }
+                    
+                    break;
+                
+                }
+                
+                case 1: {           /* ROR */
+                
+                    for (int i = 0; i < count; i++) {
+                    
+                        uint16_t bit0 = result & 1;
+                        result = (uint16_t) ((result >> 1) | (bit0 << 15));
+                        
+                        if (bit0) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_CF;
+                        }
+                    
+                    }
+                    
+                    if (count == 1) {
+                    
+                        if ((((result >> 15) & 1) ^ ((result >> 14) & 1))) {
+                            cpu->flags |= FLAG_OF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_OF;
+                        }
+                    
+                    }
+                    
+                    break;
+                
+                }
+                
+                case 2: {           /* RCL */
+                
+                    for (int i = 0; i < count; i++) {
+                    
+                        uint16_t curr_cf = (cpu->flags & FLAG_CF) ? 1 : 0;
+                        uint16_t new_cf = (result >> 15) & 1;
+                        
+                        result = (uint16_t) ((result << 1) | curr_cf);
+                        
+                        if (new_cf) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_CF;
+                        }
+                    
+                    }
+                    
+                    if (count == 1) {
+                    
+                        if ((((result >> 15) & 1) ^ ((cpu->flags & FLAG_CF) ? 1 : 0))) {
+                            cpu->flags |= FLAG_OF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_OF;
+                        }
+                    
+                    }
+                    
+                    break;
+                
+                }
+                
+                case 3: {           /* RCR */
+                
+                    for (int i = 0; i < count; i++) {
+                    
+                        uint16_t curr_cf = (cpu->flags & FLAG_CF) ? 1 : 0;
+                        uint16_t new_cf = result & 1;
+                        
+                        result = (uint16_t) ((result >> 1) | (curr_cf << 15));
+                        
+                        if (new_cf) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_CF;
+                        }
+                    
+                    }
+                    
+                    if (count == 1) {
+                    
+                        if ((((result >> 15) & 1) ^ ((result >> 14) & 1))) {
+                            cpu->flags |= FLAG_OF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_OF;
+                        }
+                    
+                    }
+                    
+                    break;
+                
+                }
+                
+                case 4: {           /* SHL / SAL */
+                
+                    if (count > 16) {
+                    
+                        cpu->flags &= (uint16_t) ~FLAG_CF;
+                        result = 0;
+                    
+                    } else {
+                    
+                        if ((val << (count - 1)) & 0x8000) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_CF;
+                        }
+                        
+                        result = (uint16_t) (val << count);
+                    
+                    }
+                    
+                    if (count == 1) {
+                    
+                        if ((result ^ val) & 0x8000) {
+                            cpu->flags |= FLAG_OF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_OF;
+                        }
+                    
+                    }
+                    
+                    update_common_flags16 (cpu, result);
+                    break;
+                
+                }
+                
+                case 5: {           /* SHR */
+                
+                    if (count > 16) {
+                    
+                        cpu->flags &= (uint16_t) ~FLAG_CF;
+                        result = 0;
+                    
+                    } else {
+                    
+                        if ((val >> (count - 1)) & 0x0001) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_CF;
+                        }
+                        
+                        result = (uint16_t) (val >> count);
+                    
+                    }
+                    
+                    if (count == 1) {
+                    
+                        if (val & 0x8000) {
+                            cpu->flags |= FLAG_OF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_OF;
+                        }
+                    
+                    }
+                    
+                    update_common_flags16 (cpu, result);
+                    break;
+                
+                }
+                
+                case 7: {           /* SAR */
+                
+                    if (count > 16) {
+                    
+                        result = (val & 0x8000) ? 0xFFFF : 0x0000;
+                        
+                        if (val & 0x8000) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_CF;
+                        }
+                    
+                    } else {
+                    
+                        if (((int16_t) val >> (count - 1)) & 0x0001) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= (uint16_t) ~FLAG_CF;
+                        }
+                        
+                        result = (uint16_t) ((int16_t) val >> count);
+                    
+                    }
+                    
+                    if (count == 1) {
+                        cpu->flags &= (uint16_t) ~FLAG_OF;
+                    }
+                    
+                    update_common_flags16 (cpu, result);
+                    break;
+                
+                }
+                
+                default:
+                
+                    fprintf (stderr, "Opcode 0xC1 subop /%d not implemented at %04X\n", subop, old_ip);
+                    return -1;
+            
+            }
+            
+            operand_write16 (cpu, &rm, result);
+            break;
+        
+        }
+        
+        case 0xC2: {           /* RET imm16 */
+        
+            uint16_t add_sp = fetch16 (cpu);
+            uint16_t new_ip = pop16 (cpu);
+            
+            cpu->regs[REG_SP] = (uint16_t) (cpu->regs[REG_SP] + add_sp);
+            cpu->ip = new_ip;
+            
+            break;
+        
+        }
+        
+        case 0xC3: {           /* RET near */
+        
+            cpu->ip = pop16 (cpu);
+            break;
+        
+        }
+        
+        case 0xC4: {           /* LES r16, m16:16 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            uint16_t ea_seg;
+            uint16_t off_addr;
+            uint16_t off;
+            uint16_t es_val;
+            
+            if (!rm.is_mem) {
+            
+                printf ("LES requires memory operand at %04X%04X modrm=%02X\n", cpu->cs, old_ip, modrm);
+                exit (1);
+            
+            }
+            
+            ea_seg   = rm.seg;
+            off_addr = (uint16_t) rm.addr;
+            
+            off    = mem_read16 (cpu->mem, cpu->mem_size, get_phys (ea_seg, off_addr));
+            es_val = mem_read16 (cpu->mem, cpu->mem_size, get_phys (ea_seg, (uint16_t) (off_addr + 2)));
+            
+            cpu->regs[reg] = off;
+            cpu->es = es_val;
+            
+            break;
+        
+        }
+        
+        case 0xC5: {           /* LDS r16, m16:16 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int reg = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            uint16_t src_seg, ptr_offset, seg_low, seg_high, ptr_segment;
+            
+            if (!rm.is_mem) {
+            
+                /* LES/LDS requires a memory operand (mod != 3) */
+                return -1;
+            
+            }
+            
+            /* Determine the segment to read the pointer FROM (respects overrides) */
+            src_seg = rm.seg;
+            
+            /* Read the 4-byte far pointer: Offset is at [addr], Segment is at [addr+2] */
+            ptr_offset = operand_read16 (cpu, &rm);                    /* Already wrapped in your code */
+            seg_low = mem_read8 (cpu->mem, cpu->mem_size, get_phys (src_seg, rm.addr + 2));
+            seg_high = mem_read8 (cpu->mem, cpu->mem_size, get_phys (src_seg, rm.addr + 3));
+            ptr_segment = (uint16_t) seg_low | ((uint16_t) seg_high << 8);
+            
+            /* Load the registers */
+            cpu->regs[reg] = ptr_offset;
+            cpu->ds = ptr_segment;
+            
+            break;
+        
+        }
+        
+        case 0xC6: /* MOV r/m8, imm8 */     case 0xC7: /* MOV r/m16, imm16 */ {
+        
+            uint8_t modrm = fetch8 (cpu);
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            if (op == 0xC6) {
+            
+                uint8_t imm8 = fetch8 (cpu);                /* Correctly fetch 8 bits */
+                operand_write8 (cpu, &rm, imm8);
+            
+            } else {
+            
+                uint16_t imm16 = fetch16 (cpu);             /* Correctly fetch 16 bits */
+                operand_write16 (cpu, &rm, imm16);
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xC9:             /* LEAVE */
+        
+            cpu->regs[REG_SP] = cpu->regs[REG_BP];
+            cpu->regs[REG_BP] = pop16 (cpu);
+            
+            break;
+        
+        case 0xCA: {           /* RETF imm16 */
+        
+            uint16_t add_sp = fetch16 (cpu);
+            uint16_t new_ip = pop16 (cpu);
+            uint16_t new_cs = pop16 (cpu);
+            
+            cpu->regs[REG_SP] = (uint16_t) (cpu->regs[REG_SP] + add_sp);
+            cpu->ip = new_ip;
+            cpu->cs = new_cs;
+            
+            break;
+        
+        }
+        
+        case 0xCB: {
+        
+            uint16_t new_ip = pop16 (cpu);
+            uint16_t new_cs = pop16 (cpu);
+            
+            cpu->ip = new_ip;
+            cpu->cs = new_cs;
+            
+            break;
+        
+        }
+        
+        case 0xCD: {           /* INT imm8 */
+        
+            uint8_t inum = fetch8 (cpu);
+            
+            /* Native DOS bridge executes only from our reserved IVT stubs. */
+            if (inum == 0xFF && cpu->cs == DOS_IVT_STUB_SEG &&
+                old_ip >= 0 && old_ip < 1024 && (old_ip & 3) == 0 &&
+                    mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->cs, (uint16_t) old_ip)) == 0xCD &&
+                        mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->cs, (uint16_t) (old_ip + 1))) == 0xFF) {
+                native_interrupt (cpu, (uint8_t) (old_ip >> 2));
+            } else {
+                handle_interrupt (cpu, inum);
+            }
+            
+            break;
+        
+        }
+        
+        case 0xCC: {           /* INT 3 (Breakpoint) */
+        
+            handle_interrupt (cpu, 3);
+            break;
+        
+        }
+        
+        case 0xCE: {           /* INTO (Interrupt on Overflow) */
+        
+            if (cpu->flags & FLAG_OF) {
+                handle_interrupt (cpu, 4);
+            }
+            
+            break;
+        
+        }
+        
+        case 0xCF: {
+        
+            uint16_t new_ip = pop16 (cpu);
+            uint16_t new_cs = pop16 (cpu);
+            uint16_t new_flags = pop16 (cpu);
+            
+            cpu->ip = new_ip;
+            cpu->cs = new_cs;
+            cpu->flags = new_flags;
+            
+            break;
+        
+        }
+        
+        case 0xD4: {           /* AAM imm8 */
+        
+            uint8_t base = fetch8 (cpu);
+            uint8_t al = get_al (cpu);
+            
+            if (base == 0) {
+            
+                handle_interrupt (cpu, 0);
+                break;
+            
+            }
+            
+            cpu->regs[REG_AX] = (uint16_t) (((uint16_t) (al / base) << 8) | (al % base));
+            
+            update_common_flags8 (cpu, get_al (cpu));
+            break;
+        
+        }
+        
+        case 0xFF: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            switch (subop) {
+            
+                case 0:             /* INC rm16 */
+                
+                    operand_write16 (cpu, &rm, inc16 (cpu, operand_read16 (cpu, &rm)));
+                    break;
+                
+                case 1:             /* DEC rm16 */
+                
+                    operand_write16 (cpu, &rm, dec16 (cpu, operand_read16 (cpu, &rm)));
+                    break;
+                
+                case 2: {           /* CALL near indirect */
+                
+                    uint16_t target = operand_read16 (cpu, &rm);
+                    push16 (cpu, cpu->ip);
+                    
+                    cpu->ip = target;
+                    break;
+                
+                }
+                
+                case 3:             /* CALL far indirect */
+                
+                    if (!rm.is_mem) {
+                        return -1;
+                    }
+                    
+                    {
+                    
+                        uint16_t ptr_seg = rm.seg;
+                        uint16_t new_ip = operand_read16 (cpu, &rm);
+                        
+                        uint8_t cs_low  = mem_read8 (cpu->mem, cpu->mem_size, get_phys (ptr_seg, rm.addr + 2));
+                        uint8_t cs_high = mem_read8 (cpu->mem, cpu->mem_size, get_phys (ptr_seg, rm.addr + 3));
+                        
+                        uint16_t new_cs = (uint16_t) cs_low | ((uint16_t) cs_high << 8);
+                        
+                        push16 (cpu, cpu->cs);
+                        push16 (cpu, cpu->ip);
+                        
+                        cpu->cs = new_cs;
+                        cpu->ip = new_ip;
+                    
+                    }
+                    
+                    break;
+                
+                case 4:             /* JMP near indirect */
+                
+                    cpu->ip = operand_read16 (cpu, &rm);
+                    break;
+                
+                case 5:             /* JMP far indirect (m16:16) */
+                
+                    if (!rm.is_mem) {
+                        return -1;
+                    }
+                    
+                    {
+                    
+                        uint16_t ptr_seg = rm.seg;
+                        uint16_t new_ip = operand_read16 (cpu, &rm);
+                        
+                        uint8_t cs_low  = mem_read8 (cpu->mem, cpu->mem_size, get_phys (ptr_seg, rm.addr + 2));
+                        uint8_t cs_high = mem_read8 (cpu->mem, cpu->mem_size, get_phys (ptr_seg, rm.addr + 3));
+                        
+                        uint16_t new_cs = (uint16_t) cs_low | ((uint16_t) cs_high << 8);
+                        
+                        cpu->cs = new_cs;
+                        cpu->ip = new_ip;
+                    
+                    }
+                    
+                    break;
+                
+                case 6:             /* PUSH rm16 */
+                
+                    push16 (cpu, operand_read16 (cpu, &rm));
+                    break;
+                
+                default:
+                
+                    fprintf (stderr, "Unhandled 0xFF extension /%d (Undefined) at %04X\n", subop, old_ip);
+                    return -1;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xD0: {           /*8-bit Shift/Rotate by 1 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            uint8_t val = operand_read8 (cpu, &rm);
+            uint8_t result = val;
+            uint8_t old_cf = (cpu->flags & FLAG_CF) ? 1 : 0;
+            
+            switch (subop) {
+            
+                case 0:             /* ROL (Rotate Left) */
+                
+                    result = (val << 1) | (val >> 7);
+                    
+                    if (val & 0x80) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    /* OF = bit 7 (after) XOR CF */
+                    if (((result & 0x80) >> 7) ^ ((cpu->flags & FLAG_CF) ? 1 : 0)) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    break;
+                
+                case 1:             /* ROR (Rotate Right) */
+                
+                    result = (val >> 1) | (val << 7);
+                    
+                    if (val & 0x01) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    /* OF = bit 7 XOR bit 6 (after) */
+                    if (((result & 0x80) >> 7) ^ ((result & 0x40) >> 6)) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    break;
+                
+                case 2:             /* RCL (Rotate through Carry Left) */
+                
+                    result = (val << 1) | old_cf;
+                    
+                    if (val & 0x80) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    if (((result & 0x80) >> 7) ^ ((cpu->flags & FLAG_CF) ? 1 : 0)) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    break;
+                
+                case 3:             /* RCR (Rotate through Carry Right) */
+                
+                    result = (val >> 1) | (old_cf << 7);
+                    
+                    if (val & 0x01) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    if (((result & 0x80) >> 7) ^ ((result & 0x40) >> 6)) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    break;
+                
+                case 4:             /* SHL / SAL */
+                
+                    result = val << 1;
+                    
+                    if (val & 0x80) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    if ((result ^ val) & 0x80) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    update_common_flags8 (cpu, result);     /* Shifts DO affect ZF/SF/PF */
+                    break;
+                
+                case 5:             /* SHR */
+                
+                    result = val >> 1;
+                    
+                    if (val & 0x01) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    if (val & 0x80) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    update_common_flags8 (cpu, result);
+                    break;
+                
+                case 7:             /* SAR */
+                
+                    result = (uint8_t) ((int8_t) val >> 1);
+                    
+                    if (val & 0x01) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    cpu->flags &= ~FLAG_OF;
+                    update_common_flags8 (cpu, result);
+                    
+                    break;
+                
+                default:
+                
+                    fprintf (stderr, "Opcode 0xD0 subop /%d not implemented at %04X\n", subop, old_ip);
+                    return -1;
+            
+            }
+            
+            operand_write8 (cpu, &rm, result);
+            break;
+        
+        }
+        
+        case 0xD1: {           /*16-bit Shift/Rotate by 1 */
+        
+            uint8_t modrm = fetch8 (cpu);
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            uint16_t val = operand_read16 (cpu, &rm);
+            uint16_t result = val;
+            
+            uint8_t old_cf = (cpu->flags & FLAG_CF) ? 1 : 0;
+            
+            switch (subop) {
+            
+                case 0:             /* ROL (Rotate Left) */
+                
+                    result = (val << 1) | (val >> 15);
+                    
+                    if (val & 0x8000) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    /* OF = bit 15 (after) XOR CF */
+                    if (((result & 0x8000) >> 15) ^ ((cpu->flags & FLAG_CF) ? 1 : 0)) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    break;
+                
+                case 1:             /* ROR (Rotate Right) */
+                
+                    result = (val >> 1) | (val << 15);
+                    
+                    if (val & 0x0001) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    /* OF = bit 15 XOR bit 14 (after) */
+                    if (((result & 0x8000) >> 15) ^ ((result & 0x4000) >> 14)) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    break;
+                
+                case 2:             /* RCL (Rotate through Carry Left) */
+                
+                    result = (val << 1) | old_cf;
+                    
+                    if (val & 0x8000) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    if (((result & 0x8000) >> 15) ^ ((cpu->flags & FLAG_CF) ? 1 : 0)) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    break;
+                
+                case 3:             /* RCR (Rotate through Carry Right) */
+                
+                    result = (val >> 1) | ((uint16_t) old_cf << 15);
+                    
+                    if (val & 0x0001) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    if (((result & 0x8000) >> 15) ^ ((result & 0x4000) >> 14)) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    break;
+                
+                case 4:             /* SHL / SAL */
+                
+                    result = val << 1;
+                    
+                    if (val & 0x8000) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    if ((result ^ val) & 0x8000) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    update_common_flags16 (cpu, result);
+                    break;
+                
+                case 5:             /* SHR */
+                
+                    result = val >> 1;
+                    
+                    if (val & 0x0001) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    if (val & 0x8000) {
+                        cpu->flags |= FLAG_OF;
+                    } else {
+                        cpu->flags &= ~FLAG_OF;
+                    }
+                    
+                    update_common_flags16 (cpu, result);
+                    break;
+                
+                case 7:             /* SAR */
+                
+                    result = (uint16_t) ((int16_t) val >> 1);
+                    
+                    if (val & 0x0001) {
+                        cpu->flags |= FLAG_CF;
+                    } else {
+                        cpu->flags &= ~FLAG_CF;
+                    }
+                    
+                    cpu->flags &= ~FLAG_OF;
+                    update_common_flags16 (cpu, result);
+                    
+                    break;
+                
+                default:
+                
+                    fprintf (stderr, "Opcode 0xD1 subop /%d not implemented at %04X\n", subop, old_ip);
+                    return -1;
+            
+            }
+            
+            operand_write16 (cpu, &rm, result);
+            break;
+        
+        }
+        
+        case 0xD2: {           /*8-bit Shift/Rotate by CL */
+        
+            uint8_t modrm = fetch8 (cpu), result;
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            uint8_t val = operand_read8 (cpu, &rm);
+            uint8_t count = get_reg8 (cpu, REG_CX);         /* Use CL register value */
+            
+            if (count == 0) {
+                break;
+            }
+            
+            /* For 8086, count is not masked. For 186+, count = count & 0x1F. */
+            /* Standard practice is to mask it to prevent long hangs. */
+            count &= 0x1F;
+            result = val;
+            
+            switch (subop) {
+            
+                case 0:             /* ROL */
+                
+                    for (int i = 0; i < count; i++) {
+                    
+                        uint8_t bit7 = (result & 0x80) >> 7;
+                        result = (result << 1) | bit7;
+                        
+                        if (bit7) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                    
+                    }
+                    
+                    break;
+                
+                case 1:             /* ROR */
+                
+                    for (int i = 0; i < count; i++) {
+                    
+                        uint8_t bit0 = result & 0x01;
+                        result = (result >> 1) | (bit0 << 7);
+                        
+                        if (bit0) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                    
+                    }
+                    
+                    break;
+                
+                case 2:             /* RCL */
+                
+                    for (int i = 0; i < count; i++) {
+                    
+                        uint8_t curr_cf = (cpu->flags & FLAG_CF) ? 1 : 0;
+                        
+                        if (result & 0x80) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                        
+                        result = (result << 1) | curr_cf;
+                    
+                    }
+                    
+                    break;
+                
+                case 3:             /* RCR */
+                
+                    for (int i = 0; i < count; i++) {
+                    
+                        uint8_t curr_cf = (cpu->flags & FLAG_CF) ? 1 : 0;
+                        
+                        if (result & 0x01) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                        
+                        result = (result >> 1) | (curr_cf << 7);
+                    
+                    }
+                    
+                    break;
+                
+                case 4:             /* SHL / SAL */
+                
+                    if (count >= 8) {
+                    
+                        if ((val << (count - 1)) & 0x80) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                        
+                        result = 0;
+                    
+                    } else {
+                    
+                        if ((val << (count - 1)) & 0x80) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                        
+                        result = val << count;
+                    
+                    }
+                    
+                    update_common_flags8 (cpu, result);
+                    break;
+                
+                case 5:             /* SHR */
+                
+                    if (count >= 8) {
+                    
+                        if ((val >> (count - 1)) & 0x01) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                        
+                        result = 0;
+                    
+                    } else {
+                    
+                        if ((val >> (count - 1)) & 0x01) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                        
+                        result = val >> count;
+                    
+                    }
+                    
+                    update_common_flags8 (cpu, result);
+                    break;
+                
+                case 7:             /* SAR */
+                
+                    if (count >= 8) {
+                    
+                        result = (val & 0x80) ? 0xFF : 0;
+                        
+                        if (val & 0x80) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                    
+                    } else {
+                    
+                        if (((int8_t) val >> (count - 1)) & 0x01) {
+                            cpu->flags |= FLAG_CF;
+                        } else {
+                            cpu->flags &= ~FLAG_CF;
+                        }
+                        
+                        result = (uint8_t) ((int8_t) val >> count);
+                    
+                    }
+                    
+                    update_common_flags8 (cpu, result);
+                    break;
+                
+                default:
+                
+                    fprintf (stderr, "Opcode 0xD2 subop /%d not implemented at %04X\n", subop, old_ip);
+                    return -1;
+            
+            }
+            
+            operand_write8 (cpu, &rm, result);
+            break;
+        
+        }
+        
+        case 0xD3: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            uint16_t val = operand_read16 (cpu, &rm), result;
+            uint8_t count = get_reg8 (cpu, REG_CX);         /* Use CL register */
+            
+            if (count == 0) {
+                break;
+            }
+            
+            /* For 8086, count is not masked. For 186+, count = count & 0x1F. */
+            /* Standard practice is to mask it to prevent long hangs. */
+            count &= 0x1F;
+            
+            result = val;
+            
+            if (subop == 4) {                    /* SHL / SAL */
+            
+                result = val << count;
+                
+                /* Set Carry Flag (CF) to the last bit shifted out */
+                if ((val << (count - 1)) & 0x8000) {
+                    cpu->flags |= FLAG_CF;
+                } else {
+                    cpu->flags &= (uint16_t) ~FLAG_CF;
+                }
+            
+            } else if (subop == 5) {                    /* SHR */
+            
+                result = val >> count;
+                
+                if ((val >> (count - 1)) & 0x0001) {
+                    cpu->flags |= FLAG_CF;
+                } else {
+                    cpu->flags &= (uint16_t) ~FLAG_CF;
+                }
+            
+            } else if (subop == 7) {                    /* SAR (Arithmetic shift - preserves sign) */
+            
+                result = (uint16_t) ((int16_t) val >> count);
+                
+                if (((int16_t) val >> (count - 1)) & 0x0001) {
+                    cpu->flags |= FLAG_CF;
+                } else {
+                    cpu->flags &= (uint16_t) ~FLAG_CF;
+                }
+            
+            } else {
+            
+                fprintf (stderr, "Shift subop /%d not implemented at %04X\n", subop, old_ip);
+                return -1;
+            
+            }
+            
+            operand_write16 (cpu, &rm, result);
+            update_common_flags16 (cpu, result);
+            
+            break;
+        
+        }
+        
+        case 0xD7: {           /* XLAT */
+        
+            uint16_t index = (uint16_t) (cpu->regs[REG_BX] + get_reg8 (cpu, REG_AX));
+            uint16_t seg = cpu->has_seg_override ? cpu->seg_override : cpu->ds;
+            
+            uint8_t value = mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, index));
+            set_reg8 (cpu, REG_AX, value);                  /* AL */
+            
+            break;
+        
+        }
+        
+        case 0xD8:  case 0xD9:  case 0xDA:  case 0xDB:
+        case 0xDC:  case 0xDD:  case 0xDE:  case 0xDF: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            
+            /* Even if FPU is not implemented, we MUST decode the ModRM */
+            /* to ensure the IP correctly skips any displacement bytes. */
+            decode_rm_operand (cpu, modrm);
+            break;
+        
+        }
+        
+        case 0xE0: {           /* LOOPNE / LOOPNZ */
+        
+            int8_t rel = (int8_t) fetch8 (cpu);
+            
+            uint16_t next_ip = cpu->ip, new_cx, target;
+            int zf, take;
+            
+            cpu->regs[REG_CX] = (uint16_t) (cpu->regs[REG_CX] - 1);
+            
+            new_cx = cpu->regs[REG_CX];
+            target = (uint16_t) (next_ip + rel);
+            
+            zf = (cpu->flags & FLAG_ZF) != 0;
+            take = (new_cx != 0) && !zf;
+            
+            if (take) {
+                cpu->ip = target;
+            }
+            
+            break;
+        
+        }
+        
+        case 0xE1: {           /* LOOPE / LOOPZ */
+        
+            int8_t rel = (int8_t) fetch8 (cpu);
+            
+            uint16_t next_ip = cpu->ip, new_cx, target;
+            int zf, take;
+            
+            cpu->regs[REG_CX] = (uint16_t) (cpu->regs[REG_CX] - 1);
+            
+            new_cx = cpu->regs[REG_CX];
+            target = (uint16_t) (next_ip + rel);
+            
+            zf = (cpu->flags & FLAG_ZF) != 0;
+            take = (new_cx != 0) && zf;
+            
+            if (take) {
+                cpu->ip = target;
+            }
+            
+            break;
+        
+        }
+        
+        case 0xE2: {           /* LOOP */
+        
+            int8_t rel = (int8_t) fetch8 (cpu);
+            
+            uint16_t next_ip = cpu->ip, new_cx, target;
+            int take;
+            
+            cpu->regs[REG_CX] = (uint16_t) (cpu->regs[REG_CX] - 1);
+            
+            new_cx = cpu->regs[REG_CX];
+            target = (uint16_t) (next_ip + rel);
+            
+            if ((take = (new_cx != 0))) {
+                cpu->ip = target;
+            }
+            
+            break;
+        
+        }
+        
+        case 0xE3: {           /* JCXZ */
+        
+            int8_t rel = (int8_t) fetch8 (cpu);
+            
+            uint16_t next_ip = cpu->ip;
+            uint16_t target = (uint16_t) (next_ip + rel);
+            
+            int take = (cpu->regs[REG_CX] == 0);
+            
+            if (take) {
+                cpu->ip = target;
+            }
+            
+            break;
+        
+        }
+        
+        case 0xE4: {           /* IN AL, imm8 */
+        
+            uint8_t port = fetch8 (cpu);
+            set_reg8 (cpu, 0, io_read8 (cpu, port));
+            
+            break;
+        
+        }
+        
+        case 0xE5: {           /* IN AX, imm8 */
+        
+            uint8_t port = fetch8 (cpu);
+            cpu->regs[REG_AX] = io_read16 (cpu, port);
+            
+            break;
+        
+        }
+        
+        case 0xE6: {           /* OUT imm8, AL */
+        
+            uint8_t port = fetch8 (cpu);
+            io_write8 (cpu, port, get_reg8 (cpu, 0));
+            
+            break;
+        
+        }
+        
+        case 0xE7: {           /* OUT imm8, AX */
+        
+            uint8_t port = fetch8 (cpu);
+            io_write16 (cpu, port, cpu->regs[REG_AX]);
+            
+            break;
+        
+        }
+        
+        case 0xE8: {
+        
+            int16_t rel = (int16_t) fetch16 (cpu);
+            
+            uint16_t ret = cpu->ip;
+            uint16_t target = (uint16_t) (cpu->ip + rel);
+            
+            push16 (cpu, ret);
+            cpu->ip = target;
+            
+            break;
+        
+        }
+        
+        case 0xE9: {
+        
+            int16_t rel = (int16_t) fetch16 (cpu);
+            uint16_t target = (uint16_t) (cpu->ip + rel);
+            
+            cpu->ip = target;
+            break;
+        
+        }
+        
+        case 0xEA: {           /* JMP far ptr16:16 */
+        
+            uint16_t new_ip = fetch16 (cpu);
+            uint16_t new_cs = fetch16 (cpu);
+            
+            cpu->cs = new_cs;
+            cpu->ip = new_ip;
+            
+            break;
+        
+        }
+        
+        case 0xEB: {
+        
+            int8_t rel = (int8_t) fetch8 (cpu);
+            uint16_t target = (uint16_t) (cpu->ip + rel);
+            
+            cpu->ip = target;
+            break;
+        
+        }
+        
+        case 0xEC: {           /* IN AL, DX */
+        
+            uint16_t port = cpu->regs[REG_DX];
+            set_reg8 (cpu, 0, io_read8 (cpu, port));
+            
+            break;
+        
+        }
+        
+        case 0xED: {           /* IN AX, DX */
+        
+            uint16_t port = cpu->regs[REG_DX];
+            cpu->regs[REG_AX] = io_read16 (cpu, port);
+            
+            break;
+        
+        }
+        
+        case 0xEE: {           /* OUT DX, AL */
+        
+            io_write8 (cpu, cpu->regs[REG_DX], get_reg8 (cpu, 0));
+            break;
+        
+        }
+        
+        case 0xEF: {           /* OUT DX, AX */
+        
+            io_write16 (cpu, cpu->regs[REG_DX], cpu->regs[REG_AX]);
+            break;
+        
+        }
+        
+        case 0xF0:             /* LOCK prefix */
+        
+            goto fetch_instruction;
+        
+        case 0xF2:             /* REPNZ */
+        
+            cpu->rep_type = 2;
+            goto fetch_instruction;
+        
+        case 0xF3:             /* REP / REPE */
+        
+            cpu->rep_type = 1;
+            goto fetch_instruction;
+        
+        case 0xF4:
+        
+            cpu->halted = 1;
+            break;
+        
+        case 0xF6: /* Unary rm8 */      case 0xF7: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            
+            if (op == 0xF6) {                    /*8-bit Operations */
+            
+                switch (subop) {
+                
+                    case 0: case 1: {
+                    
+                        uint8_t imm = fetch8 (cpu);
+                        uint8_t val = operand_read8 (cpu, &rm);
+                        
+                        and8 (cpu, val, imm);
+                        break;
+                    }
+                    
+                    case 2:             /* NOT rm8 */
+                    
+                        operand_write8 (cpu, &rm, ~operand_read8 (cpu, &rm));
+                        break;
+                    
+                    case 3:             /* NEG rm8 */
+                    
+                        operand_write8 (cpu, &rm, sub8 (cpu, 0, operand_read8 (cpu, &rm)));
+                        break;
+                    
+                    case 4: {           /* MUL rm8 (AL * rm8 -> AX) */
+                    
+                        uint8_t src = operand_read8 (cpu, &rm);
+                        uint16_t res = (uint16_t) get_reg8 (cpu, 0) * (uint16_t) src;               /* 0 = AL */
+                        
+                        cpu->regs[REG_AX] = res;
+                        
+                        if ((res >> 8) != 0) {
+                            cpu->flags |= (FLAG_CF | FLAG_OF);
+                        } else {
+                            cpu->flags &= ~(FLAG_CF | FLAG_OF);
+                        }
+                        
+                        break;
+                    
+                    }
+                    
+                    case 5: {           /* IMUL rm8 (AL * rm8 -> AX) */
+                    
+                        int8_t src = (int8_t) operand_read8 (cpu, &rm);
+                        int16_t res = (int16_t) (int8_t) get_reg8 (cpu, 0) * (int16_t) src;
+                        
+                        cpu->regs[REG_AX] = (uint16_t) res;
+                        
+                        if (res < -128 || res > 127) {
+                            cpu->flags |= (FLAG_CF | FLAG_OF);
+                        } else {
+                            cpu->flags &= ~(FLAG_CF | FLAG_OF);
+                        }
+                        
+                        break;
+                    
+                    }
+                    
+                    case 6: {           /* DIV rm8 (AX / rm8) */
+                    
+                        uint16_t dividend, quotient, remainder;
+                        uint8_t divisor = operand_read8 (cpu, &rm);
+                        
+                        if (divisor == 0) {
+                            return -1;
+                        }
+                        
+                        dividend = cpu->regs[REG_AX];
+                        quotient = dividend / divisor;
+                        remainder = dividend % divisor;
+                        
+                        if (quotient > 0xFF) {
+                            return -1;
+                        }
+                        
+                        set_reg8 (cpu, 0, (uint8_t) quotient);  /* AL */
+                        set_reg8 (cpu, 4, (uint8_t) remainder); /* AH */
+                        
+                        break;
+                    
+                    }
+                    
+                    default:
+                    
+                        fprintf (stderr, "F6 Subop /%d not implemented\n", subop);
+                        return -1;
+                
+                }
+            
+            } else {                    /*0xF7: 16-bit Operations */
+            
+                switch (subop) {
+                
+                    case 0:     case 1:                     /* TEST rm16, imm16 */
+                    
+                        and16 (cpu, operand_read16 (cpu, &rm), fetch16 (cpu));
+                        break;
+                    
+                    case 2:             /* NOT rm16 */
+                    
+                        operand_write16 (cpu, &rm, ~operand_read16 (cpu, &rm));
+                        break;
+                    
+                    case 3:             /* NEG rm16 */
+                    
+                        operand_write16 (cpu, &rm, sub16 (cpu, 0, operand_read16 (cpu, &rm)));
+                        break;
+                    
+                    case 4: {           /* MUL rm16 (AX * rm16 -> DX:AX) */
+                    
+                        uint16_t src = operand_read16 (cpu, &rm);
+                        uint32_t res = (uint32_t) cpu->regs[REG_AX] * (uint32_t) src;
+                        
+                        cpu->regs[REG_AX] = (uint16_t) (res & 0xFFFF);
+                        cpu->regs[REG_DX] = (uint16_t) (res >> 16);
+                        
+                        if (cpu->regs[REG_DX] != 0) {
+                            cpu->flags |= (FLAG_CF | FLAG_OF);
+                        } else {
+                            cpu->flags &= ~(FLAG_CF | FLAG_OF);
+                        }
+                        
+                        break;
+                    
+                    }
+                    
+                    case 5: {           /* IMUL rm16 (AX * rm16 -> DX:AX) */
+                    
+                        int16_t src = (int16_t) operand_read16 (cpu, &rm);
+                        int32_t res = (int32_t) (int16_t) cpu->regs[REG_AX] * (int32_t) src;
+                        
+                        cpu->regs[REG_AX] = (uint16_t) (res & 0xFFFF);
+                        cpu->regs[REG_DX] = (uint16_t) ((uint32_t) res >> 16);
+                        
+                        if (res < -32768 || res > 32767) {
+                            cpu->flags |= (FLAG_CF | FLAG_OF);
+                        } else {
+                            cpu->flags &= ~(FLAG_CF | FLAG_OF);
+                        }
+                        
+                        break;
+                    
+                    }
+                    
+                    case 6: {           /* DIV rm16 (Unsigned DX:AX / rm16) */
+                    
+                        uint32_t dividend, quotient, remainder;
+                        uint16_t divisor = operand_read16 (cpu, &rm);
+                        
+                        if (divisor == 0) {
+                            handle_interrupt (cpu, 0); break;
+                        }
+                        
+                        dividend = ((uint32_t) cpu->regs[REG_DX] << 16) | cpu->regs[REG_AX];
+                        quotient = dividend / divisor;
+                        remainder = dividend % divisor;
+                        
+                        if (quotient > 0xFFFF) {
+                            handle_interrupt (cpu, 0); break;
+                        }
+                        
+                        cpu->regs[REG_AX] = (uint16_t) quotient;
+                        cpu->regs[REG_DX] = (uint16_t) remainder;
+                        
+                        break;
+                    
+                    }
+                    
+                    case 7: {           /* IDIV rm16 (Signed DX:AX / rm16) */
+                    
+                        int32_t dividend, quotient, remainder;
+                        int16_t divisor = (int16_t) operand_read16 (cpu, &rm);
+                        
+                        if (divisor == 0) {
+                            handle_interrupt (cpu, 0); break;
+                        }
+                        
+                        dividend = (int32_t) (((uint32_t) cpu->regs[REG_DX] << 16) | cpu->regs[REG_AX]);
+                        quotient = dividend / divisor;
+                        remainder = dividend % divisor;
+                        
+                        if (quotient > 32767 || quotient < -32768) {
+                            handle_interrupt (cpu, 0); break;
+                        }
+                        
+                        cpu->regs[REG_AX] = (uint16_t) quotient;
+                        cpu->regs[REG_DX] = (uint16_t) remainder;
+                        
+                        break;
+                    
+                    }
+                    
+                    default:
+                    
+                        fprintf (stderr, "F7 Subop /%d not implemented\n", subop);
+                        return -1;
+                
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0xF8:             /* CLC (Clear Carry Flag) */
+        
+            cpu->flags &= (uint16_t) ~FLAG_CF;
+            break;
+        
+        case 0xF9:             /* STC (Set Carry Flag) */
+        
+            cpu->flags |= FLAG_CF;
+            break;
+        
+        case 0xF5:             /* CMC (Complement Carry Flag) */
+        
+            cpu->flags ^= FLAG_CF;
+            break;
+        
+        /* Handle CLI (Clear Interrupt Flag - 0xFA) */
+        case 0xFA: {
+        
+            cpu->flags &= ~FLAG_IF;     /* Clear the interrupt flag (IF) */
+            break;
+        
+        }
+        
+        /* Handle STI (Set Interrupt Flag - 0xFB) */
+        case 0xFB: {
+        
+            cpu->flags |= FLAG_IF;      /* Set the interrupt flag (IF) */
+            break;
+        
+        }
+        
+        case 0xFC:             /* CLD (Clear Direction Flag) */
+        
+            cpu->flags &= (uint16_t) ~FLAG_DF;
+            break;
+        
+        case 0xFD:             /* STD (Set Direction Flag) */
+        
+            cpu->flags |= FLAG_DF;
+            break;
+        
+        case 0xFE: {
+        
+            uint8_t modrm = fetch8 (cpu);
+            int subop = (modrm >> 3) & 7;
+            
+            OperandRef rm = decode_rm_operand (cpu, modrm);
+            uint8_t val = operand_read8 (cpu, &rm);
+            
+            if (subop == 0) {           /* INC rm8 */
+                operand_write8 (cpu, &rm, inc8 (cpu, val));
+            } else if (subop == 1) {    /* DEC rm8 */
+                operand_write8 (cpu, &rm, dec8 (cpu, val));
+            } else {
+            
+                fprintf (stderr, "Unhandled 0xFE extension /%d at %04X\n", subop, old_ip);
+                return -1;
+            
+            }
+            
+            break;
+        
+        }
+        
+        default:
+        
+            fprintf (stderr, "Unhandled opcode 0x%02X (%s) at %04X\n", op, opcode_name (op), old_ip);
+            return -1;
+    
+    }
+    
+    return 1;
+
+}
diff --git a/cpu.h b/cpu.h
new file mode 100644 (file)
index 0000000..c3bdb70
--- /dev/null
+++ b/cpu.h
@@ -0,0 +1,65 @@
+/******************************************************************************
+ * @file            cpu.h
+ *****************************************************************************/
+#ifndef     _CPU_H
+#define     _CPU_H
+
+#include    <stdint.h>
+#include    <stddef.h>
+
+enum {
+
+    REG_AX = 0,
+    REG_CX = 1,
+    REG_DX = 2,
+    REG_BX = 3,
+    REG_SP = 4,
+    REG_BP = 5,
+    REG_SI = 6,
+    REG_DI = 7
+
+};
+
+typedef struct __attribute__ ((packed)) CPU8086 {
+
+    uint16_t regs[8];                   /* General-purpose registers: AX, BX, CX, DX, SP, BP, SI, DI */
+    uint16_t ip;                        /* Instruction pointer */
+    uint16_t flags;                     /* Flags register */
+    uint16_t cs, ds, es, fs, gs, ss;    /* Segment registers */
+    
+    uint8_t *mem;                       /* Memory */
+    size_t mem_size;                    /* Memory size */
+    
+    int halted;                         /* Flag to indicate if the CPU is halted */
+    
+    uint16_t seg_override;
+    uint16_t psp_seg;
+    
+    int has_seg_override;
+    int has_addr_override;
+    int has_operand_override;
+    int rep_type;
+    
+    uint8_t port61, vga_status_flip;
+
+} CPU8086;
+
+uint32_t get_phys (uint16_t seg, uint16_t off);
+int cpu_step (CPU8086 *cpu);
+
+void cpu_reset (CPU8086 *cpu, uint8_t *mem, size_t mem_size);
+void cpu_dump (const CPU8086 *cpu);
+
+uint16_t cpu_get_reg (const CPU8086 *cpu, int reg);
+void cpu_set_reg (CPU8086 *cpu, int reg, uint16_t value);
+
+void push16 (CPU8086 *cpu, uint16_t value);
+uint16_t pop16 (CPU8086 *cpu);
+
+uint8_t fetch8 (CPU8086 *cpu);
+uint16_t fetch16 (CPU8086 *cpu);
+
+uint8_t get_reg8 (const CPU8086 *cpu, int reg);
+void set_reg8 (CPU8086 *cpu, int reg, uint8_t value);
+
+#endif      /* _CPU_H */
diff --git a/heap.c b/heap.c
new file mode 100644 (file)
index 0000000..f61be50
--- /dev/null
+++ b/heap.c
@@ -0,0 +1,25 @@
+/******************************************************************************
+ * @file            heap.c
+ *****************************************************************************/
+#include    <string.h>
+
+#include    "heap.h"
+#include    "cpu.h"
+
+void init_dos_heap (CPU8086 *cpu, uint16_t start_seg) {
+
+    uint32_t phys = (uint32_t) start_seg << 4, total_paras;
+    DOS_MCB first;
+    
+    /* 1. Zero out the entire 16-byte paragraph first */
+    memset (&first, 0, sizeof (DOS_MCB)); 
+    
+    first.type = 'Z'; 
+    first.owner = 0x0000; 
+    
+    total_paras = DOS_MEMORY_END_SEG;
+    first.size = (uint16_t)(total_paras - start_seg - 1); 
+    
+    memcpy (&cpu->mem[phys], &first, sizeof (DOS_MCB));
+
+}
diff --git a/heap.h b/heap.h
new file mode 100644 (file)
index 0000000..ff6f298
--- /dev/null
+++ b/heap.h
@@ -0,0 +1,26 @@
+/******************************************************************************
+ * @file            heap.h
+ *****************************************************************************/
+#ifndef     _HEAP_H
+#define     _HEAP_H
+
+#include    <stdint.h>
+
+#define     DOS_MEMORY_END_SEG          0x9FB0
+
+#define     DOS_IVT_STUB_SEG            DOS_MEMORY_END_SEG
+#define     DOS_CASE_MAP_SEG            0x9FF0
+
+typedef struct __attribute__ ((packed)) {
+
+    uint8_t type;                       /* 'M' (0x4D) for intermediate blocks, 'Z' (0x5A) for the last block */
+    
+    uint16_t owner;                     /* PSP segment of the owner (0 means free) */
+    uint16_t size;                      /* Size of the block in paragraphs (16-byte chunks) */
+    
+    uint8_t reserved[3];
+    uint8_t name[8];                    /* Optional: Program name */
+
+} DOS_MCB;
+
+#endif      /* _HEAP_H */
diff --git a/interrupts.c b/interrupts.c
new file mode 100644 (file)
index 0000000..f5009ac
--- /dev/null
@@ -0,0 +1,6008 @@
+/******************************************************************************
+ * @file            interrupts.c
+ *****************************************************************************/
+#include    <ctype.h>
+#include    <dirent.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+#include    <time.h>
+
+#include    <sys/stat.h>
+#include    <errno.h>
+#include    <fcntl.h>
+
+#if     defined (_WIN32)
+# include       <conio.h>
+# include       <direct.h>
+# include       <io.h>
+# include       <windows.h>
+#else
+# include       <sys/statvfs.h>
+# include       <termios.h>
+# include       <unistd.h>
+#endif
+
+#include    "cpu.h"
+#include    "memory.h"
+#include    "interrupts.h"
+#include    "heap.h"
+
+#if     defined (_WIN32)
+static int host_lstat (const char *path, struct stat *st) {
+
+    DWORD attributes = GetFileAttributesA (path);
+    
+    if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+    
+        /* A reparse point is neither a regular file nor a directory for lstat. */
+        memset (st, 0, sizeof (*st));
+        return 0;
+    
+    }
+    
+    return stat (path, st);
+
+}
+#else
+# define       host_lstat               lstat
+#endif
+
+/* Interrupt flag masks */
+#define     FLAG_CF                     0x0001
+#define     FLAG_ZF                     0x0040
+#define     FLAG_SF                     0x0080
+#define     FLAG_IF                     0x0200
+
+#define     MAX_DOS_HANDLES             20
+static FILE *file_handles[MAX_DOS_HANDLES] = { NULL };
+
+/* The DOS handle indexes the PSP's JFT; each JFT byte selects an SFT slot. */
+static int dos_get_sft (CPU8086 *cpu, uint16_t handle) {
+
+    uint32_t base = (uint32_t) cpu->psp_seg << 4;
+    uint8_t entry;
+    
+    if (handle >= MAX_DOS_HANDLES) {
+        return -1;
+    }
+    
+    entry = mem_read8 (cpu->mem, cpu->mem_size, base + 0x18 + handle);
+    
+    if (entry >= MAX_DOS_HANDLES || file_handles[entry] == NULL) {
+        return -1;
+    }
+    
+    return entry;
+
+}
+
+static FILE *dos_get_handle_file (CPU8086 *cpu, uint16_t handle) {
+
+    int sft = dos_get_sft (cpu, handle);
+    return sft < 0 ? NULL : file_handles[sft];
+
+}
+
+static void dos_set_jft (CPU8086 *cpu, uint16_t handle, uint8_t entry) {
+    mem_write8 (cpu->mem, cpu->mem_size, ((uint32_t) cpu->psp_seg << 4) + 0x18 + handle, entry);
+}
+
+/* The first five DOS handle slots are reserved for the standard devices. */
+static int get_free_handle (CPU8086 *cpu) {
+
+    uint32_t base = (uint32_t) cpu->psp_seg << 4;
+    int i;
+    
+    for (i = 0; i < MAX_DOS_HANDLES; i++) {
+    
+        if (mem_read8 (cpu->mem, cpu->mem_size, base + 0x18 + i) == 0xFF) {
+            return i;
+        }
+    
+    }
+    
+    return -1;
+
+}
+
+static int get_free_sft (void) {
+
+    int i;
+    
+    for (i = 5; i < MAX_DOS_HANDLES; i++) {
+        
+        if (file_handles[i] == NULL) {
+            return i;
+        }
+    
+    }
+    
+    return -1;
+
+}
+
+static int dos_close_handle (CPU8086 *cpu, uint16_t handle) {
+
+    uint32_t base = (uint32_t) cpu->psp_seg << 4;
+    int sft = dos_get_sft (cpu, handle), i;
+    
+    if (sft < 0) {
+        return 0;
+    }
+    
+    dos_set_jft (cpu, handle, 0xFF);
+    
+    for (i = 0; i < MAX_DOS_HANDLES; i++) {
+    
+        if (mem_read8 (cpu->mem, cpu->mem_size, base + 0x18 + i) == sft) {
+            return 1;
+        }
+    
+    }
+    
+    if (sft >= 5) {
+    
+        fclose (file_handles[sft]);
+        file_handles[sft] = NULL;
+    
+    }
+    
+    return 1;
+
+}
+
+static int is_valid_mcb (CPU8086 *cpu, uint16_t mcb_seg) {
+
+    uint32_t phys;
+    DOS_MCB *mcb;
+    
+    uint32_t end_phys;
+    uint32_t next_seg;
+    
+    /* Arena starts at MCB 004F, data starts at 0050, ends before DOS_MEMORY_END_SEG */
+    if (mcb_seg < 0x004F || mcb_seg >= DOS_MEMORY_END_SEG) {
+        return 0;
+    }
+    
+    phys = (uint32_t) mcb_seg << 4;
+    
+    if (phys + sizeof (DOS_MCB) > cpu->mem_size) {
+        return 0;
+    }
+    
+    mcb = (DOS_MCB *)&cpu->mem[phys];
+    
+    if (mcb->type != 'M' && mcb->type != 'Z') {
+        return 0;
+    }
+    
+    /**
+     * size is in paragraphs of DATA, not including the MCB paragraph itself.
+     * The next MCB is at mcb_seg + 1 + size.
+     */
+    next_seg = (uint32_t) mcb_seg + 1u + (uint32_t) mcb->size;
+    
+    /**
+     * End of this block's data area in bytes.
+     * 
+     * This must stay within RAM and below DOS_MEMORY_END_SEG.
+     */
+    end_phys = ((uint32_t) (mcb_seg + 1u) << 4) + ((uint32_t) mcb->size << 4);
+    
+    if (end_phys > cpu->mem_size) {
+        return 0;
+    }
+    
+    if (next_seg > DOS_MEMORY_END_SEG) {
+        return 0;
+    }
+    
+    /* If this is not the last block, the next MCB header itself must fit. */
+    if (mcb->type == 'M') {
+    
+        uint32_t next_phys = next_seg << 4;
+        
+        if (next_seg >= DOS_MEMORY_END_SEG) {
+            return 0;
+        }
+        
+        if (next_phys + sizeof (DOS_MCB) > cpu->mem_size) {
+            return 0;
+        }
+    
+    } else {
+    
+        /**
+         * Last block should end exactly at DOS_MEMORY_END_SEG if your arena
+         * is [0050, DOS_MEMORY_END_SEG). Relax this to <= if you want to
+         * tolerate setup differences.
+         */
+        if (next_seg != DOS_MEMORY_END_SEG) {
+            return 0;
+        }
+    
+    }
+    
+    return 1;
+
+}
+
+
+static uint16_t dos_dta_seg = 0;
+static uint16_t dos_dta_off = 0;
+
+static void dos_get_dta (CPU8086 *cpu, uint16_t *seg, uint16_t *off) {
+
+    if (dos_dta_seg == 0 && dos_dta_off == 0) {
+    
+        *seg = cpu->psp_seg;
+        *off = 0x0080;
+    
+    } else {
+    
+        *seg = dos_dta_seg;
+        *off = dos_dta_off;
+    
+    }
+
+}
+
+static void dos_write_console_char (FILE *out, uint8_t ch) {
+
+    static const uint16_t cp437[128] = {
+    
+        0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
+        0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+        0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
+        0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
+        0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
+        0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+        0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+        0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+        0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
+        0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+        0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
+        0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+        0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
+        0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+        0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
+        0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+    
+    };
+    
+    static int ansi_clear_state = 0;
+    uint16_t codepoint;
+    
+    if (ch < 0x80) {
+    
+        fputc (ch, out);
+        
+        if (out == stdout || out == stderr) {
+        
+            if (ansi_clear_state == 0 && ch == 0x1B) {
+                ansi_clear_state = 1;
+            } else if (ansi_clear_state == 1 && ch == '[') {
+                ansi_clear_state = 2;
+            } else if (ansi_clear_state == 2 && ch == '2') {
+                ansi_clear_state = 3;
+            } else if (ansi_clear_state == 3 && ch == 'J') {
+            
+                /* DOS ANSI.SYS homes the cursor after ESC[2J. */
+                fputs ("\033[H", out);
+                fputs ("\033[3J", out);
+                
+                ansi_clear_state = 0;
+            
+            } else {
+                ansi_clear_state = (ch == 0x1B) ? 1 : 0;
+            }
+        
+        }
+        
+        return;
+    
+    }
+    
+    if (out == stdout || out == stderr) {
+        ansi_clear_state = 0;
+    }
+    
+    codepoint = cp437[ch - 0x80];
+    
+    if (codepoint < 0x0800) {
+    
+        fputc (0xC0 | (codepoint >> 6), out);
+        fputc (0x80 | (codepoint & 0x3F), out);
+    
+    } else {
+    
+        fputc (0xE0 | (codepoint >> 12), out);
+        fputc (0x80 | ((codepoint >> 6) & 0x3F), out);
+        fputc (0x80 | (codepoint & 0x3F), out);
+    
+    }
+
+}
+
+static void dos_write_standard_output (CPU8086 *cpu, uint8_t ch) {
+
+    FILE *out = dos_get_handle_file (cpu, 1);
+    
+    if (out == stdout || out == stderr) {
+        dos_write_console_char (out, ch);
+    } else if (out != NULL) {
+        fputc (ch, out);
+    }
+
+}
+
+static uint16_t dos_last_error = 0;
+
+#if     defined (_WIN32)
+static int host_read_key_from (FILE *source) {
+
+    HANDLE handle;
+    DWORD old_mode, new_mode;
+    
+    int close_handle = 0;
+    int changed_mode = 0;
+    int ch = EOF;
+    
+    /* DOS programs can redirect STDIN, then reopen CON via handle duplication. */
+    if (source == stdout || source == stderr) {
+    
+        handle = CreateFileA ("CONIN$", GENERIC_READ | GENERIC_WRITE,
+                             FILE_SHARE_READ | FILE_SHARE_WRITE,
+                             NULL, OPEN_EXISTING, 0, NULL);
+        
+        if (handle == INVALID_HANDLE_VALUE) {
+            return EOF;
+        }
+        
+        close_handle = 1;
+    
+    } else {
+        handle = (HANDLE) _get_osfhandle (_fileno (source));
+    }
+    
+    if (handle == NULL || handle == INVALID_HANDLE_VALUE) {
+        return EOF;
+    }
+    
+    if (GetConsoleMode (handle, &old_mode)) {
+    
+        new_mode = old_mode;
+        new_mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
+        
+        if (SetConsoleMode (handle, new_mode)) {
+            changed_mode = 1;
+        }
+        
+        ch = _getch ();
+        
+        if (changed_mode) {
+            SetConsoleMode (handle, old_mode);
+        }
+    
+    } else if (!close_handle) {
+        ch = fgetc (source);
+    }
+    
+    if (close_handle) {
+        CloseHandle (handle);
+    }
+    
+    return ch;
+
+}
+
+static int host_poll_key_from (FILE *source) {
+
+    HANDLE handle;
+    DWORD old_mode, new_mode, available;
+    
+    int close_handle = 0;
+    int changed_mode = 0;
+    int ch = EOF;
+    
+    if (source == stdout || source == stderr) {
+    
+        handle = CreateFileA ("CONIN$", GENERIC_READ | GENERIC_WRITE,
+                             FILE_SHARE_READ | FILE_SHARE_WRITE,
+                             NULL, OPEN_EXISTING, 0, NULL);
+        
+        if (handle == INVALID_HANDLE_VALUE) {
+            return EOF;
+        }
+        
+        close_handle = 1;
+    
+    } else {
+        handle = (HANDLE) _get_osfhandle (_fileno (source));
+    }
+    
+    if (handle == NULL || handle == INVALID_HANDLE_VALUE) {
+        return EOF;
+    }
+    
+    if (GetConsoleMode (handle, &old_mode)) {
+    
+        new_mode = old_mode;
+        new_mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
+        
+        if (SetConsoleMode (handle, new_mode)) {
+            changed_mode = 1;
+        }
+        
+        if (_kbhit ()) {
+            ch = _getch ();
+        }
+        
+        if (changed_mode) {
+            SetConsoleMode (handle, old_mode);
+        }
+    
+    } else if (!close_handle) {
+    
+        if (GetFileType (handle) == FILE_TYPE_PIPE) {
+        
+            available = 0;
+            
+            if (PeekNamedPipe (handle, NULL, 0, NULL, &available, NULL)) {
+            
+                if (available != 0) {
+                    ch = fgetc (source);
+                }
+            
+            }
+        
+        } else if (GetFileType (handle) == FILE_TYPE_DISK) {
+            ch = fgetc (source);
+        }
+        
+        if (ch == EOF) {
+            clearerr (source);
+        }
+    
+    }
+    
+    if (close_handle) {
+        CloseHandle (handle);
+    }
+    
+    return ch;
+
+}
+#else
+static int host_read_key_from (FILE *source) {
+
+    struct termios oldt, newt;
+    
+    FILE *in = source;
+    int fd, ch;
+    
+    /* DOS programs can redirect STDIN, then reopen CON via handle duplication. */
+    if (source == stdout || source == stderr) {
+    
+        in = fopen ("/dev/tty", "rb");
+        
+        if (in == NULL) {
+            return EOF;
+        }
+    
+    }
+    
+    fd = fileno (in);
+    
+    if (tcgetattr (fd, &oldt) != 0) {
+        ch = fgetc (in);
+    } else {
+    
+        newt = oldt;
+        newt.c_lflag &= ~(ICANON | ECHO | ISIG);
+        newt.c_iflag &= ~(IXON | ICRNL);
+        newt.c_cc[VMIN] = 1;
+        newt.c_cc[VTIME] = 0;
+        
+        if (tcsetattr (fd, TCSANOW, &newt) != 0) {
+            ch = fgetc (in);
+        } else {
+        
+            ch = fgetc (in);
+            tcsetattr (fd, TCSANOW, &oldt);
+        
+        }
+    
+    }
+    
+    if (in != source) {
+        fclose (in);
+    }
+    
+    return ch;
+
+}
+
+static int host_poll_key_from (FILE *source) {
+
+    struct termios oldt, newt;
+    FILE *in = source;
+    
+    int fd, old_flags, changed_termios = 0;
+    int ch = EOF;
+    
+    if (source == stdout || source == stderr) {
+    
+        in = fopen ("/dev/tty", "rb");
+        
+        if (in == NULL) {
+            return EOF;
+        }
+    
+    }
+    
+    fd = fileno (in);
+    old_flags = fcntl (fd, F_GETFL, 0);
+    
+    if (old_flags >= 0) {
+    
+        if (tcgetattr (fd, &oldt) == 0) {
+        
+            newt = oldt;
+            newt.c_lflag &= ~(ICANON | ECHO | ISIG);
+            newt.c_iflag &= ~(IXON | ICRNL);
+            newt.c_cc[VMIN] = 1;
+            newt.c_cc[VTIME] = 0;
+            
+            if (tcsetattr (fd, TCSANOW, &newt) == 0) {
+                changed_termios = 1;
+            }
+        
+        }
+        
+        if (fcntl (fd, F_SETFL, old_flags | O_NONBLOCK) == 0) {
+        
+            ch = fgetc (in);
+            
+            if (ch == EOF) {
+                clearerr (in);
+            }
+            
+            fcntl (fd, F_SETFL, old_flags);
+        
+        }
+        
+        if (changed_termios) {
+            tcsetattr (fd, TCSANOW, &oldt);
+        }
+    
+    }
+    
+    if (in != source) {
+        fclose (in);
+    }
+    
+    return ch;
+
+}
+#endif
+
+static int host_read_key (CPU8086 *cpu) {
+
+    FILE *source = dos_get_handle_file (cpu, 0);
+    return host_read_key_from (source ? source : stdin);
+
+}
+
+/* Optional CHDIR path extension; AH=47h keeps its 64-byte output limit. */
+static int dos_long_paths = 0;
+
+#define     DOS_DIRECTORY_PATH_MAX      64
+static char dos_current_dir[260] = "";
+
+/* Extended BPB information for the emulated C: volume. */
+static uint32_t dos_volume_serial = 0x80860886;
+
+static uint8_t dos_volume_label[11] = { 'E', 'M', 'U', '8', '0', '8', '6', ' ', ' ', ' ', ' ' };
+static uint8_t dos_volume_fs[8] = { 'F', 'A', 'T', '1', '6', ' ', ' ', ' ' };
+
+static void dos_set_error (CPU8086 *cpu, uint16_t err) {
+
+    dos_last_error = err;
+    
+    cpu->regs[REG_AX] = err;
+    cpu->flags |= FLAG_CF;
+
+}
+
+static void dos_clear_success (CPU8086 *cpu) {
+    cpu->flags &= (uint16_t) ~FLAG_CF;
+}
+
+static void dos_get_file_datetime (time_t modified, uint16_t *dos_date, uint16_t *dos_time) {
+
+    const struct tm *local = localtime (&modified);
+    int year;
+    
+    if (local == NULL) {
+    
+        *dos_date = 0x0021;             /* 1 January 1980 */
+        *dos_time = 0;
+        
+        return;
+    
+    }
+    
+    year = local->tm_year + 1900;
+    
+    if (year < 1980) {
+    
+        *dos_date = 0x0021;
+        *dos_time = 0;
+        
+        return;
+    
+    }
+    
+    if (year > 2107) {
+    
+        *dos_date = 0xFF9F;             /* 31 December 2107 */
+        *dos_time = 0xBF7D;             /* 23:59:58 */
+        
+        return;
+    
+    }
+    
+    *dos_date = (uint16_t) (((year - 1980) << 9) | ((local->tm_mon + 1) << 5) | local->tm_mday);
+    *dos_time = (uint16_t) ((local->tm_hour << 11) | (local->tm_min << 5) | (local->tm_sec / 2));
+
+}
+
+static void dos_write_dta_find_result (CPU8086 *cpu, const char *name, uint32_t size, time_t modified, uint8_t attr) {
+
+    uint16_t dos_date, dos_time;
+    int i;
+    
+    uint16_t dta_seg, dta_off;
+    uint32_t base;
+    
+    dos_get_dta (cpu, &dta_seg, &dta_off);
+    base = get_phys (dta_seg, dta_off);
+    
+    /* Offset 21: Attribute */
+    mem_write8 (cpu->mem, cpu->mem_size, base + 21, attr);
+    
+    /* Offset 22-25: Time and Date */
+    dos_get_file_datetime (modified, &dos_date, &dos_time);
+    
+    mem_write16 (cpu->mem, cpu->mem_size, base + 22, dos_time);
+    mem_write16 (cpu->mem, cpu->mem_size, base + 24, dos_date);
+    
+    /* Offset 26-29: File Size */
+    mem_write32 (cpu->mem, cpu->mem_size, base + 26, size);
+    
+    /* Offset 30-42: Filename (ASCIIZ) */
+    /* CRITICAL: You must null-terminate this string! */
+    for (i = 0; i < 13; i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, base + 30 + i, 0);
+    }
+    
+    for (i = 0; name[i] && i < 12; i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, base + 30 + i, (uint8_t) name[i]);
+    }
+    
+    mem_write8 (cpu->mem, cpu->mem_size, base + 30 + i, 0);                /* Explicit null */
+
+}
+
+static void dos_read_asciiz (CPU8086 *cpu, uint16_t seg, uint16_t off, char *out, size_t out_sz) {
+
+    size_t i;
+    
+    if (out_sz == 0) {
+        return;
+    }
+    
+    for (i = 0; i + 1 < out_sz; i++) {
+    
+        out[i] = (char) mem_read8 (cpu->mem, cpu->mem_size, get_phys (seg, (uint16_t) (off + i)));
+        
+        if (out[i] == '\0') {
+            break;
+        }
+    
+    }
+    
+    out[out_sz - 1] = '\0';
+
+}
+
+static void dos_append_bounded (char *dest, size_t dest_size, const char *src);
+
+static void dos_collapse_dotdot (char *path) {
+
+    char tmp[260];
+    char out[260];
+    char *parts[64];
+    char *tok;
+    
+    int n = 0;
+    int i;
+    
+    snprintf (tmp, sizeof (tmp), "%s", path);
+    tok = strtok (tmp, "/\\");
+    
+    while (tok) {
+    
+        if (strcmp (tok, ".") == 0) {
+            /* skip */
+        } else if (strcmp (tok, "..") == 0) {
+        
+            if (n > 0) {
+                n--;
+            }
+        
+        } else {
+        
+            if (n < 64) {
+                parts[n++] = tok;
+            }
+        
+        }
+        
+        tok = strtok (NULL, "/\\");
+    
+    }
+    
+    out[0] = '\0';
+    
+    for (i = 0; i < n; i++) {
+    
+        if (i > 0) {
+            dos_append_bounded (out, sizeof (out), "/");
+        }
+        
+        dos_append_bounded (out, sizeof (out), parts[i]);
+    
+    }
+    
+    snprintf (path, 260, "%s", out);
+
+}
+
+static void dos_normalize_path (char *path) {
+
+    size_t i, j;
+    
+    if (path[0] && path[1] == ':') {
+        memmove (path, path + 2, strlen (path + 2) + 1);
+    }
+    
+    while (path[0] == '\\' || path[0] == '/') {
+        memmove (path, path + 1, strlen (path));
+    }
+    
+    for (i = 0; path[i]; i++) {
+    
+        if (path[i] == '\\') {
+            path[i] = '/';
+        }
+    
+    }
+    
+    while (path[0] == '.' && path[1] == '/') {
+        memmove (path, path + 2, strlen (path + 2) + 1);
+    }
+    
+    for (i = 0, j = 0; path[i]; i++) {
+    
+        if (path[i] == '/' && j > 0 && path[j - 1] == '/') {
+            continue;
+        }
+        
+        path[j++] = path[i];
+    
+    }
+    
+    path[j] = '\0';
+    dos_collapse_dotdot (path);
+
+}
+
+static char dos_drive_root[260] = ".";
+
+static void dos_append_bounded (char *dest, size_t dest_size, const char *src) {
+
+    size_t used;
+    size_t len;
+    
+    if (dest_size == 0) {
+        return;
+    }
+    
+    used = strlen (dest);
+    
+    if (used >= dest_size - 1) {
+        return;
+    }
+    
+    len = strlen (src);
+    
+    if (len > dest_size - used - 1) {
+        len = dest_size - used - 1;
+    }
+    
+    memcpy (dest + used, src, len);
+    dest[used + len] = '\0';
+
+}
+
+static int dos_resolve_existing_host_path (const char *dos_rel, char *host_out, size_t host_out_sz);
+static int dos_83_cache_get_host (const char *dirpath, const char *name, char *host_out, size_t host_out_sz);
+
+static void dos_resolve_path (const char *in, char *out, size_t out_sz) {
+
+    char tmp[260], dos_rel[520], host_base[520], host_leaf[260];
+    
+    char *leaf;
+    int absolute;
+    
+    if (out_sz == 0) {
+        return;
+    }
+    
+    tmp[0] = '\0';
+    dos_append_bounded (tmp, sizeof (tmp), in ? in : "");
+    
+    dos_normalize_path (tmp);
+    
+    absolute = in && (in[0] == '\\' || in[0] == '/' || (in[0] && in[1] == ':'));
+    dos_rel[0] = '\0';
+    
+    if (!absolute && dos_current_dir[0] != '\0') {
+    
+        dos_append_bounded (dos_rel, sizeof (dos_rel), dos_current_dir);
+        dos_append_bounded (dos_rel, sizeof (dos_rel), "/");
+    
+    }
+    
+    dos_append_bounded (dos_rel, sizeof (dos_rel), tmp);
+    
+    host_base[0] = '\0';
+    dos_append_bounded (host_base, sizeof (host_base), dos_drive_root);
+    
+    leaf = strrchr (dos_rel, '/');
+    
+    if (leaf) {
+    
+        *leaf++ = '\0';
+        
+        if (!dos_resolve_existing_host_path (dos_rel, host_base, sizeof (host_base))) {
+        
+            host_base[0] = '\0';
+            
+            dos_append_bounded (host_base, sizeof (host_base), dos_drive_root);
+            dos_append_bounded (host_base, sizeof (host_base), "/");
+            dos_append_bounded (host_base, sizeof (host_base), dos_rel);
+        
+        }
+    
+    } else {
+        leaf = dos_rel;
+    }
+    
+    if (leaf[0] && !strchr (leaf, '*') && !strchr (leaf, '?') &&
+        dos_83_cache_get_host (host_base, leaf, host_leaf, sizeof (host_leaf))) {
+        leaf = host_leaf;
+    }
+    
+    out[0] = '\0';
+    
+    dos_append_bounded (out, out_sz, host_base);
+    dos_append_bounded (out, out_sz, "/");
+    dos_append_bounded (out, out_sz, leaf);
+    
+    /* NOTE: Do NOT use dos_normalize_path on 'out' here because it */
+    /* will strip the leading '/' required by the Linux filesystem. */
+
+}
+
+typedef struct {
+    
+    char dos83[12];                     /* 8 + 3 + NUL */
+    char host[260];
+
+} DOS83_MAP;
+
+static void dos_make_83_display_parts (const char *src, char name[9], char ext[4]);
+
+static int dos_char_ok_83 (unsigned char c) {
+
+    c = (unsigned char) toupper (c);
+    
+    if (c >= 'A' && c <= 'Z') {
+        return 1;
+    }
+    
+    if (c >= '0' && c <= '9') {
+        return 1;
+    }
+    
+    switch (c) {
+        
+        case '$': case '%': case '\'': case '-':
+        case '_': case '@': case '~': case '`':
+        case '!': case '(': case ')': case '{':
+        case '}': case '^': case '#': case '&':
+        
+            return 1;
+        
+    }
+    
+    return 0;
+
+}
+
+static void dos_make_83_parts (const char *src, char name[9], char ext[4]) {
+
+    const char *base;
+    const char *dot;
+    
+    int i;
+    
+    memset (name, ' ', 8);
+    name[8] = '\0';
+    
+    memset (ext, ' ', 3);
+    ext[3] = '\0';
+    
+    base = strrchr (src, '/');
+    
+    if (!base) {
+        base = strrchr (src, '\\');
+    }
+    
+    base = base ? base + 1 : src;
+    
+    /* Host dotfiles: expose ".ssh" as DOS name "SSH", not ".SSH" */
+    if (base[0] == '.' && base[1] != '\0') {
+        base++;
+    }
+    
+    dot = strchr (base, '.');
+    
+    if (dot) {
+    
+        for (i = 0; i < 8 && base[i] && &base[i] < dot; i++) {
+            name[i] = (char) toupper ((int) base[i]);
+        }
+        
+        dot++;
+        
+        for (i = 0; i < 3 && dot[i]; i++) {
+            ext[i] = (char) toupper ((int) dot[i]);
+        }
+    
+    } else {
+    
+        for (i = 0; i < 8 && base[i]; i++) {
+            name[i] = (char) toupper ((int) base[i]);
+        }
+    
+    }
+
+}
+
+static void dos_make_83_display_parts (const char *src, char name[9], char ext[4]) {
+
+    const char *base;
+    const char *dot;
+    
+    int i, j;
+    int needs_short = 0;
+    
+    memset (name, ' ', 8);
+    name[8] = '\0';
+    
+    memset (ext, ' ', 3);
+    ext[3] = '\0';
+    
+    base = strrchr (src, '/');
+    
+    if (!base) {
+        base = strrchr (src, '\\');
+    }
+    
+    base = base ? base + 1 : src;
+    
+    if (base[0] == '.' && base[1] != '\0') {
+        base++;
+    }
+    
+    dot = strrchr (base, '.');
+    
+    for (i = 0; base[i]; i++) {
+    
+        unsigned char c = (unsigned char) base[i];
+        
+        if (c == '.') {
+            continue;
+        }
+        
+        if (!dos_char_ok_83 (c) || c == ' ' || c == '\t') {
+        
+            needs_short = 1;
+            break;
+        
+        }
+    
+    }
+    
+    if (!needs_short && strlen (base) <= 12 && !strchr (base, '_')) {
+    
+        dos_make_83_parts (src, name, ext);
+        return;
+    
+    }
+    
+    j = 0;
+    
+    for (i = 0; base[i] && base + i != dot && j < 6; i++) {
+    
+        unsigned char c = (unsigned char) base[i];
+        
+        if (dos_char_ok_83 (c)) {
+            name[j++] = (char) toupper (c);
+        }
+    
+    }
+    
+    if (j == 0) {
+        name[j++] = '_';
+    }
+    
+    while (j < 6) {
+        name[j++] = ' ';
+    }
+    
+    name[6] = '~';
+    name[7] = '1';
+    
+    if (dot) {
+    
+        j = 0;
+        dot++;
+        
+        for (i = 0; dot[i] && j < 3; i++) {
+        
+            unsigned char c = (unsigned char) dot[i];
+            
+            if (dos_char_ok_83 (c)) {
+                ext[j++] = (char) toupper (c);
+            }
+        
+        }
+    
+    }
+
+}
+
+#define     DOS_83_CACHE_SLOTS          16
+
+typedef struct {
+
+    char *host;
+    int index;
+    
+    int needs_short;
+    char dos_name[13];
+
+} DOS_83_NAME_ENTRY;
+
+typedef struct {
+
+    char key[10];
+    int count;
+    int used;
+
+} DOS_83_COUNT_ENTRY;
+
+typedef struct {
+
+    char key[11];
+    int used;
+
+} DOS_83_USED_ENTRY;
+
+typedef struct {
+
+    char *dirpath;
+    
+    DOS_83_NAME_ENTRY *names;
+    size_t capacity;
+    
+    struct stat identity;
+    unsigned long age;
+
+} DOS_83_CACHE;
+
+static DOS_83_CACHE dos_83_cache[DOS_83_CACHE_SLOTS];
+static unsigned long dos_83_cache_age;
+
+static unsigned long dos_83_hash (const void *data, size_t length) {
+
+    const unsigned char *p = data;
+    unsigned long h = 2166136261UL;
+    
+    size_t i;
+    
+    for (i = 0; i < length; i++) {
+        h = (h ^ p[i]) * 16777619UL;
+    }
+    
+    return h;
+
+}
+
+static int dos_83_needs_short (const char *host) {
+
+    const char *dot = strchr (host, '.');
+    
+    int base_len;
+    int ext_len;
+    
+    if (dot) {
+    
+        base_len = (int) (dot - host);
+        ext_len = (int) strlen (dot + 1);
+    
+    } else {
+    
+        base_len = (int) strlen (host);
+        ext_len = 0;
+    
+    }
+    
+    return strchr (host, '_') != NULL || strchr (host, ' ') != NULL || strchr (host, '+') != NULL || strchr (host, '-') != NULL || (dot != NULL && strchr (dot + 1, '.') != NULL) || base_len > 8 || ext_len > 3;
+
+}
+
+static int dos_83_format_alias (const char *host, int index, char *out) {
+
+    char name[9], ext[4];
+    char prefix[7];
+    char suffix[16];
+    
+    size_t prefix_len;
+    size_t suffix_len;
+    
+    int i, n = 0;
+    dos_make_83_display_parts (host, name, ext);
+    
+    if (index == 0) {
+    
+        for (i = 7; i >= 0 && name[i] == ' '; i--) {
+            name[i] = '\0';
+        }
+        
+        for (i = 2; i >= 0 && ext[i] == ' '; i--) {
+            ext[i] = '\0';
+        }
+        
+        if (ext[0]) {
+            snprintf (out, 13, "%s.%s", name, ext);
+        } else {
+            snprintf (out, 13, "%s", name);
+        }
+        
+        return 1;
+    
+    }
+    
+    for (i = 0; i < 8 && name[i] != ' ' && n < 6; i++) {
+        prefix[n++] = name[i];
+    }
+    
+    prefix[n] = '\0';
+    
+    snprintf (suffix, sizeof (suffix), "~%d", index);
+    suffix_len = strlen (suffix);
+    
+    if (suffix_len >= 8) {
+        return 0;
+    }
+    
+    prefix_len = 8 - suffix_len;
+    
+    if (prefix_len > (size_t) n) {
+        prefix_len = (size_t) n;
+    }
+    
+    memcpy (out, prefix, prefix_len);
+    out[prefix_len] = '\0';
+    
+    dos_append_bounded (out, 13, suffix);
+    
+    if (ext[0] != ' ') {
+    
+        dos_append_bounded (out, 13, ".");
+        dos_append_bounded (out, 13, ext);
+    
+    }
+    
+    i = (int) strlen (out) - 1;
+    
+    while (i >= 0 && out[i] == ' ') {
+        out[i--] = '\0';
+    }
+    
+    return 1;
+
+}
+
+static void dos_83_alias_key (const char *name, char key[11]) {
+
+    const char *dot = strchr (name, '.');
+    size_t i;
+    
+    memset (key, ' ', 11);
+    
+    for (i = 0; i < 8 && name[i] && (!dot || name + i < dot); i++) {
+        key[i] = (char) toupper ((int) name[i]);
+    }
+    
+    if (dot) {
+    
+        dot++;
+        
+        for (i = 0; i < 3 && dot[i]; i++) {
+            key[8 + i] = (char) toupper ((int) dot[i]);
+        }
+    
+    }
+
+}
+
+static int dos_83_reserve_name (DOS_83_USED_ENTRY *used, size_t capacity, const char *name) {
+
+    char key[11];
+    size_t slot;
+    
+    dos_83_alias_key (name, key);
+    slot = dos_83_hash (key, sizeof (key)) & (capacity - 1);
+    
+    while (used[slot].used) {
+    
+        if (memcmp (used[slot].key, key, sizeof (key)) == 0) {
+            return 0;
+        }
+        
+        slot = (slot + 1) & (capacity - 1);
+    
+    }
+    
+    memcpy (used[slot].key, key, sizeof (key));
+    
+    used[slot].used = 1;
+    return 1;
+
+}
+
+static void dos_83_cache_release (DOS_83_CACHE *cache) {
+
+    size_t i;
+    
+    if (cache->names) {
+    
+        for (i = 0; i < cache->capacity; i++) {
+            free (cache->names[i].host);
+        }
+    
+    }
+    
+    free (cache->names);
+    free (cache->dirpath);
+    
+    memset (cache, 0, sizeof (*cache));
+
+}
+
+static void dos_83_cache_invalidate (void) {
+
+    size_t i;
+
+    for (i = 0; i < DOS_83_CACHE_SLOTS; i++) {
+        dos_83_cache_release (&dos_83_cache[i]);
+    }
+
+}
+
+static int dos_83_cache_same_directory (const struct stat *a, const struct stat *b) {
+    return a->st_dev == b->st_dev && a->st_ino == b->st_ino && a->st_size == b->st_size && a->st_mtime == b->st_mtime && a->st_ctime == b->st_ctime;
+}
+
+static int dos_83_cache_build (DOS_83_CACHE *cache, const char *dirpath, const struct stat *identity) {
+
+    DOS_83_COUNT_ENTRY *counts = NULL;
+    DOS_83_USED_ENTRY *used = NULL;
+    
+    struct dirent *de;
+    DIR *dir;
+    
+    struct stat after;
+    size_t i;
+    
+    size_t capacity = 16;
+    size_t num_entries = 0;
+    
+    dir = opendir (dirpath);
+
+    if (!dir) {
+        return 0;
+    }
+    
+    while ((de = readdir (dir)) != NULL) {
+    
+        if (strcmp (de->d_name, ".") != 0 && strcmp (de->d_name, "..") != 0) {
+            num_entries++;
+        }
+    
+    }
+    
+    while (capacity <= num_entries && capacity <= ((size_t) -1) / 2) {
+        capacity *= 2;
+    }
+    
+    if (capacity <= num_entries || capacity > ((size_t) -1) / 2) {
+    
+        closedir (dir);
+        return 0;
+    
+    }
+    
+    capacity *= 2;
+    
+    cache->names = calloc (capacity, sizeof (*cache->names));
+    
+    counts = calloc (capacity, sizeof (*counts));
+    used = calloc (capacity, sizeof (*used));
+    
+    cache->capacity = capacity;
+    
+    if (!cache->names || !counts || !used) {
+        goto failure;
+    }
+    
+    cache->dirpath = malloc (strlen (dirpath) + 1);
+    
+    if (!cache->dirpath) {
+        goto failure;
+    }
+    
+    strcpy (cache->dirpath, dirpath);
+    rewinddir (dir);
+    
+    while ((de = readdir (dir)) != NULL) {
+    
+        char name[9], ext[4], prefix[7], key[10];
+        size_t j, index;
+        
+        int n = 0;
+        
+        if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) {
+            continue;
+        }
+        
+        dos_make_83_display_parts (de->d_name, name, ext);
+        memset (prefix, 0, sizeof (prefix));
+        
+        for (i = 0; i < 8 && name[i] != ' ' && n < 6; i++) {
+            prefix[n++] = name[i];
+        }
+        
+        memcpy (key, prefix, 7);
+        memcpy (key + 7, ext, 3);
+        
+        j = dos_83_hash (key, sizeof (key)) & (capacity - 1);
+        
+        while (counts[j].used && memcmp (counts[j].key, key, sizeof (key)) != 0) {
+            j = (j + 1) & (capacity - 1);
+        }
+        
+        if (!counts[j].used) {
+        
+            memcpy (counts[j].key, key, sizeof (key));
+            counts[j].used = 1;
+        
+        }
+        
+        counts[j].count++;
+        index = dos_83_hash (de->d_name, strlen (de->d_name)) & (capacity - 1);
+        
+        while (cache->names[index].host) {
+            index = (index + 1) & (capacity - 1);
+        }
+        
+        cache->names[index].host = malloc (strlen (de->d_name) + 1);
+        
+        if (!cache->names[index].host) {
+            goto failure;
+        }
+        
+        strcpy (cache->names[index].host, de->d_name);
+        
+        cache->names[index].index = counts[j].count;
+        cache->names[index].needs_short = dos_83_needs_short (de->d_name);
+    
+    }
+    
+    /* Reserve existing DOS-compatible names before assigning generated aliases. */
+    for (i = 0; i < capacity; i++) {
+    
+        DOS_83_NAME_ENTRY *entry = &cache->names[i];
+        
+        if (!entry->host || entry->needs_short) {
+            continue;
+        }
+        
+        dos_83_format_alias (entry->host, 0, entry->dos_name);
+        
+        if (!dos_83_reserve_name (used, capacity, entry->dos_name)) {
+        
+            entry->needs_short = 1;
+            entry->dos_name[0] = '\0';
+        
+        }
+    
+    }
+    
+    /* Assign suffixes in directory order, checking the whole 8.3 namespace. */
+    rewinddir (dir);
+    
+    while ((de = readdir (dir)) != NULL) {
+    
+        DOS_83_NAME_ENTRY *entry;
+        size_t slot;
+        int index;
+        
+        if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) {
+            continue;
+        }
+        
+        slot = dos_83_hash (de->d_name, strlen (de->d_name)) & (capacity - 1);
+        
+        while (cache->names[slot].host && strcmp (cache->names[slot].host, de->d_name) != 0) {
+            slot = (slot + 1) & (capacity - 1);
+        }
+        
+        if (!cache->names[slot].host) {
+            goto failure;
+        }
+        
+        entry = &cache->names[slot];
+        
+        if (!entry->needs_short) {
+            continue;
+        }
+        
+        index = entry->index;
+        
+        while (dos_83_format_alias (entry->host, index, entry->dos_name)) {
+        
+            if (dos_83_reserve_name (used, capacity, entry->dos_name)) {
+                break;
+            }
+            
+            if (index == 9999999) {
+                goto failure;
+            }
+            
+            index++;
+        
+        }
+        
+        if (index > 9999999) {
+            goto failure;
+        }
+        
+        entry->index = index;
+    
+    }
+    
+    if (stat (dirpath, &after) != 0 || !dos_83_cache_same_directory (identity, &after)) {
+        goto failure;
+    }
+    
+    cache->identity = after;
+    
+    free (counts);
+    free (used);
+    
+    closedir (dir);
+    return 1;
+    
+failure:
+    
+    free (counts);
+    free (used);
+    
+    closedir (dir);
+    dos_83_cache_release (cache);
+    
+    return 0;
+
+}
+
+static DOS_83_CACHE *dos_83_cache_get (const char *dirpath) {
+
+    DOS_83_CACHE *cache = NULL;
+    size_t i;
+    
+    struct stat identity;
+    
+    if (stat (dirpath, &identity) != 0 || !S_ISDIR (identity.st_mode)) {
+        return 0;
+    }
+    
+    for (i = 0; i < DOS_83_CACHE_SLOTS; i++) {
+    
+        if (dos_83_cache[i].dirpath && strcmp (dos_83_cache[i].dirpath, dirpath) == 0) {
+        
+            cache = &dos_83_cache[i];
+            
+            if (!dos_83_cache_same_directory (&cache->identity, &identity)) {
+                dos_83_cache_release (cache);
+            }
+            
+            break;
+        
+        }
+    
+    }
+    
+    if (!cache || !cache->dirpath) {
+    
+        cache = &dos_83_cache[0];
+        
+        for (i = 0; i < DOS_83_CACHE_SLOTS; i++) {
+        
+            if (!dos_83_cache[i].dirpath) {
+            
+                cache = &dos_83_cache[i];
+                break;
+            
+            }
+            
+            if (dos_83_cache[i].age < cache->age) {
+                cache = &dos_83_cache[i];
+            }
+        
+        }
+        
+        dos_83_cache_release (cache);
+        
+        if (!dos_83_cache_build (cache, dirpath, &identity)) {
+            return 0;
+        }
+    
+    }
+    
+    cache->age = ++dos_83_cache_age;
+    return cache;
+
+}
+
+static int dos_83_cache_get_name (const char *dirpath, const char *host, char out[13]) {
+
+    DOS_83_CACHE *cache = dos_83_cache_get (dirpath);
+    size_t j;
+    
+    if (!cache) {
+        return 0;
+    }
+    
+    j = dos_83_hash (host, strlen (host)) & (cache->capacity - 1);
+    
+    while (cache->names[j].host) {
+    
+        if (strcmp (cache->names[j].host, host) == 0) {
+        
+            memcpy (out, cache->names[j].dos_name, sizeof (cache->names[j].dos_name));
+            return 1;
+        
+        }
+        
+        j = (j + 1) & (cache->capacity - 1);
+    
+    }
+    
+    return 0;
+
+}
+
+static int dos_83_cache_get_host (const char *dirpath, const char *name, char *host_out, size_t host_out_sz) {
+
+    DOS_83_CACHE *cache = dos_83_cache_get (dirpath);
+    size_t i;
+    
+    if (!cache || host_out_sz == 0) {
+        return 0;
+    }
+    
+    /* Prefer a real host name when it is also an 8.3 alias. */
+    for (i = 0; i < cache->capacity; i++) {
+    
+        if (cache->names[i].host && strcasecmp (cache->names[i].host, name) == 0) {
+        
+            if (strlen (cache->names[i].host) >= host_out_sz) {
+                return 0;
+            }
+            
+            strcpy (host_out, cache->names[i].host);
+            return 1;
+        
+        }
+    
+    }
+    
+    for (i = 0; i < cache->capacity; i++) {
+    
+        if (cache->names[i].host && strcasecmp (cache->names[i].dos_name, name) == 0) {
+        
+            if (strlen (cache->names[i].host) >= host_out_sz) {
+                return 0;
+            }
+            
+            strcpy (host_out, cache->names[i].host);
+            return 1;
+        
+        }
+    
+    }
+    
+    return 0;
+
+}
+
+static void dos_make_83_display_name_unique (const char *dirpath, const char *host, char *out) {
+
+    struct dirent *de;
+    DIR *d;
+    
+    char my_n[9], my_e[4];
+    char my_prefix[7];
+    
+    int needs_short;
+    int index = 1;
+    int k, len;
+    
+    if (dos_83_cache_get_name (dirpath, host, out)) {
+        return;
+    }
+    
+    dos_make_83_display_parts (host, my_n, my_e);
+    
+    {
+    
+        const char *dot;
+        
+        int base_len;
+        int ext_len;
+        
+        dot = strchr (host, '.');
+        
+        if (dot) {
+        
+            base_len = (int) (dot - host);
+            ext_len = (int) strlen (dot + 1);
+        
+        } else {
+        
+            base_len = (int) strlen (host);
+            ext_len = 0;
+        
+        }
+        
+        needs_short = strchr (host, '_') != NULL || strchr (host, ' ') != NULL || strchr (host, '+') != NULL || strchr (host, '-') != NULL || (dot != NULL && strchr (dot + 1, '.') != NULL) || base_len > 8 || ext_len > 3;
+    
+    }
+    
+    if (!needs_short) {
+    
+        dos_83_format_alias (host, 0, out);
+        return;
+    
+    }
+    
+    memset (my_prefix, 0, sizeof (my_prefix));
+    len = 0;
+    
+    for (k = 0; k < 8 && my_n[k] != ' '; k++) {
+    
+        if (len < 6) {
+            my_prefix[len++] = my_n[k];
+        }
+    
+    }
+    
+    my_prefix[len] = '\0';
+    
+    /**
+     * Use position among matching entries.
+     *
+     * This intentionally gives:
+     *   java2dex_fields.c    -> JAVA2D~1.C
+     *   java2dex_.c         -> JAVA2D~2.C
+     *   ...
+     *   java2dex_ (2).c     -> JAVA2D~10.C
+     *
+     * Do NOT break before counting this file.
+     */
+    d = opendir (dirpath);
+    
+    if (d) {
+    
+        while ((de = readdir (d)) != NULL) {
+        
+            char on[9], oe[4];
+            char oprefix[7];
+            
+            int olen = 0;
+            
+            if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) {
+                continue;
+            }
+            
+            dos_make_83_display_parts (de->d_name, on, oe);
+            memset (oprefix, 0, sizeof (oprefix));
+            
+            for (k = 0; k < 8 && on[k] != ' '; k++) {
+            
+                if (olen < 6) {
+                    oprefix[olen++] = on[k];
+                }
+            
+            }
+            
+            oprefix[olen] = '\0';
+            
+            if (strcasecmp (oprefix, my_prefix) == 0 && memcmp (oe, my_e, 3) == 0) {
+            
+                if (strcasecmp (oprefix, my_prefix) == 0 && memcmp (oe, my_e, 3) == 0) {
+                
+                    if (strcmp (de->d_name, host) == 0) {
+                    
+                        /* this is OUR file â†’ count how many come BEFORE it */
+                        index = 1;
+                        rewinddir (d);
+                        
+                        while ((de = readdir (d)) != NULL) {
+                        
+                            char on2[9], oe2[4];
+                            char oprefix2[7];
+                            
+                            int olen2 = 0;
+                            
+                            if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) {
+                                continue;
+                            }
+                            
+                            if (strcmp (de->d_name, host) == 0) {
+                                break;
+                            }
+                            
+                            dos_make_83_display_parts (de->d_name, on2, oe2);
+                            
+                            memset (oprefix2, 0, sizeof (oprefix2));
+                            
+                            for (k = 0; k < 8 && on2[k] != ' '; k++) {
+                            
+                                if (olen2 < 6) {
+                                    oprefix2[olen2++] = on2[k];
+                                }
+                            
+                            }
+                            
+                            oprefix2[olen2] = '\0';
+                            
+                            if (strcasecmp (oprefix2, my_prefix) == 0 && memcmp (oe2, my_e, 3) == 0) {
+                                index++;
+                            }
+                        
+                        }
+                        
+                        break;
+                    
+                    }
+                
+                }
+            
+            }
+        
+        }
+        
+        closedir (d);
+    
+    }
+    
+    {
+    
+        char suffix[16];
+        char full[32];
+        
+        size_t prefix_len;
+        size_t suffix_len;
+        size_t len;
+        
+        snprintf (suffix, sizeof (suffix), "~%d", index);
+        suffix_len = strlen (suffix);
+        
+        /* Leave enough room for the complete decimal suffix in 8.3. */
+        prefix_len = suffix_len < 8 ? 8 - suffix_len : 0;
+        
+        if (prefix_len > strlen (my_prefix)) {
+            prefix_len = strlen (my_prefix);
+        }
+        
+        memcpy (full, my_prefix, prefix_len);
+        full[prefix_len] = '\0';
+        
+        dos_append_bounded (full, sizeof (full), suffix);
+        
+        if (my_e[0] != ' ') {
+        
+            dos_append_bounded (full, sizeof (full), ".");
+            dos_append_bounded (full, sizeof (full), my_e);
+        
+        }
+        
+        len = strlen (full);
+        
+        if (len > 12) {
+            len = 12;
+        }
+        
+        memcpy (out, full, len);
+        out[len] = '\0';
+    
+    }
+    
+    k = (int) strlen (out) - 1;
+    
+    while (k >= 0 && out[k] == ' ') {
+        out[k--] = '\0';
+    }
+
+}
+
+static int dos_match_83_field (const char *pat, const char *val, int len) {
+
+    int i;
+    
+    for (i = 0; i < len; i++) {
+    
+        char p = (char) toupper ((int) pat[i]);
+        char v = (char) toupper ((int) val[i]);
+        
+        if (p == '*') {
+            return 1; /* rest matches */
+        }
+        
+        if (p == '?') {
+            continue; /* any char in this field */
+        }
+        
+        if (p != v) {
+            return 0;
+        }
+    
+    }
+    
+    return 1;
+
+}
+
+static int dos_wildcard_match (const char *pattern, const char *name) {
+
+    char pat_name[9], pat_ext[4];
+    char val_name[9], val_ext[4];
+    
+    if (strcmp (pattern, "*.*") == 0 || strcmp (pattern, "*") == 0) {
+        return 1;
+    }
+    
+    dos_make_83_parts (pattern, pat_name, pat_ext);
+    dos_make_83_parts (name,    val_name, val_ext);
+    
+    return dos_match_83_field (pat_name, val_name, 8) && dos_match_83_field (pat_ext,  val_ext,  3);
+
+}
+
+static void dos_split_dir_pattern (const char *input, char *dir_out, size_t dir_sz, char *pat_out, size_t pat_sz) {
+
+    const char *slash1 = strrchr (input, '/');
+    const char *slash2 = strrchr (input, '\\');
+    const char *slash = slash1;
+    
+    if (slash2 && (!slash1 || slash2 > slash1)) {
+        slash = slash2;
+    }
+    
+    if (slash) {
+    
+        size_t dir_len = (size_t) (slash - input);
+        
+        if (dir_len >= dir_sz) {
+            dir_len = dir_sz - 1;
+        }
+        
+        memcpy (dir_out, input, dir_len);
+        dir_out[dir_len] = '\0';
+        
+        strncpy (pat_out, slash + 1, pat_sz - 1);
+        pat_out[pat_sz - 1] = '\0';
+        
+        if (dir_out[0] == '\0') {
+            strcpy (dir_out, ".");
+        }
+    
+    } else {
+    
+        strcpy (dir_out, ".");
+        strncpy (pat_out, input, pat_sz - 1);
+        
+        pat_out[pat_sz - 1] = '\0';
+    
+    }
+    
+    if (pat_out[0] == '\0') {
+        strcpy (pat_out, "*");
+    }
+
+}
+
+FILE *dos_fopen_case_insensitive (const char *path, const char *mode) {
+
+    char dir[260], name[128];
+    
+    struct dirent *ent;
+    DIR *d;
+    
+    dos_split_dir_pattern (path, dir, sizeof (dir), name, sizeof (name));
+    
+    d = opendir (dir[0] ? dir : ".");
+    
+    if (!d) {
+        return NULL;
+    }
+    
+    while ((ent = readdir (d)) != NULL) {
+    
+        if (strcasecmp (ent->d_name, name) == 0) {
+        
+            char full[512];
+            snprintf (full, sizeof (full), "%s/%s", dir[0] ? dir : ".", ent->d_name);
+            
+            closedir (d);
+            return fopen (full, mode);
+        
+        }
+    
+    }
+    
+    closedir (d);
+    return NULL;
+
+}
+
+static void dos_buffered_line_input (CPU8086 *cpu) {
+
+    uint16_t off = cpu->regs[REG_DX];
+    uint32_t phys = get_phys (cpu->ds, off);
+    
+    uint8_t maxlen = mem_read8 (cpu->mem, cpu->mem_size, phys + 0);
+    uint8_t count = 0;
+    
+    int c;
+    
+    while (1) {
+    
+        c = host_read_key (cpu);
+        
+        if (c == EOF) {
+            break;
+        }
+        
+        /* Ctrl+C: DOS calls INT 23h, it is not returned as a line */
+        if (c == 0x03) {
+        
+            uint32_t vec = 0x23 * 4;
+            uint16_t new_ip = mem_read16 (cpu->mem, cpu->mem_size, vec + 0);
+            uint16_t new_cs = mem_read16 (cpu->mem, cpu->mem_size, vec + 2);
+            
+            cpu->regs[REG_SP] -= 2;
+            mem_write16 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, cpu->regs[REG_SP]), cpu->flags);
+            
+            cpu->regs[REG_SP] -= 2;
+            mem_write16 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, cpu->regs[REG_SP]), cpu->cs);
+            
+            cpu->regs[REG_SP] -= 2;
+            mem_write16 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, cpu->regs[REG_SP]), cpu->ip);
+            
+            cpu->cs = new_cs;
+            cpu->ip = new_ip;
+            
+            return;
+        
+        }
+        
+        /* Enter finishes input */
+        if (c == '\r' || c == '\n') {
+        
+            putchar ('\r');
+            putchar ('\n');
+            
+            break;
+        
+        }
+        
+        /* Backspace */
+        if (c == 0x08 || c == 0x7F) {
+        
+            if (count > 0) {
+            
+                count--;
+                
+                /* erase from screen */
+                putchar ('\b');
+                putchar (' ');
+                putchar ('\b');
+            
+            }
+            
+            continue;
+        
+        }
+        
+        /* don't store Ctrl+X, Ctrl+A, etc. as text */
+        if (c < 0x20) {
+            continue;
+        }
+        
+        /* printable character */
+        if (count < maxlen) {
+        
+            mem_write8 (cpu->mem, cpu->mem_size, phys + 2 + count, (uint8_t) c);
+            count++;
+            
+            /* echo */
+            dos_write_console_char (stdout, (uint8_t) c);
+        
+        }
+    
+    }
+    
+    mem_write8 (cpu->mem, cpu->mem_size, phys + 1, count);
+    mem_write8 (cpu->mem, cpu->mem_size, phys + 2 + count, '\r');
+    
+    cpu->flags &= ~FLAG_CF;
+
+}
+
+/* Interrupt Handler Function Prototypes */
+static void int_0 (CPU8086 *cpu);
+static void int_1 (CPU8086 *cpu);
+static void int_2 (CPU8086 *cpu);
+static void int_3 (CPU8086 *cpu);
+static void int_4 (CPU8086 *cpu);
+static void int_5 (CPU8086 *cpu);
+static void int_6 (CPU8086 *cpu);
+static void int_7 (CPU8086 *cpu);
+static void int_10 (CPU8086 *cpu);
+static void int_13 (CPU8086 *cpu);
+static void int_15 (CPU8086 *cpu);
+static void int_16 (CPU8086 *cpu);
+static void int_20 (CPU8086 *cpu);
+static void int_21 (CPU8086 *cpu);
+
+/* Interrupt handler functions */
+static void int_0 (CPU8086 *cpu) {
+
+    printf ("Interrupt 0: Divide by Zero Exception\n");
+    cpu->halted = 1;
+
+}
+
+static void int_1 (CPU8086 *cpu) {
+
+    printf ("Interrupt 1: Single Step Exception\n");
+    cpu->halted = 1;
+
+}
+
+static void int_2 (CPU8086 *cpu) {
+
+    printf ("Interrupt 2: Non-Maskable Interrupt (NMI)\n");
+    cpu->halted = 1;
+
+}
+
+static void int_3 (CPU8086 *cpu) {
+
+    printf ("\n*** BREAKPOINT (INT 3) at %04X:%04X ***\n", cpu->cs, cpu->ip);
+    
+    cpu_dump (cpu);                     /* Show registers to see what triggered it */
+    /* cpu->halted = 1; Uncomment if you want the emulator to stop here */
+
+}
+
+static void int_4 (CPU8086 *cpu) {
+
+    printf ("Interrupt 4: Overflow Exception\n");
+    
+    /* Handle Overflow exception here */
+    (void) cpu;
+
+}
+
+static void int_5 (CPU8086 *cpu) {
+
+    printf ("\n*** BOUND RANGE EXCEEDED (INT 5) at %04X:%04X ***\n", cpu->cs, cpu->ip);
+    
+    /* On the IBM PC, INT 5 was also used for Print Screen. */
+    /* If your program is old, it might just be trying to print the screen. */
+    cpu->halted = 1;
+    (void) cpu;
+
+}
+
+static void int_6 (CPU8086 *cpu) {
+
+    printf ("Interrupt 6: Invalid Opcode Exception\n");
+    
+    /* Handle Invalid Opcode exception */
+    (void) cpu;
+
+}
+
+static void int_7 (CPU8086 *cpu) {
+
+    printf ("Interrupt 7: Device Not Available\n");
+    
+    /* Handle FPU unavailable */
+    (void) cpu;
+
+}
+
+static void int_20 (CPU8086 *cpu) {
+
+    /* Legacy DOS Terminate */
+    cpu->halted = 1;
+
+}
+
+extern uint16_t old_ip;
+
+static void int2f_not_installed (CPU8086 *cpu) {
+
+    /* Most INT 2F install checks use AL=00h and expect AL=00h if absent. */
+    set_reg8 (cpu, 0, 0x00); /* AL */
+    cpu->flags &= ~FLAG_CF;
+
+}
+
+static void int_2F (CPU8086 *cpu) {
+
+    uint16_t ax = cpu->regs[REG_AX];
+    
+    uint8_t ah = (uint8_t) (ax >> 8);
+    uint8_t al = (uint8_t) (ax & 0xFF);
+    
+    switch (ah) {
+    
+        case 0x11:
+            
+            /**
+             * DOS network redirector / SHARE multiplex.
+             * For local-only emulator: not installed / not handled.
+             */
+            cpu->flags |= FLAG_CF;
+            cpu->regs[REG_AX] = 0x0001;
+            
+            return;
+        
+        case 0x12:
+            
+            /**
+             * DOS internal services. Many programs probe this.
+             * Do not claim support unless individual subfunctions are implemented.
+             */
+            cpu->flags |= FLAG_CF;
+            return;
+        
+        case 0x16:
+            
+            /**
+             * Windows enhanced-mode / Windows presence.
+             * AX=1600h install check:
+             *   AL=00h means Windows not running.
+             */
+            if (al == 0x00) {
+            
+                cpu->regs[REG_AX] = 0x0000;
+                cpu->flags &= ~FLAG_CF;
+                
+                return;
+            
+            }
+            
+            /* Other Windows INT 2F services absent. */
+            set_reg8 (cpu, 0, 0x00);
+            
+            cpu->flags &= ~FLAG_CF;
+            return;
+        
+        case 0x43:
+            
+            /**
+             * XMS driver install check.
+             * AX=4300h:
+             *   AL=80h means XMS driver installed.
+             *   AL=00h means not installed.
+             *
+             * Return not installed unless you implement INT 2F AX=4310h
+             * returning an XMS entry point.
+             */
+            if (al == 0x00) {
+            
+                set_reg8 (cpu, 0, 0x00);
+                
+                cpu->flags &= ~FLAG_CF;
+                return;
+            
+            }
+            
+            if (al == 0x10) {
+            
+                /* Get XMS driver address: unsupported */
+                cpu->es = 0x0000;
+                
+                cpu->regs[REG_BX] = 0x0000;
+                cpu->flags |= FLAG_CF;
+                
+                return;
+            
+            }
+            
+            break;
+        
+        case 0x48:
+        
+            /**
+             * DOSKEY/PCED multiplex. AX=4810h reads a buffered line
+             * using the same DS:DX buffer layout as INT 21h AH=0Ah.
+             */
+            if (al == 0x10) {
+            
+                dos_buffered_line_input (cpu);
+                
+                cpu->regs[REG_AX] = 0x0000;
+                cpu->flags &= ~FLAG_CF;
+                
+                return;
+            
+            }
+            
+            if (al == 0x00) {
+            
+                int2f_not_installed (cpu);
+                return;
+            
+            }
+            
+            cpu->flags |= FLAG_CF;
+            return;
+        
+        case 0x4A:
+        
+            /* HMA / DOS=HIGH related checks. Say unavailable. */
+            cpu->flags |= FLAG_CF;
+            return;
+        
+        case 0xB7:
+        
+            /**
+             * APPEND multiplex.
+             * AX=B700h install check:
+             *   AL=00h if not installed.
+             */
+            if (al == 0x00) {
+            
+                int2f_not_installed (cpu);
+                return;
+            
+            }
+            
+            int2f_not_installed (cpu);
+            return;
+        
+        case 0xAD:
+        
+            /**
+             * KEYB / keyboard enhancer style multiplex checks.
+             * Not installed.
+             */
+            if (al == 0x00) {
+            
+                int2f_not_installed (cpu);
+                return;
+            
+            }
+            
+            int2f_not_installed (cpu);
+            return;
+        
+        case 0xAE:
+        
+            /**
+             * NLSFUNC multiplex checks.
+             * Not installed.
+             */
+            if (al == 0x00) {
+            
+                int2f_not_installed (cpu);
+                return;
+            
+            }
+            
+            int2f_not_installed (cpu);
+            return;
+        
+        default:
+        
+            /**
+             * Generic multiplex install check convention:
+             * AX=xx00 often asks "are you installed?"
+             * AL=00h answer means "not installed".
+             */
+            if (al == 0x00) {
+            
+                int2f_not_installed (cpu);
+                return;
+            
+            }
+            
+            printf ("Unhandled INT 2Fh AX=%04X at %04X:%04X\n", ax, cpu->cs, cpu->ip);
+            
+            cpu->flags |= FLAG_CF;
+            return;
+    
+    }
+    
+    printf ("Unhandled INT 2Fh AX=%04X at %04X:%04X\n", ax, cpu->cs, cpu->ip);
+    cpu->flags |= FLAG_CF;
+
+}
+
+static void fcb_make_pattern (CPU8086 *cpu, uint16_t seg, uint16_t off, char *out) {
+
+    uint32_t p = get_phys (seg, off);
+    
+    int i, n = 0;
+    int any = 0;
+    
+    /* Check Name part */
+    for (i = 0; i < 8; i++) {
+    
+        char c = (char) mem_read8 (cpu->mem, cpu->mem_size, p + 1 + i);
+        
+        if (c != ' ') {
+        
+            any = 1;
+            out[n++] = c;
+        
+        }
+    
+    }
+    
+    /* If name was all spaces, it's a wildcard */
+    if (!any) {
+    
+        strcpy (out, "*.*");
+        return;
+    
+    }
+    
+    /* Add Dot and Check Extension part */
+    out[n++] = '.';
+    
+    for (i = 0; i < 3; i++) {
+    
+        char c = (char) mem_read8 (cpu->mem, cpu->mem_size, p + 9 + i);
+        
+        if (c != ' ') {
+            out[n++] = c;
+        }
+    
+    }
+    
+    /* Cleanup trailing dot if no extension */
+    if (out[n-1] == '.') {
+        out[n-1] = '\0';
+    } else {
+        out[n] = '\0';
+    }
+
+}
+
+static void fcb_write_result_to_dta (CPU8086 *cpu, const char *name, uint32_t size, time_t modified, uint8_t attr, uint8_t extended) {
+
+    char base[9], ext[4];
+    
+    uint16_t dos_date, dos_time;
+    uint16_t dta_seg, dta_off;
+    
+    uint32_t dta_base, entry_ptr;
+    int i;
+    
+    dos_get_dta (cpu, &dta_seg, &dta_off);
+    dta_base = get_phys (dta_seg, dta_off);
+    
+    /* Standard FCB result at DTA+0, Extended at DTA+7 */
+    entry_ptr = dta_base + (extended ? 7 : 0);
+    
+    if (extended) {
+    
+        mem_write8 (cpu->mem, cpu->mem_size, dta_base + 0x00, 0xFF);
+        
+        for (i = 1; i <= 5; i++) {
+            mem_write8 (cpu->mem, cpu->mem_size, dta_base + i, 0);
+        }
+        
+        mem_write8 (cpu->mem, cpu->mem_size, dta_base + 0x06, attr);
+    
+    }
+    
+    dos_make_83_display_parts (name, base, ext);
+    
+    {
+    
+        const char *dot;
+        int j;
+        
+        memset (base, ' ', 8);
+        memset (ext, ' ', 3);
+        
+        dot = strchr (name, '.');
+        
+        for (j = 0; name[j] && name + j != dot && j < 8; j++) {
+            base[j] = (char) toupper ((int) name[j]);
+        }
+        
+        if (dot) {
+        
+            dot++;
+            
+            for (j = 0; dot[j] && j < 3; j++) {
+                ext[j] = (char) toupper ((int) dot[j]);
+            }
+        
+        }
+    
+    }
+    
+    mem_write8 (cpu->mem, cpu->mem_size, entry_ptr + 0x00, 3);             /* Drive C: */
+    
+    for (i = 0; i < 8; i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, entry_ptr + 0x01 + i, (uint8_t) base[i]);
+    }
+    
+    for (i = 0; i < 3; i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, entry_ptr + 0x09 + i, (uint8_t) ext[i]);
+    }
+    
+    mem_write8 (cpu->mem, cpu->mem_size, entry_ptr + 0x0C, attr);
+    
+    /* Reserved area (0x0D to 0x16) */
+    for (i = 0x0D; i <= 0x16; i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, entry_ptr + i, 0);
+    }
+    
+    /* Time (0x17) and Date (0x19) */
+    if (attr & 0x08) {
+    
+        dos_time = 0;
+        dos_date = 0;
+    
+    } else {
+        dos_get_file_datetime (modified, &dos_date, &dos_time);
+    }
+    
+    mem_write16 (cpu->mem, cpu->mem_size, entry_ptr + 0x17, dos_time);
+    mem_write16 (cpu->mem, cpu->mem_size, entry_ptr + 0x19, dos_date);
+    
+    /* CRITICAL: Starting Cluster (0x1B). DOS skips files with cluster 0. */
+    mem_write16 (cpu->mem, cpu->mem_size, entry_ptr + 0x1B, 0x0002);
+    
+    /* File Size (0x1D) */
+    if (attr & 0x10) {
+        size = 0;
+    }
+    
+    mem_write32 (cpu->mem, cpu->mem_size, entry_ptr + 0x1D, size);
+
+}
+
+#define     DOS_FCB_SEARCH_MAX          128
+
+typedef struct {
+    
+    DIR *dir;
+    
+    char dirpath[260];
+    char pattern[32];
+    
+    uint8_t attr;
+    uint8_t extended;
+    
+    uint16_t dta_seg;
+    uint16_t dta_off;
+    
+    char last_name[260];
+    
+    uint32_t last_size;
+    uint8_t last_attr;
+    
+    int have_last;
+    int active;
+    
+    time_t last_mtime;
+
+} DOS_FCB_SEARCH;
+
+static DOS_FCB_SEARCH dos_fcb_search[DOS_FCB_SEARCH_MAX];
+static int fcb_forced_slot = -1;
+
+static int fcb_find_slot_lifo (uint16_t seg, uint16_t off, const char *dirpath) {
+
+    int i;
+    
+    for (i = DOS_FCB_SEARCH_MAX - 1; i >= 0; i--) {
+    
+        if (dos_fcb_search[i].active && dos_fcb_search[i].dta_seg == seg && dos_fcb_search[i].dta_off == off && strcmp (dos_fcb_search[i].dirpath, dirpath) == 0) {
+            return i;
+        }
+    
+    }
+    
+    return -1;
+
+}
+
+
+static int fcb_alloc_free_slot (void) {
+
+    int i;
+    
+    for (i = 0; i < DOS_FCB_SEARCH_MAX; i++) {
+    
+        if (!dos_fcb_search[i].active) {
+        
+            memset (&dos_fcb_search[i], 0, sizeof (dos_fcb_search[i]));
+            return i;
+        
+        }
+    
+    }
+    
+    return -1;
+
+}
+
+static int fcb_alloc_slot (uint16_t seg, uint16_t off) {
+
+    (void)seg;
+    (void)off;
+    
+    return fcb_alloc_free_slot ();
+
+}
+
+static int dos_append_real_component (char *host_path, size_t host_sz, const char *dos_comp) {
+
+    struct dirent *de;
+    DIR *dir;
+    
+    if (!(dir = opendir (host_path))) {
+        return 0;
+    }
+    
+    while ((de = readdir (dir)) != NULL) {
+    
+        char unique[13];
+        
+        if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) {
+            continue;
+        }
+        
+        dos_make_83_display_name_unique (host_path, de->d_name, unique);
+        
+        if (strcasecmp (de->d_name, dos_comp) != 0 && strcasecmp (unique, dos_comp) != 0) {
+            continue;
+        }
+        
+        if (host_path[0] && host_path[strlen (host_path) - 1] != '/') {
+            dos_append_bounded (host_path, host_sz, "/");
+        }
+        
+        dos_append_bounded (host_path, host_sz, de->d_name);
+        
+        closedir (dir);
+        return 1;
+    
+    }
+    
+    closedir (dir);
+    return 0;
+
+}
+
+static void fcb_make_current_host_dir (char *dirpath, size_t dirpath_sz) {
+
+    char tmp[260];
+    char *tok;
+    
+    snprintf (dirpath, dirpath_sz, "%s", dos_drive_root);
+    snprintf (tmp, sizeof (tmp), "%s", dos_current_dir);
+    
+    dos_normalize_path (tmp);
+    tok = strtok (tmp, "/\\");
+    
+    while (tok) {
+    
+        if (!dos_append_real_component (dirpath, dirpath_sz, tok)) {
+        
+            if (dirpath[0] && dirpath[strlen (dirpath) - 1] != '/') {
+                dos_append_bounded (dirpath, dirpath_sz, "/");
+            }
+            
+            dos_append_bounded (dirpath, dirpath_sz, tok);
+        
+        }
+        
+        tok = strtok (NULL, "/\\");
+    
+    }
+
+}
+
+static int dos_component_matches_83 (const char *dos, const char *host_dir, const char *host) {
+
+    char unique[13];
+    
+    if (strcasecmp (host, dos) == 0) {
+        return 1;
+    }
+    
+    dos_make_83_display_name_unique (host_dir, host, unique);
+    return strcasecmp (unique, dos) == 0;
+
+}
+
+static int dos_resolve_existing_host_path (const char *dos_rel, char *host_out, size_t host_out_sz) {
+
+    char tmp[260];
+    char *tok;
+    char cur[520];
+    
+    if (host_out_sz == 0) {
+        return 0;
+    }
+    
+    cur[0] = '\0';
+    dos_append_bounded (cur, sizeof (cur), dos_drive_root);
+    
+    tmp[0] = '\0';
+    dos_append_bounded (tmp, sizeof (tmp), dos_rel);
+    
+    tok = strtok (tmp, "/\\");
+    
+    while (tok) {
+    
+        struct dirent *de;
+        DIR *d;
+        
+        int found = 0;
+        
+        if (!(d = opendir (cur))) {
+            return 0;
+        }
+        
+        while ((de = readdir (d)) != NULL) {
+        
+            char next[520];
+            struct stat st;
+            
+            if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) {
+                continue;
+            }
+            
+            if (strcasecmp (de->d_name, tok) != 0 && !dos_component_matches_83 (tok, cur, de->d_name)) {
+                continue;
+            }
+            
+            next[0] = '\0';
+            
+            dos_append_bounded (next, sizeof (next), cur);
+            dos_append_bounded (next, sizeof (next), "/");
+            dos_append_bounded (next, sizeof (next), de->d_name);
+            
+            if (host_lstat (next, &st) == 0 && S_ISDIR (st.st_mode)) {
+            
+                cur[0] = '\0';
+                dos_append_bounded (cur, sizeof (cur), next);
+                
+                found = 1;
+                break;
+            
+            }
+        
+        }
+        
+        closedir (d);
+        
+        if (!found) {
+            return 0;
+        }
+        
+        tok = strtok (NULL, "/\\");
+    
+    }
+    
+    host_out[0] = '\0';
+    dos_append_bounded (host_out, host_out_sz, cur);
+    
+    return 1;
+
+}
+
+#define     MAX_HANDLE_SEARCHES         64
+
+typedef struct {
+    
+    DIR *dir;
+    
+    char dirpath[260];
+    char pattern[64];
+    
+    uint8_t attr;
+    int active;
+
+} HANDLE_SEARCH;
+
+static HANDLE_SEARCH handle_searches[MAX_HANDLE_SEARCHES];
+
+static int alloc_handle_search_slot () {
+
+    for (int i = 0; i < MAX_HANDLE_SEARCHES; i++) {
+    
+        if (!handle_searches[i].active) {
+        
+            memset (&handle_searches[i], 0, sizeof (HANDLE_SEARCH));
+            return i;
+        
+        }
+    
+    }
+    
+    return -1;
+
+}
+
+static void dos_install_case_map (CPU8086 *cpu) {
+
+    /* FAR CALL: AL = CP437 character, AL = uppercase character. */
+    static const uint8_t code[] = {
+    
+        0x9C,                   /* pushf */
+        0x3C, 0x80,             /* cmp al, 80h */
+        0x72, 0x0C,             /* jb popf */
+        0x53,                   /* push bx */
+        0x1E,                   /* push ds */
+        0x0E,                   /* push cs */
+        0x1F,                   /* pop ds */
+        0x2C, 0x80,             /* sub al, 80h */
+        0xBB, 0x13, 0x00,       /* mov bx, 0013h (uppercase table) */
+        0xD7,                   /* xlat */
+        0x1F,                   /* pop ds */
+        0x5B,                   /* pop bx */
+        0x9D,                   /* popf */
+        0xCB                    /* retf */
+    
+    };
+    static const uint8_t uppercase[128] = {
+    
+        0x80, 0x9A, 0x90, 0x83, 0x8E, 0x85, 0x8F, 0x80,
+        0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+        0x90, 0x92, 0x92, 0x93, 0x99, 0x95, 0x96, 0x97,
+        0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+        0xA0, 0xA1, 0xA2, 0xA3, 0xA5, 0xA5, 0xA6, 0xA7,
+        0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+        0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
+        0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+        0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
+        0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+        0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
+        0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+        0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE4, 0xE6, 0xE7,
+        0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xE8, 0xEE, 0xEF,
+        0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
+        0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+    
+    };
+    
+    uint32_t phys = (uint32_t) DOS_CASE_MAP_SEG << 4;
+    size_t i;
+    
+    for (i = 0; i < sizeof (code); i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, phys + i, code[i]);
+    }
+    
+    for (i = 0; i < sizeof (uppercase); i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, phys + sizeof (code) + i, uppercase[i]);
+    }
+
+}
+
+static int dos_host_file_in_use (const char *path, const struct stat *identity) {
+
+    int i;
+    
+#if     defined (_WIN32)
+    BY_HANDLE_FILE_INFORMATION target_info, open_info;
+    HANDLE target;
+    
+    (void) identity;
+    
+    target = CreateFileA (path, FILE_READ_ATTRIBUTES,
+                          FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                          NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, NULL);
+    
+    if (target == INVALID_HANDLE_VALUE) {
+        return 0;
+    }
+    
+    if (!GetFileInformationByHandle (target, &target_info)) {
+    
+        CloseHandle (target);
+        return 0;
+    
+    }
+    
+    CloseHandle (target);
+    
+    if (target_info.nFileIndexHigh == 0 && target_info.nFileIndexLow == 0) {
+        return 0;
+    }
+    
+    for (i = 0; i < MAX_DOS_HANDLES; i++) {
+    
+        intptr_t handle;
+        
+        if (!file_handles[i]) {
+            continue;
+        }
+        
+        handle = _get_osfhandle (_fileno (file_handles[i]));
+        
+        if (handle == (intptr_t) -1 || handle == (intptr_t) -2) {
+            continue;
+        }
+        
+        if (!GetFileInformationByHandle ((HANDLE) handle, &open_info)) {
+            continue;
+        }
+        
+        if (target_info.dwVolumeSerialNumber == open_info.dwVolumeSerialNumber &&
+            target_info.nFileIndexHigh == open_info.nFileIndexHigh &&
+            target_info.nFileIndexLow == open_info.nFileIndexLow) {
+            return 1;
+        }
+    
+    }
+#else
+    (void) path;
+    
+    for (i = 0; i < MAX_DOS_HANDLES; i++) {
+    
+        struct stat open_st;
+        
+        if (!file_handles[i]) {
+            continue;
+        }
+        
+        if (fstat (fileno (file_handles[i]), &open_st) != 0) {
+            continue;
+        }
+        
+        if (open_st.st_dev == identity->st_dev && open_st.st_ino == identity->st_ino) {
+            return 1;
+        }
+    
+    }
+#endif
+    
+    return 0;
+
+}
+
+static void int_21 (CPU8086 *cpu) {
+
+    uint8_t ah = (cpu->regs[REG_AX] >> 8) & 0xFF;
+    
+    switch (ah) {
+    
+        case 0x01: {                    /* Character Input with Echo */
+        
+            int c = host_read_key (cpu);
+            
+            if (c == EOF) {
+                c = '\r';
+            }
+            
+            if (c == '\n') {
+                c = '\r';
+            }
+            
+            if (c == 0x03) {            /* Ctrl+C: invoke INT 23h */
+            
+                uint32_t vec = 0x23 * 4;
+                uint16_t new_ip = mem_read16 (cpu->mem, cpu->mem_size, vec + 0);
+                uint16_t new_cs = mem_read16 (cpu->mem, cpu->mem_size, vec + 2);
+                
+                cpu->regs[REG_SP] -= 2;
+                mem_write16 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, cpu->regs[REG_SP]), cpu->flags);
+                
+                cpu->regs[REG_SP] -= 2;
+                mem_write16 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, cpu->regs[REG_SP]), cpu->cs);
+                
+                cpu->regs[REG_SP] -= 2;
+                mem_write16 (cpu->mem, cpu->mem_size, get_phys (cpu->ss, cpu->regs[REG_SP]), cpu->ip);
+                
+                cpu->cs = new_cs;
+                cpu->ip = new_ip;
+                
+                break;
+            
+            }
+            
+            set_reg8 (cpu, REG_AX, (uint8_t) c);
+            
+            if (c == '\r') {
+            
+                putchar ('\r');
+                putchar ('\n');
+            
+            } else {
+                dos_write_console_char (stdout, (uint8_t) c);
+            }
+            
+            fflush (stdout);
+            
+            cpu->flags &= ~FLAG_CF;
+            break;
+        
+        }
+        
+        case 0x02: {                    /* Character Output */
+        
+            uint8_t dl = get_reg8 (cpu, 2);
+            
+            FILE *out = dos_get_handle_file (cpu, 1);
+            dos_write_standard_output (cpu, dl);
+            
+            if (out != NULL) {
+                fflush (out);
+            }
+            
+            break;
+        
+        }
+        
+        case 0x06: {                    /* Direct Console Input/Output */
+        
+            uint8_t dl = get_reg8 (cpu, REG_DX);
+            
+            if (dl == 0xFF) {
+            
+                int ch = host_poll_key_from (file_handles[0] ? file_handles[0] : stdin);
+                
+                if (ch == EOF) {
+                
+                    set_reg8 (cpu, REG_AX, 0);
+                    cpu->flags |= FLAG_ZF;
+                
+                } else {
+                
+                    set_reg8 (cpu, REG_AX, (uint8_t) ch);
+                    cpu->flags &= (uint16_t) ~FLAG_ZF;
+                
+                }
+            
+            } else {
+            
+                dos_write_console_char (stdout, dl);
+                
+                fflush (stdout);
+                set_reg8 (cpu, REG_AX, dl);
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x09: {
+        
+            FILE *out = dos_get_handle_file (cpu, 1);
+            uint16_t off = cpu->regs[REG_DX];
+            
+            while (1) {
+            
+                uint8_t ch = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, off));
+                
+                if (ch == '$') {
+                    break;
+                }
+                
+                if (ch == '\n' && (out == stdout || out == stderr)) {
+                    dos_write_standard_output (cpu, '\r');
+                }
+                
+                dos_write_standard_output (cpu, ch);
+                off = (uint16_t) (off + 1);
+            
+            }
+            
+            if (out != NULL) {
+                fflush (out);
+            }
+            
+            cpu->flags &= ~FLAG_CF;
+            break;
+        
+        }
+        
+        case 0x0A: {                    /* Buffered keyboard input */
+        
+            dos_buffered_line_input (cpu);
+            break;
+            
+        }
+        
+        case 0x0C: {                    /* Flush keyboard buffer and invoke input function AL */
+        
+            uint8_t subfn = get_reg8 (cpu, REG_AX); /* AL */
+            
+            switch (subfn) {
+                
+                case 0x01: {            /* Read char with echo */
+                
+                    int c = host_read_key (cpu);
+                    
+                    if (c == EOF) {
+                        c = '\r';
+                    }
+                    
+                    /* swallow trailing LF after Enter if present */
+                    if (c == '\n') {
+                        c = '\r';
+                    }
+                    
+                    set_reg8 (cpu, REG_AX, (uint8_t) c);    /* AL */
+                    
+                    if (c != '\r') {
+                    
+                        dos_write_console_char (stdout, (uint8_t) c);
+                        fflush (stdout);
+                    
+                    }
+                    
+                    cpu->flags &= ~FLAG_CF;
+                    break;
+                
+                }
+                
+                case 0x07:              /* direct char input, no echo */
+                case 0x08: {            /* char input, no echo */
+                
+                    int c = host_read_key (cpu);
+                    
+                    if (c == EOF) {
+                        c = '\r';
+                    }
+                    
+                    if (c == '\n') {
+                        c = '\r';
+                    }
+                    
+                    set_reg8 (cpu, REG_AX, (uint8_t) c); /* AL */
+                    cpu->flags &= ~FLAG_CF;
+                    
+                    break;
+                
+                }
+                
+                case 0x0A: {            /* buffered line input */
+                
+                    dos_buffered_line_input (cpu);
+                    break;
+                
+                }
+                
+                default:
+                
+                    dos_set_error (cpu, 1);
+                    break;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x0D: {                    /* Disk Reset */
+        
+            /* Flush pending writes without changing registers or flags. */
+            fflush (NULL);
+            break;
+        
+        }
+        
+        case 0x0E: {                    /* Select Default Drive */
+        
+            uint8_t drive = cpu->regs[REG_DX] & 0x00FF; /* DL */
+            
+            /* simple stub: accept any requested drive */
+            (void) drive;
+            
+            /* return highest possible drive count in AL; pretend A:, B:, C: exist */
+            cpu->regs[REG_AX] = (cpu->regs[REG_AX] & 0xFF00) | 3;
+            cpu->flags &= ~FLAG_CF;
+            
+            break;
+        
+        }
+        
+        case 0x11: {                    /* FCB Find First */
+        
+            char pattern[32];
+            char dirpath[260];
+            
+            uint8_t attr = 0;
+            uint8_t extended = 0;
+            
+            uint16_t fcb_off = cpu->regs[REG_DX];
+            
+            uint8_t first;
+            int slot;
+            
+            first = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, fcb_off));
+            
+            if (first == 0xFF) {
+            
+                attr = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, fcb_off + 6));
+                
+                fcb_off += 7;
+                extended = 1;
+            
+            } else {
+            
+                attr = 0;
+                extended = 0;
+            
+            }
+            
+            /* Volume Label is a one-shot; handle it and return. */
+            if (attr & 0x08) {
+            
+                fcb_write_result_to_dta (cpu, "EMU8086", 0, 0, 0x08, extended);
+                
+                set_reg8 (cpu, 0, 0x00);
+                cpu->flags &= ~FLAG_CF;
+                
+                break;
+            
+            }
+            
+            fcb_make_current_host_dir (dirpath, sizeof (dirpath));
+            slot = fcb_find_slot_lifo (cpu->ds, fcb_off, dirpath);
+            
+            if (slot >= 0) {
+            
+                DOS_FCB_SEARCH *s = &dos_fcb_search[slot];
+                
+                if (s->have_last) {
+                    fcb_write_result_to_dta (cpu, s->last_name, s->last_size, s->last_mtime, s->last_attr, s->extended);
+                }
+                
+                set_reg8 (cpu, 0, 0x00);
+                cpu->flags &= ~FLAG_CF;
+                
+                break;
+            
+            }
+            
+            slot = fcb_alloc_slot (cpu->ds, fcb_off);
+            
+            if (slot < 0) {
+            
+                set_reg8 (cpu, 0, 0xFF);
+                dos_last_error = 0x0004;
+                
+                cpu->flags &= ~FLAG_CF;
+                break;
+            
+            }
+            
+            memset (&dos_fcb_search[slot], 0, sizeof (dos_fcb_search[slot]));
+            
+            fcb_make_pattern (cpu, cpu->ds, fcb_off, pattern);
+            
+            if (pattern[0] == '\0' || strcmp (pattern, ".") == 0) {
+                strcpy (pattern, "*.*");
+            }
+            
+            dos_fcb_search[slot].dir = opendir (dirpath);
+            
+            if (!dos_fcb_search[slot].dir) {
+            
+                set_reg8 (cpu, 0, 0xFF);
+                dos_last_error = 0x0003;
+                
+                cpu->flags &= ~FLAG_CF;
+                break;
+            
+            }
+            
+            /* 2. Set up the slot metadata */
+            snprintf (dos_fcb_search[slot].dirpath, sizeof (dos_fcb_search[slot].dirpath), "%s", dirpath);
+            snprintf (dos_fcb_search[slot].pattern, sizeof (dos_fcb_search[slot].pattern), "%s", pattern);
+            
+            dos_fcb_search[slot].attr = attr;
+            dos_fcb_search[slot].extended = extended;
+            dos_fcb_search[slot].dta_seg = cpu->ds;
+            dos_fcb_search[slot].dta_off = fcb_off;
+            dos_fcb_search[slot].active = 1;
+            
+            /* 3. NOW check for Volume Label. If requested, return it as the first result. */
+            /* This leaves the directory open for the next AH=12h call. */
+            if (attr & 0x08) {
+            
+                fcb_write_result_to_dta (cpu, "EMU8086", 0, 0, 0x08, extended);
+                
+                set_reg8 (cpu, 0, 0x00);
+                cpu->flags &= ~FLAG_CF;
+                
+                break;
+            
+            }
+            
+            goto fcb_find_next_common;
+        
+        }
+        
+        case 0x12: {                    /* FCB Find Next */
+        
+            fcb_find_next_common:
+            {
+            
+                DOS_FCB_SEARCH *s;
+                
+                struct dirent *de;
+                int found = 0, slot;
+                
+                uint16_t fcb_off = cpu->regs[REG_DX];
+                uint8_t first = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, fcb_off));
+                
+                if (first == 0xFF) {
+                    fcb_off += 7;
+                }
+                
+                if (fcb_forced_slot >= 0) {
+                
+                    slot = fcb_forced_slot;
+                    fcb_forced_slot = -1;
+                
+                } else {
+                
+                    char dirpath[260];
+                    
+                    fcb_make_current_host_dir (dirpath, sizeof (dirpath));
+                    slot = fcb_find_slot_lifo (cpu->ds, fcb_off, dirpath);
+                
+                }
+                
+                if (slot < 0 || slot >= DOS_FCB_SEARCH_MAX) {
+                
+                    set_reg8 (cpu, 0, 0xFF);
+                    dos_last_error = 0x0012;
+                    
+                    cpu->flags &= ~FLAG_CF;
+                    break;
+                
+                }
+                
+                s = &dos_fcb_search[slot];
+                
+                if (!s->active || !s->dir) {
+                
+                    set_reg8 (cpu, 0, 0xFF);
+                    dos_last_error = 0x0012;
+                    
+                    cpu->flags &= ~FLAG_CF;
+                    break;
+                
+                }
+                
+                while ((de = readdir (s->dir)) != NULL) {
+                
+                    struct stat st;
+                    
+                    char full[520];
+                    uint8_t file_attr;
+                    
+                    if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) {
+                        continue;
+                    }
+                    
+                    if (de->d_name[0] == '.') {
+                        continue;
+                    }
+                    
+                    {
+                    
+                        char dos_name[13];
+                        dos_make_83_display_name_unique (s->dirpath, de->d_name, dos_name);
+                        
+                        if (!dos_wildcard_match (s->pattern, dos_name)) {
+                            continue;
+                        }
+                    
+                    }
+                    
+                    snprintf (full, sizeof (full), "%s/%s", s->dirpath, de->d_name);
+                    
+                    if (host_lstat (full, &st) != 0) {
+                        continue;
+                    }
+                    
+                    if (S_ISDIR (st.st_mode)) {
+                    
+                        /* If it's a directory, return it ONLY if 0x10 is in the search mask */
+                        if (!(s->attr & 0x10)) {
+                            continue;
+                        }
+                        
+                        file_attr = 0x10;                   /* Directory */
+                    
+                    } else {
+                    
+                        /* If it's a file, skip it if the search is ONLY for Volume Labels (0x08) */
+                        if (s->attr == 0x08) {
+                            continue;
+                        }
+                        
+                        file_attr = 0x20;                   /* Archive (Normal File) */
+                    
+                    }
+                    
+                    s->last_size = (uint32_t) st.st_size;
+                    
+                    {
+                    
+                        char dos_name[13];
+                        
+                        dos_make_83_display_name_unique (s->dirpath, de->d_name, dos_name);
+                        snprintf (s->last_name, sizeof (s->last_name), "%s", dos_name);
+                        
+                        s->last_size = (uint32_t) st.st_size;
+                        s->last_mtime = st.st_mtime;
+                        
+                        s->last_attr = file_attr;
+                        s->have_last = 1;
+                        
+                        fcb_write_result_to_dta (cpu, dos_name, (uint32_t) st.st_size, st.st_mtime, file_attr, s->extended);
+                    
+                    }
+                    
+                    found = 1;
+                    break;
+                
+                }
+                
+                if (!found) {
+                
+                    dos_last_error = 0x0012;
+                    
+                    if (s->dir) {
+                        closedir (s->dir);
+                    }
+                    
+                    memset (s, 0, sizeof (*s));
+                
+                }
+                
+                set_reg8 (cpu, 0, found ? 0x00 : 0xFF);
+                cpu->flags &= ~FLAG_CF;
+                
+                break;
+            
+            }
+        
+        }
+        
+        case 0x13: {                    /* FCB Delete File */
+        
+            uint16_t fcb_off = cpu->regs[REG_DX];
+            
+            uint8_t drive;
+            uint8_t attr = 0;
+            uint8_t first;
+            
+            char pattern[32];
+            char dirpath[260];
+            
+            struct dirent *de;
+            DIR *dir;
+            
+            int deleted = 0;
+            int i;
+            int has_name = 0;
+            
+            first = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, fcb_off));
+            
+            if (first == 0xFF) {
+            
+                attr = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, (uint16_t) (fcb_off + 6)));
+                fcb_off += 7;
+            
+            }
+            
+            drive = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, fcb_off));
+            
+            for (i = 1; i <= 8; i++) {
+            
+                uint8_t ch = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, (uint16_t) (fcb_off + i)));
+                
+                if (ch != ' ' && ch != 0) {
+                    has_name = 1;
+                }
+            
+            }
+            
+            /* Only C: is currently backed by the host filesystem. */
+            if ((drive != 0 && drive != 3) || !has_name) {
+            
+                set_reg8 (cpu, REG_AX, 0xFF);
+                break;
+            
+            }
+            
+            fcb_make_pattern (cpu, cpu->ds, fcb_off, pattern);
+            fcb_make_current_host_dir (dirpath, sizeof (dirpath));
+            
+            dir = opendir (dirpath);
+            
+            if (!dir) {
+            
+                set_reg8 (cpu, REG_AX, 0xFF);
+                break;
+            
+            }
+            
+            while ((de = readdir (dir)) != NULL) {
+            
+                char dos_name[13];
+                char full[520];
+                
+                struct stat st;
+                
+                if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) {
+                    continue;
+                }
+                
+                if (de->d_name[0] == '.' && !(attr & 0x02)) {
+                    continue;
+                }
+                
+                dos_make_83_display_name_unique (dirpath, de->d_name, dos_name);
+                
+                if (!dos_wildcard_match (pattern, dos_name)) {
+                    continue;
+                }
+               
+                snprintf (full, sizeof (full), "%s/%s", dirpath, de->d_name);
+                
+                if (host_lstat (full, &st) != 0 || !S_ISREG (st.st_mode)) {
+                    continue;
+                }
+                
+                if (!(st.st_mode & S_IWUSR) && !(attr & 0x01)) {
+                    continue;
+                }
+                
+                if (dos_host_file_in_use (full, &st)) {
+                    continue;
+                }
+                
+                if (remove (full) == 0) {
+                    deleted = 1;
+                }
+            
+            }
+            
+            closedir (dir);
+            
+            if (deleted) {
+                dos_83_cache_invalidate ();
+            }
+            
+            set_reg8 (cpu, REG_AX, deleted ? 0x00 : 0xFF);
+            break;
+        
+        }
+        
+        case 0x16: {                    /* FCB Create File */
+            
+            uint16_t fcb_off = cpu->regs[REG_DX];
+            
+            char name[64];
+            char path[260];
+            
+            uint8_t first;
+            FILE *fp;
+            
+            first = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, fcb_off));
+            
+            if (first == 0xFF) {
+                fcb_off += 7;
+            }
+            
+            fcb_make_pattern (cpu, cpu->ds, fcb_off, name);
+            
+            if (name[0] == '\0' || strcmp (name, "*.*") == 0) {
+            
+                set_reg8 (cpu, REG_AX, 0xFF);
+                
+                dos_last_error = 0x0002;
+                cpu->flags &= ~FLAG_CF;
+                
+                break;
+            
+            }
+            
+            dos_resolve_path (name, path, sizeof (path));
+            fp = fopen (path, "wb+");
+            
+            if (!fp) {
+            
+                set_reg8 (cpu, REG_AX, 0xFF);   /* no room / create failed */
+                
+                dos_last_error = 0x0005;
+                cpu->flags &= ~FLAG_CF;
+                
+                break;
+            
+            }
+            
+            fclose (fp);
+            
+            dos_83_cache_invalidate ();
+            set_reg8 (cpu, REG_AX, 0x00);
+            
+            dos_last_error = 0;
+            
+            cpu->flags &= ~FLAG_CF;
+            break;
+        
+        }
+        
+        case 0x19: {                    /* Get Current Default Drive */
+        
+            cpu->regs[REG_AX] = (cpu->regs[REG_AX] & 0xFF00) | 2;               /* AL = 2 => C: */
+            cpu->flags &= ~FLAG_CF;
+            
+            break;
+        
+        }
+        
+        case 0x1A: {
+        
+            dos_dta_seg = cpu->ds;
+            dos_dta_off = cpu->regs[REG_DX];
+            
+            cpu->flags &= ~FLAG_CF;
+            break;
+        
+        }
+        
+        case 0x25: {                    /* Set Interrupt Vector */
+        
+            uint8_t int_num = cpu->regs[REG_AX] & 0x00FF;   /* AL */
+            uint16_t new_off = cpu->regs[REG_DX];           /* DX */
+            uint16_t new_seg = cpu->ds;                     /* DS */
+            
+            uint32_t ivt_addr = (uint32_t) int_num * 4;
+            
+            mem_write16 (cpu->mem, cpu->mem_size, ivt_addr + 0, new_off);
+            mem_write16 (cpu->mem, cpu->mem_size, ivt_addr + 2, new_seg);
+            
+            cpu->flags &= ~FLAG_CF;
+            break;
+        
+        }
+        
+        case 0x29: {                    /* Parse filename for FCB */
+        
+            uint16_t src_off = cpu->regs[REG_SI];   /* DS:SI -> source string */
+            uint16_t fcb_off = cpu->regs[REG_DI];   /* ES:DI -> destination FCB */
+            
+            uint8_t options = cpu->regs[REG_AX] & 0xFF;
+            uint8_t drive = 0;
+            uint8_t result = 0;
+            uint8_t ch;
+            uint8_t next;
+            uint8_t name[8];
+            uint8_t ext[3];
+            
+            int i;
+            int has_name = 0;
+            int has_ext = 0;
+            int starred;
+            
+            memset (name, ' ', sizeof (name));
+            memset (ext, ' ', sizeof (ext));
+            
+            /* Option bit 0 skips leading filename separators. */
+            if (options & 0x01) {
+            
+                for (;;) {
+                
+                    ch = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, src_off));
+                    
+                    if (ch < 0x20 || (ch != ' ' && strchr (":;.,=+/\"[]\\<>|", ch) == NULL)) {
+                        break;
+                    }
+                    
+                    src_off++;
+                
+                }
+            
+            }
+            
+            ch = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, src_off));
+            next = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, (uint16_t) (src_off + 1)));
+            
+            if (next == ':') {
+            
+                ch = (uint8_t) toupper ((unsigned char) ch);
+                
+                if (ch < 'A' || ch > 'Z') {
+                
+                    set_reg8 (cpu, REG_AX, 0xFF);
+                    break;
+                
+                }
+                
+                drive = (uint8_t) (ch - 'A' + 1);
+                src_off += 2;
+            
+            } else if (options & 0x02) {
+                drive = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, fcb_off));
+            }
+            
+            for (i = 0, starred = 0; ; ) {
+            
+                ch = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, src_off));
+                
+                if (ch == '.' || ch <= 0x20 || ch == 0x7F || strchr (";,:=+/\"[]\\<>|", ch) != NULL) {
+                    break;
+                }
+                
+                has_name = 1;
+                
+                if (ch == '*') {
+                
+                    starred = 1;
+                    result = 1;
+                    
+                    while (i < 8) {
+                        name[i++] = '?';
+                    }
+                
+                } else if (!starred && i < 8) {
+                
+                    name[i++] = (uint8_t) toupper ((unsigned char) ch);
+                    
+                    if (ch == '?') {
+                        result = 1;
+                    }
+                
+                }
+            
+                src_off++;
+            
+            }
+            
+            if (ch == '.') {
+            
+                src_off++;
+                
+                for (i = 0, starred = 0; ; ) {
+                
+                    ch = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, src_off));
+                    
+                    if (ch <= 0x20 || ch == 0x7F || strchr (";,:.=+/\"[]\\<>|", ch) != NULL) {
+                        break;
+                    }
+                    
+                    has_ext = 1;
+                    
+                    if (ch == '*') {
+                    
+                        starred = 1;
+                        result = 1;
+                        
+                        while (i < 3) {
+                            ext[i++] = '?';
+                        }
+                    
+                    } else if (!starred && i < 3) {
+                    
+                        ext[i++] = (uint8_t) toupper ((unsigned char) ch);
+                        
+                        if (ch == '?') {
+                            result = 1;
+                        }
+                    
+                    }
+                    
+                    src_off++;
+                
+                }
+            
+            }
+            
+            mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, fcb_off), drive);
+            
+            if (has_name || !(options & 0x04)) {
+            
+                for (i = 0; i < 8; i++) {
+                    mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, (uint16_t) (fcb_off + i + 1)), name[i]);
+                }
+            
+            }
+            
+            if (has_ext || !(options & 0x08)) {
+            
+                for (i = 0; i < 3; i++) {
+                    mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->es, (uint16_t) (fcb_off + i + 9)), ext[i]);
+                }
+            
+            }
+            
+            cpu->regs[REG_SI] = src_off;
+            set_reg8 (cpu, REG_AX, result);
+            
+            break;
+        
+        }
+        
+        case 0x2F: {                    /* Get DTA address */
+        
+            uint16_t dta_seg;
+            uint16_t dta_off;
+            
+            dos_get_dta (cpu, &dta_seg, &dta_off);
+            
+            cpu->es = dta_seg;
+            cpu->regs[REG_BX] = dta_off;
+            
+            cpu->flags &= ~FLAG_CF;
+            break;
+        
+        }
+        
+        case 0x30: {                    /* Get DOS version */
+        
+            uint16_t ver = 0x0005;
+            const char *env_p;
+            
+            if ((env_p = getenv ("DOS_VER"))) {
+            
+                if (!strchr (env_p, '.') && env_p[0] == '0') {
+                
+                    if (tolower ((int) env_p[1]) == 'x') {
+                        ver = (uint16_t) strtoul (env_p, 0, 16);
+                    } else if (env_p[1] >= '0' && env_p[1] <= '7') {
+                        ver = (uint16_t) strtoul (env_p, 0, 8);
+                    } else {
+                        ver = (uint16_t) strtoul (env_p, 0, 10);
+                    }
+                
+                } else {
+                    
+                    unsigned long major, minor;
+                    char *end_p;
+                    
+                    major = strtoul (env_p, &end_p, 10);
+                    minor = 0;
+                    
+                    if (*end_p == '.') {
+                        minor = strtoul (end_p + 1, 0, 10);
+                    }
+                    
+                    ver = (uint16_t) ((minor << 8) | major);
+                
+                }
+            
+            }
+            
+            cpu->regs[REG_AX] = ver;    /* AH=0, AL=5 */
+            
+            /* Optional (some programs check these) */
+            cpu->regs[REG_BX] = 0x0000;
+            cpu->regs[REG_CX] = 0x0000;
+            
+            cpu->flags &= ~FLAG_CF;     /* success */
+            break;
+        
+        }
+        
+        case 0x31: {                    /* Terminate and Stay Resident */
+        
+            cpu->halted = 1;
+            break;
+        
+        }
+        
+        case 0x33: {                    /* Ctrl-Break check */
+        
+            uint8_t al = (uint8_t) (cpu->regs[REG_AX] & 0xFF);
+            
+            switch (al) {
+            
+                case 0x00:              /* get state */
+                
+                    set_reg8 (cpu, 1, 0x00);                /* DL = 0 off, 1 on -- adjust if your set_reg8 indexes differ */
+                    
+                    cpu->flags &= ~FLAG_CF;
+                    break;
+                
+                case 0x01:              /* set state */
+                
+                    /* DL contains new state; store it if you have a DOS state struct */
+                    cpu->flags &= ~FLAG_CF;
+                    break;
+                
+                case 0x02:              /* get/set extended break checking - often DOS 3.3+ */
+                
+                    set_reg8 (cpu, 1, 0x00);
+                    
+                    cpu->flags &= ~FLAG_CF;
+                    break;
+                
+                default:
+                    
+                    cpu->flags |= FLAG_CF;
+                    cpu->regs[REG_AX] = 0x0001; /* invalid function */
+                    
+                    break;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x35: {                    /* Get Interrupt Vector */
+        
+            uint8_t int_num = cpu->regs[REG_AX] & 0x00FF;   /* AL */
+            uint32_t ivt_addr = (uint32_t) int_num * 4;
+            
+            cpu->regs[REG_BX] = mem_read16 (cpu->mem, cpu->mem_size, ivt_addr + 0);
+            cpu->es           = mem_read16 (cpu->mem, cpu->mem_size, ivt_addr + 2);
+            cpu->flags &= ~FLAG_CF;
+            
+            break;
+        
+        }
+       
+        case 0x36: {                    /* Get Disk Free Space */
+        
+            uint8_t drive = get_reg8 (cpu, REG_DX);
+            
+            uint64_t total_bytes;
+            uint64_t free_bytes;
+            uint64_t cluster_bytes;
+            uint64_t total_clusters;
+            uint64_t free_clusters;
+            
+            uint16_t sectors_per_cluster = 8;
+            
+            if (drive != 0 && drive != 3) {
+            
+                cpu->regs[REG_AX] = 0xFFFF;
+                break;
+            
+            }
+            
+#if     defined (_WIN32)
+            {
+            
+                ULARGE_INTEGER available, total, unused;
+                
+                if (!GetDiskFreeSpaceExA (dos_drive_root, &available, &total, &unused)) {
+                
+                    cpu->regs[REG_AX] = 0xFFFF;
+                    break;
+                
+                }
+                
+                total_bytes = total.QuadPart;
+                free_bytes = available.QuadPart;
+            
+            }
+#else
+            {
+            
+                struct statvfs st;
+                uint64_t block_size;
+                
+                if (statvfs (dos_drive_root, &st) != 0) {
+                
+                    cpu->regs[REG_AX] = 0xFFFF;
+                    break;
+                
+                }
+                
+                block_size = st.f_frsize ? st.f_frsize : st.f_bsize;
+                
+                total_bytes = (uint64_t) st.f_blocks * block_size;
+                free_bytes = (uint64_t) st.f_bavail * block_size;
+            
+            }
+#endif
+            
+            /* Fit the host volume's free-space report into DOS's 16-bit cluster fields. */
+            cluster_bytes = (uint64_t) sectors_per_cluster * 512;
+            
+            while (sectors_per_cluster < 64 && total_bytes / cluster_bytes > 0xFFFF) {
+            
+                sectors_per_cluster *= 2;
+                cluster_bytes *= 2;
+            
+            }
+            
+            total_clusters = total_bytes / cluster_bytes;
+            free_clusters = free_bytes / cluster_bytes;
+            
+            if (total_clusters > 0xFFFF) {
+                total_clusters = 0xFFFF;
+            }
+            
+            if (free_clusters > total_clusters) {
+                free_clusters = total_clusters;
+            }
+            
+            cpu->regs[REG_AX] = sectors_per_cluster;
+            cpu->regs[REG_BX] = (uint16_t) free_clusters;
+            
+            cpu->regs[REG_CX] = 512;
+            cpu->regs[REG_DX] = (uint16_t) total_clusters;
+            
+            break;
+        
+        }
+        
+        case 0x37: {                    /* Get/Set switch character */
+        
+            uint8_t al = cpu->regs[REG_AX] & 0xFF;
+            
+            switch (al) {
+                
+                case 0x00:              /* get switch char */
+                    
+                    cpu->regs[REG_DX] = (cpu->regs[REG_DX] & 0xFF00) | '/'; /* DL='/' */
+                    
+                    dos_clear_success (cpu);
+                    break;
+                    
+                case 0x01:              /* set switch char */
+                    
+                    /* DL has requested char; ignore/accept for now */
+                    dos_clear_success (cpu);
+                    break;
+                    
+                default:
+                    
+                    dos_set_error (cpu, 0x0001);
+                    break;
+                
+            }
+            
+            break;
+            
+        }
+        
+        case 0x38: {                    /* Get/Set country info */
+        
+            uint8_t al = (uint8_t) (cpu->regs[REG_AX] & 0xFF);
+            
+            if (al == 0x00 || al == 0xFF) {
+            
+                /**
+                 * Get country info:
+                 * 
+                 *     DS:DX points to 34-byte country info buffer.
+                 *     Return BX = country code.
+                 */
+                uint16_t seg = cpu->ds;
+                uint16_t off = cpu->regs[REG_DX];
+                uint32_t p = get_phys (seg, off);
+                
+                /* Default U.S. country information (DOS 2.11+ layout). */
+                mem_write16 (cpu->mem, cpu->mem_size, p + 0x00, 0);        /* date format: USA */
+                
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x02, '$');       /* currency symbol */
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x03, 0);
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x04, 0);
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x05, 0);
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x06, 0);
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x07, ',');       /* thousands separator */
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x08, 0);
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x09, '.');       /* decimal separator */
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x0A, 0);
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x0B, '/');       /* date separator */
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x0C, 0);
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x0D, ':');       /* time separator */
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x0E, 0);
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x0F, 0);        /* currency format */
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x10, 2);        /* currency decimals */
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x11, 0);        /* 12-hour clock */
+                
+                dos_install_case_map (cpu);
+                
+                mem_write16 (cpu->mem, cpu->mem_size, p + 0x12, 0x0000);  /* upper-case routine offset */
+                mem_write16 (cpu->mem, cpu->mem_size, p + 0x14, DOS_CASE_MAP_SEG);
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x16, ',');       /* list separator */
+                mem_write8 (cpu->mem, cpu->mem_size, p + 0x17, 0);
+                
+                for (int i = 0; i < 10; i++) {
+                    mem_write8 (cpu->mem, cpu->mem_size, p + 0x18 + i, 0);
+                }
+                
+                cpu->regs[REG_BX] = 1;                      /* country code 1 */
+                cpu->flags &= ~FLAG_CF;
+            
+            } else {
+            
+                /* Set country code - accept but do nothing */
+                cpu->flags &= ~FLAG_CF;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x39: {                    /* MKDIR / Create directory */
+        
+            char path[260];
+            
+            dos_read_asciiz (cpu, cpu->ds, cpu->regs[REG_DX], path, sizeof (path));
+            dos_normalize_path (path);
+            
+            if (path[0] == '\0') {
+            
+                dos_set_error (cpu, 0x0003);                /* path not found */
+                break;
+            
+            }
+            
+#if     defined (_WIN32)
+            if (_mkdir(path) == 0) {
+#else
+            if (mkdir(path, 0777) == 0) {
+#endif
+            
+                dos_83_cache_invalidate ();
+                cpu->flags &= ~FLAG_CF;
+            
+            } else {
+            
+                switch (errno) {
+                
+                    case EEXIST:
+                    
+                        dos_set_error (cpu, 0x0050); /* file exists */
+                        break;
+                    
+                    case ENOENT:
+                    
+                        dos_set_error (cpu, 0x0003); /* path not found */
+                        break;
+                    
+                    case EACCES:
+                    case EPERM:
+                    
+                        dos_set_error (cpu, 0x0005); /* access denied */
+                        break;
+                    
+                    default:
+                    
+                        dos_set_error (cpu, 0x0003);
+                        break;
+                
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x3A: {                    /* RMDIR / Remove directory */
+        
+            char raw[260];
+            char dos_path[260];
+            char host_path[520];
+            
+            struct stat st;
+            int absolute;
+            
+            dos_read_asciiz (cpu, cpu->ds, cpu->regs[REG_DX], raw, sizeof (raw));
+            
+            if (raw[0] == '\0' || (!dos_long_paths && strlen (raw) > DOS_DIRECTORY_PATH_MAX) || strlen (raw) >= sizeof (raw) - 1) {
+            
+                dos_set_error (cpu, 0x0003);                /* path not found */
+                break;
+            
+            }
+            
+            absolute = raw[0] == '\\' || raw[0] == '/' || (raw[0] && raw[1] == ':');
+            
+            if (!absolute && strlen (dos_current_dir) + strlen (raw) + 2 > sizeof (dos_path)) {
+            
+                dos_set_error (cpu, 0x0003);
+                break;
+            
+            }
+            
+            dos_path[0] = '\0';
+            
+            if (!absolute && dos_current_dir[0] != '\0') {
+            
+                dos_append_bounded (dos_path, sizeof (dos_path), dos_current_dir);
+                dos_append_bounded (dos_path, sizeof (dos_path), "/");
+            
+            }
+            
+            dos_append_bounded (dos_path, sizeof (dos_path), raw);
+            dos_normalize_path (dos_path);
+            
+            if (dos_path[0] == '\0') {
+            
+                dos_set_error (cpu, 0x0005);                /* cannot remove root directory */
+                break;
+            
+            }
+            
+            if (strcasecmp (dos_path, dos_current_dir) == 0) {
+            
+                dos_set_error (cpu, 0x0010);                /* current directory */
+                break;
+            
+            }
+            
+            if (!dos_resolve_existing_host_path (dos_path, host_path, sizeof (host_path))) {
+            
+                dos_resolve_path (raw, host_path, sizeof (host_path));
+                
+                if (stat (host_path, &st) == 0) {
+                    dos_set_error (cpu, 0x0005);            /* not a directory */
+                } else {
+                    dos_set_error (cpu, errno == EACCES || errno == EPERM ? 0x0005 : 0x0003);
+                }
+                
+                break;
+            
+            }
+            
+            if (stat (host_path, &st) != 0) {
+            
+                dos_set_error (cpu, errno == EACCES || errno == EPERM ? 0x0005 : 0x0003);
+                break;
+            
+            }
+            
+            if (!S_ISDIR (st.st_mode)) {
+            
+                dos_set_error (cpu, 0x0005);                /* not a directory */
+                break;
+            
+            }
+            
+#if     defined (_WIN32)
+            if (_rmdir (host_path) == 0) {
+#else
+            if (rmdir (host_path) == 0) {
+#endif
+            
+                dos_83_cache_invalidate ();
+                dos_clear_success (cpu);
+            
+            } else {
+            
+                switch (errno) {
+                
+                    case ENOENT:
+                    case ENOTDIR:
+                    
+                        dos_set_error (cpu, 0x0003);        /* path not found */
+                        break;
+                    
+                    default:
+                    
+                        dos_set_error (cpu, 0x0005);        /* access denied */
+                        break;
+                
+                }
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x3B: {                    /* Change Current Directory */
+        
+            struct stat st;
+            
+            char raw[260];
+            char req[260];
+            char new_dos[260];
+            char host_rel[260];
+            char host_path[520];
+            
+            dos_read_asciiz (cpu, cpu->ds, cpu->regs[REG_DX], raw, sizeof (raw));
+            
+            if ((!dos_long_paths && strlen (raw) > DOS_DIRECTORY_PATH_MAX) || strlen (raw) >= sizeof (raw) - 1) {
+            
+                dos_set_error (cpu, 0x0003);
+                break;
+            
+            }
+            
+            req[0] = '\0';
+            dos_append_bounded (req, sizeof (req), raw);
+            
+            if (strcmp (req, "..") == 0 || strcmp (req, ". .") == 0) {
+            
+                char *slash;
+                
+                dos_normalize_path (dos_current_dir);
+                slash = strrchr (dos_current_dir, '/');
+                
+                if (slash) {
+                    *slash = '\0';
+                } else {
+                    dos_current_dir[0] = '\0';
+                }
+                
+                dos_clear_success (cpu);
+                break;
+            
+            }
+            
+            new_dos[0] = '\0';
+            
+            if (strcmp (raw, "\\") == 0 || strcmp (raw, "/") == 0 || (raw[0] && raw[1] == ':' && (raw[2] == '\0' || strcmp (raw + 2, "\\") == 0 || strcmp (raw + 2, "/") == 0))) {
+                new_dos[0] = '\0';
+            } else {
+            
+                dos_normalize_path (req);
+                
+                if (req[0] == '\0' || strcmp (req, ".") == 0) {
+                    dos_append_bounded (new_dos, sizeof (new_dos), dos_current_dir);
+                } else if (raw[0] == '\\' || raw[0] == '/' || (raw[0] && raw[1] == ':')) {
+                
+                    dos_append_bounded (new_dos, sizeof (new_dos), req);
+                    dos_normalize_path (new_dos);
+                
+                } else {
+                
+                    if (strlen (dos_current_dir) + strlen (req) + (dos_current_dir[0] != '\0') >= sizeof (new_dos)) {
+                    
+                        dos_set_error (cpu, 0x0003);
+                        break;
+                    
+                    }
+                    
+                    if (dos_current_dir[0] != '\0') {
+                    
+                        dos_append_bounded (new_dos, sizeof (new_dos), dos_current_dir);
+                        dos_append_bounded (new_dos, sizeof (new_dos), "/");
+                        dos_append_bounded (new_dos, sizeof (new_dos), req);
+                    
+                    } else {
+                        dos_append_bounded (new_dos, sizeof (new_dos), req);
+                    }
+                    
+                    dos_normalize_path (new_dos);
+                
+                }
+            
+            }
+            
+            if (!dos_long_paths && strlen (new_dos) >= DOS_DIRECTORY_PATH_MAX) {
+            
+                dos_set_error (cpu, 0x0003);
+                break;
+            
+            }
+            
+            host_rel[0] = '\0';
+            dos_append_bounded (host_rel, sizeof (host_rel), new_dos);
+            
+            if (host_rel[0] == '\0') {
+            
+                host_path[0] = '\0';
+                dos_append_bounded (host_path, sizeof (host_path), dos_drive_root);
+            
+            } else {
+            
+                if (!dos_resolve_existing_host_path (host_rel, host_path, sizeof (host_path))) {
+                
+                    dos_set_error (cpu, 0x0003);
+                    break;
+                
+                }
+            
+            }
+            
+            if (stat (host_path, &st) == 0 && S_ISDIR (st.st_mode)) {
+            
+                dos_current_dir[0] = '\0';
+                dos_append_bounded (dos_current_dir, sizeof (dos_current_dir), new_dos);
+                
+                /**
+                 * Do NOT prune FCB searches here.
+                 *
+                 * TREE.COM keeps a parent directory enumeration alive while it CHDIRs
+                 * into a child directory, then CHDIRs back and continues the parent
+                 * search.  If we close the parent slot here, the later FCB FindFirst
+                 * restarts at Videos/Documents forever.
+                 */
+                dos_clear_success (cpu);
+            
+            } else {
+                dos_set_error (cpu, 0x0003);
+            }
+            
+            break;
+        
+        }
+        
+        case 0x3C: {                    /* Create File */
+        
+            char rawname[260];
+            char filename[260];
+            
+            FILE *fp;
+            int h, sft;
+            
+            dos_read_asciiz (cpu, cpu->ds, cpu->regs[REG_DX], rawname, sizeof (rawname));
+            dos_resolve_path (rawname, filename, sizeof (filename));
+            
+            h = get_free_handle (cpu);
+            sft = get_free_sft ();
+            
+            if (h < 0 || sft < 0) {
+            
+                dos_set_error (cpu, 0x04);                  /* too many open files */
+                break;
+            
+            }
+            
+            fp = fopen (filename, "w+b");
+            
+            if (!fp) {
+            
+                dos_set_error (cpu, 0x05);                  /* access denied / create failed */
+                break;
+            
+            }
+            
+            file_handles[sft] = fp;
+            
+            dos_set_jft (cpu, (uint16_t) h, (uint8_t) sft);
+            cpu->regs[REG_AX] = (uint16_t) h;
+            
+            dos_83_cache_invalidate ();
+            dos_clear_success (cpu);
+            
+            break;
+        
+        }
+        
+        case 0x3D: {                    /* Open File */
+        
+            const char *mode = "rb";
+            
+            char rawname[260];
+            char filename[260];
+            
+            FILE *fp;
+            int h, sft;
+            
+            dos_read_asciiz (cpu, cpu->ds, cpu->regs[REG_DX], rawname, sizeof (rawname));
+            dos_resolve_path (rawname, filename, sizeof (filename));;
+            
+            switch (get_reg8 (cpu, 0) & 0x03) {
+                
+                case 0:
+                
+                    mode = "rb";
+                    break;
+                
+                case 1:
+                
+                    mode = "rb+";
+                    break; /* DOS write-only is awkward on stdio; rb+ is okay for now */
+                
+                case 2:
+                
+                    mode = "rb+";
+                    break;
+            
+            }
+            
+            h = get_free_handle (cpu);
+            sft = get_free_sft ();
+            
+            if (h < 0 || sft < 0) {
+            
+                dos_set_error (cpu, 0x04);                  /* too many open files */
+                break;
+            
+            }
+            
+            fp = dos_fopen_case_insensitive (filename, mode);
+            
+            if (!fp) {
+            
+                dos_set_error (cpu, 0x02);                  /* file not found */
+                break;
+            
+            }
+            
+            file_handles[sft] = fp;
+            
+            dos_set_jft (cpu, (uint16_t) h, (uint8_t) sft);
+            cpu->regs[REG_AX] = (uint16_t) h;
+            
+            dos_clear_success (cpu);
+            break;
+        
+        }
+        
+        case 0x3E: {                    /* Close File (AH=3Eh) */
+        
+            uint16_t h = cpu->regs[REG_BX];
+            
+            if (dos_close_handle (cpu, h)) {
+                dos_clear_success (cpu);
+            } else {
+                dos_set_error (cpu, 0x06);                  /* Invalid handle */
+            }
+            
+            break;
+        
+        }
+        
+        case 0x3F: {                    /* Read from File */
+        
+            uint16_t h = cpu->regs[REG_BX];
+            uint16_t count = cpu->regs[REG_CX];
+            uint16_t addr = cpu->regs[REG_DX];
+            
+            FILE *fp = dos_get_handle_file (cpu, h);
+            uint16_t bytes_read = 0;
+            
+            /* Standard and duplicated handles share the same file position. */
+            if (fp) {
+            
+                uint16_t i, off;
+                
+                for (i = 0; i < count; i++) {
+                
+                    int c;
+                    
+                    if (fp == stdout || fp == stderr) {
+                        c = host_read_key_from (fp);
+                    } else {
+                        c = fgetc (fp);
+                    }
+                    
+                    if (c == EOF) {
+                        break;
+                    }
+                    
+                    off = (uint16_t) (addr + i);
+                    
+                    mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, off), (uint8_t) c);
+                    bytes_read++;
+                
+                }
+                
+                cpu->regs[REG_AX] = bytes_read;
+                cpu->flags &= ~FLAG_CF;
+            
+            } else {
+                dos_set_error (cpu, 0x06);                  /* Invalid handle */
+            }
+            
+            break;
+        
+        }
+        
+        case 0x40: {                    /* Write to File or Device */
+        
+            uint16_t handle = cpu->regs[REG_BX];
+            uint16_t count = cpu->regs[REG_CX];
+            uint16_t addr = cpu->regs[REG_DX];
+            
+            FILE *out = dos_get_handle_file (cpu, handle);
+            cpu->flags &= (uint16_t) ~FLAG_CF;
+            
+            if (out == stdout || out == stderr) {
+            
+                uint16_t i;
+                
+                for (i = 0; i < count; i++) {
+                
+                    uint8_t c = mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, (uint16_t) (addr + i)));
+                    dos_write_console_char (out, c);
+                
+                }
+                
+                fflush (out);
+                cpu->regs[REG_AX] = count;
+            
+            } else if (out != NULL) {
+            
+                uint16_t written = 0, i;
+                
+                for (i = 0; i < count; i++) {
+                
+                    uint32_t phys_addr = get_phys (cpu->ds, (uint16_t) (addr + i));
+                    uint8_t byte = mem_read8 (cpu->mem, cpu->mem_size, phys_addr);
+                    
+                    if (fputc (byte, out) != EOF) {
+                        written++;
+                    } else {
+                    
+                        dos_set_error (cpu, 0x001D);
+                        break;
+                    
+                    }
+                
+                }
+                
+                /* Always report the number of bytes written if no error occurred during this call */
+                if (!(cpu->flags & FLAG_CF)) {
+                
+                    cpu->regs[REG_AX] = written;
+                    fflush (out);
+                
+                }
+            
+            } else {
+                dos_set_error (cpu, 0x06);
+            }
+            
+            break;
+        
+        }
+        
+        case 0x41: {
+        
+            char rawname[260];
+            char filename[260];
+            
+            dos_read_asciiz (cpu, cpu->ds, cpu->regs[REG_DX], rawname, sizeof (rawname));
+            dos_resolve_path (rawname, filename, sizeof (filename));
+            
+            if (remove (filename) == 0) {
+            
+                dos_83_cache_invalidate ();
+                dos_clear_success (cpu);
+            
+            } else {
+                dos_set_error (cpu, 0x02);
+            }
+            
+            break;
+        
+        }
+        
+        case 0x42: {                    /* LSEEK */
+        
+            uint16_t h = cpu->regs[REG_BX];
+            
+            uint8_t method = get_reg8 (cpu, REG_AX);   /* AL */
+            int32_t offset = (int32_t) (((uint32_t) cpu->regs[REG_CX] << 16) | (uint32_t) cpu->regs[REG_DX]);
+            
+            FILE *fp = dos_get_handle_file (cpu, h);
+            
+            if (fp) {
+            
+                int whence = (method == 0) ? SEEK_SET : (method == 1) ? SEEK_CUR : SEEK_END;
+                
+                if (fseek (fp, offset, whence) == 0) {
+                
+                    long new_pos = ftell (fp);
+                    
+                    cpu->regs[REG_AX] = (uint16_t) (new_pos & 0xFFFF);
+                    cpu->regs[REG_DX] = (uint16_t) ((unsigned long) new_pos >> 16);
+                    cpu->flags &= ~FLAG_CF;
+                
+                } else {
+                    dos_set_error (cpu, 0x0001);
+                }
+            
+            } else {
+                dos_set_error (cpu, 0x0006);
+            }
+            
+            break;
+        
+        }
+        
+        case 0x44: {                    /* IOCTL */
+        
+            uint16_t handle = cpu->regs[REG_BX];
+            
+            FILE *fp = dos_get_handle_file (cpu, handle);
+            uint8_t al = get_reg8 (cpu, 0);
+            
+            switch (al) {
+                
+                case 0x00:              /* Get device information */
+                
+                    if (fp != NULL) {
+                    
+                        cpu->regs[REG_DX] = isatty (fileno (fp)) ? 0x0080 : 0x0000;
+                        cpu->regs[REG_AX] = cpu->regs[REG_DX];
+                        
+                        dos_clear_success (cpu);
+                    
+                    } else {
+                        dos_set_error (cpu, 0x0006);
+                    }
+                    
+                    break;
+                
+                case 0x01:              /* Set device information */
+                
+                    /* For now, accept and ignore for character devices / std handles. */
+                    if (fp != NULL) {
+                        cpu->flags &= ~FLAG_CF;
+                    } else {
+                        dos_set_error (cpu, 0x0006);
+                    }
+                    
+                    break;
+                
+                default:
+                
+                    dos_set_error (cpu, 0x0001);
+                    break;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x45: {                    /* Duplicate file handle */
+        
+            uint16_t oldh = cpu->regs[REG_BX];
+            int sft = dos_get_sft (cpu, oldh), newh;
+            
+            if (sft < 0) {
+            
+                dos_set_error (cpu, 0x0006);                /* invalid handle */
+                break;
+            
+            }
+            
+            newh = get_free_handle (cpu);
+            
+            if (newh < 0) {
+            
+                dos_set_error (cpu, 0x0004);                /* too many open files */
+                break;
+            
+            }
+            
+            dos_set_jft (cpu, (uint16_t) newh, (uint8_t) sft);
+            
+            cpu->regs[REG_AX] = (uint16_t) newh;
+            dos_clear_success (cpu);
+            
+            break;
+        
+        }
+        
+        case 0x46: {                    /* Force duplicate file handle */
+        
+            uint16_t oldh = cpu->regs[REG_BX];
+            uint16_t newh = cpu->regs[REG_CX];
+            
+            int sft = dos_get_sft (cpu, oldh);
+            
+            if (sft < 0 || newh >= MAX_DOS_HANDLES) {
+            
+                dos_set_error (cpu, 0x0006);
+                break;
+            
+            }
+            
+            if (oldh != newh) {
+            
+                if (dos_get_sft (cpu, newh) >= 0) {
+                    dos_close_handle (cpu, newh);
+                }
+                
+                dos_set_jft (cpu, newh, (uint8_t) sft);
+            
+            }
+            
+            dos_clear_success (cpu);
+            break;
+        
+        }
+        
+        case 0x47: {                    /* Get Current Directory */
+        
+            uint8_t drive = cpu->regs[REG_AX] & 0x00FF;     /* AL, 0 = default */
+            uint16_t off = cpu->regs[REG_SI];
+            
+            const char *cwd = dos_current_dir;
+            size_t i;
+            
+            (void) drive;               /* single-drive stub for now */
+            
+            if (strlen (cwd) >= DOS_DIRECTORY_PATH_MAX) {
+            
+                dos_set_error (cpu, 0x000F);
+                break;
+            
+            }
+            
+            /* DOS returns path without leading backslash */
+            for (i = 0; ; i++) {
+            
+                char ch = cwd[i];
+                
+                if (ch == '/') {
+                    ch = '\\';
+                }
+                
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, (uint16_t) (off + i)), (uint8_t) ch);
+                
+                if (cwd[i] == '\0') {
+                    break;
+                }
+            
+            }
+            
+            cpu->flags &= ~FLAG_CF;
+            break;
+        
+        }
+        
+        case 0x48: {
+        
+            uint16_t requested = cpu->regs[REG_BX];
+            uint16_t current_seg = 0x004F;
+            
+            int found = 0;
+            int invalid_chain = 0;
+            
+            uint16_t max_free = 0;
+            
+            while (1) {
+            
+                uint32_t phys = (uint32_t) current_seg << 4;
+                DOS_MCB *mcb;
+                
+                if (phys + sizeof (DOS_MCB) > cpu->mem_size) {
+                
+                    invalid_chain = 1;
+                    
+                    dos_set_error (cpu, 0x0009);
+                    break;
+                
+                }
+                
+                if (!is_valid_mcb (cpu, current_seg)) {
+                
+                    invalid_chain = 1;
+                    
+                    dos_set_error (cpu, 0x0009);
+                    break;
+                
+                }
+                
+                mcb = (DOS_MCB *)&cpu->mem[phys];
+                
+                if (mcb->owner == 0) {
+                
+                    if (mcb->size > max_free) {
+                        max_free = mcb->size;
+                    }
+                    
+                    if (mcb->size >= requested) {
+                    
+                        if (mcb->size > requested + 1) {
+                        
+                            uint16_t old_size = mcb->size;
+                            uint8_t old_type = mcb->type;
+                            
+                            uint16_t next_seg = (uint16_t) (current_seg + 1 + requested);
+                            uint32_t next_phys = (uint32_t) next_seg << 4;
+                            
+                            DOS_MCB *next_mcb;
+                            
+                            if (next_phys + sizeof (DOS_MCB) > cpu->mem_size) {
+                            
+                                invalid_chain = 1;
+                                
+                                dos_set_error (cpu, 0x0009);
+                                break;
+                            
+                            }
+                            
+                            next_mcb = (DOS_MCB *)&cpu->mem[next_phys];
+                            next_mcb->type  = old_type;
+                            next_mcb->owner = 0;
+                            next_mcb->size  = (uint16_t) (old_size - requested - 1);
+                            
+                            mcb->type = 'M';
+                            mcb->size = requested;
+                            
+                            if (!is_valid_mcb (cpu, current_seg) || !is_valid_mcb (cpu, next_seg)) {
+                            
+                                invalid_chain = 1;
+                                
+                                dos_set_error (cpu, 0x0009);
+                                break;
+                            
+                            }
+                        
+                        }
+                        
+                        mcb->owner = 0x0050;
+                        
+                        cpu->regs[REG_AX] = (uint16_t) (current_seg + 1);
+                        cpu->flags &= (uint16_t) ~FLAG_CF;
+                        
+                        found = 1;
+                        break;
+                    
+                    }
+                
+                }
+                
+                if (mcb->type == 'Z') {
+                    break;
+                }
+                
+                {
+                
+                    uint16_t next_seg = (uint16_t) (current_seg + 1 + mcb->size);
+                    
+                    /* validate BEFORE moving */
+                    if (!is_valid_mcb (cpu, next_seg)) {
+                    
+                        invalid_chain = 1;
+                        
+                        dos_set_error (cpu, 0x0009);
+                        break;
+                    
+                    }
+                    
+                    current_seg = next_seg;
+                
+                }
+            
+            }
+            
+            if (!found && !invalid_chain) {
+            
+                dos_set_error (cpu, 0x08);
+                cpu->regs[REG_BX] = max_free;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x49: {
+        
+            uint16_t seg_to_free = cpu->es;
+            
+            uint16_t mcb_seg;
+            uint32_t mcb_phys;
+            
+            DOS_MCB *mcb;
+            
+            if (seg_to_free == 0) {
+            
+                dos_set_error (cpu, 0x0009);
+                break;
+            
+            }
+            
+            mcb_seg = (uint16_t) (seg_to_free - 1);
+            mcb_phys = (uint32_t) mcb_seg << 4;
+            
+            if (!is_valid_mcb (cpu, mcb_seg)) {
+            
+                dos_set_error (cpu, 0x0009);
+                break;
+            
+            }
+            
+            mcb = (DOS_MCB *)&cpu->mem[mcb_phys];
+            
+            if (mcb->owner == 0) {
+            
+                dos_set_error (cpu, 0x0009);
+                break;
+            
+            }
+            
+            mcb->owner = 0;
+            
+            while (mcb->type == 'M') {
+            
+                uint16_t next_seg = (uint16_t) (mcb_seg + 1 + mcb->size);
+                uint32_t next_phys = (uint32_t) next_seg << 4;
+                
+                DOS_MCB *next_mcb;
+                
+                if (!is_valid_mcb (cpu, next_seg)) {
+                    break;
+                }
+                
+                next_mcb = (DOS_MCB *)&cpu->mem[next_phys];
+                
+                if (next_mcb->owner != 0) {
+                    break;
+                }
+                
+                mcb->size = (uint16_t) (mcb->size + 1 + next_mcb->size);
+                mcb->type = next_mcb->type;
+            
+            }
+            
+            {
+            
+                uint16_t current_seg = 0x004F;
+                DOS_MCB *prev_mcb = NULL;
+                
+                while (current_seg < mcb_seg) {
+                
+                    uint32_t phys = (uint32_t) current_seg << 4;
+                    DOS_MCB *cur;
+                    
+                    if (!is_valid_mcb (cpu, current_seg)) {
+                        break;
+                    }
+                    
+                    cur = (DOS_MCB *)&cpu->mem[phys];
+                    
+                    if ((uint16_t) (current_seg + 1 + cur->size) == mcb_seg) {
+                    
+                        prev_mcb = cur;
+                        break;
+                    
+                    }
+                    
+                    if (cur->type == 'Z') {
+                        break;
+                    }
+                    
+                    current_seg = (uint16_t) (current_seg + 1 + cur->size);
+                
+                }
+                
+                if (prev_mcb != NULL && prev_mcb->owner == 0) {
+                
+                    prev_mcb->size = (uint16_t) (prev_mcb->size + 1 + mcb->size);
+                    prev_mcb->type = mcb->type;
+                
+                }
+            
+            }
+            
+            cpu->flags &= (uint16_t) ~FLAG_CF;
+            break;
+        
+        }
+        
+        case 0x4A: {                    /* Resize Memory Block */
+        
+            uint16_t requested = cpu->regs[REG_BX];
+            uint16_t seg = cpu->es;                         /* block data segment */
+            
+            uint16_t mcb_seg = (uint16_t) (seg - 1);
+            uint32_t mcb_phys = (uint32_t) mcb_seg << 4;
+            
+            DOS_MCB *mcb;
+            
+            if (mcb_phys + sizeof (DOS_MCB) > cpu->mem_size) {
+            
+                dos_set_error (cpu, 0x0009);
+                break;
+            
+            }
+            
+            mcb = (DOS_MCB *)&cpu->mem[mcb_phys];
+            
+            if (!is_valid_mcb (cpu, mcb_seg)) {
+            
+                dos_set_error (cpu, 0x0009);
+                break;
+            
+            }
+            
+            if (requested == mcb->size) {
+            
+                cpu->flags &= (uint16_t) ~FLAG_CF;
+                break;
+            
+            }
+            
+            /* SHRINK */
+            if (requested < mcb->size) {
+            
+                uint16_t old_size = mcb->size;
+                uint8_t old_type = mcb->type;
+                
+                uint16_t new_free_mcb_seg = (uint16_t) (mcb_seg + 1 + requested);
+                uint32_t new_free_phys = (uint32_t) new_free_mcb_seg << 4;
+                
+                DOS_MCB *tail;
+                
+                if (old_size <= requested) {
+                
+                    dos_set_error (cpu, 0x0008);
+                    break;
+                
+                }
+                
+                if (new_free_phys + sizeof (DOS_MCB) > cpu->mem_size) {
+                
+                    dos_set_error (cpu, 0x0008);
+                    break;
+                
+                }
+                
+                tail = (DOS_MCB *) &cpu->mem[new_free_phys];
+                
+                tail->type  = old_type;
+                tail->owner = 0;
+                tail->size  = (uint16_t) (old_size - requested - 1);
+                
+                mcb->type = 'M';
+                mcb->size = requested;
+                
+                if (!is_valid_mcb (cpu, mcb_seg) || !is_valid_mcb (cpu, new_free_mcb_seg)) {
+                
+                    dos_set_error (cpu, 0x0009);
+                    break;
+                
+                }
+                
+                cpu->flags &= (uint16_t) ~FLAG_CF;
+                break;
+            
+            }
+            
+            /* EXPAND */
+            {
+            
+                uint16_t next_mcb_seg = (uint16_t) (mcb_seg + 1 + mcb->size);
+                uint32_t next_phys = (uint32_t) next_mcb_seg << 4;
+                uint16_t max_size = mcb->size;
+                
+                if (mcb->type == 'M' && next_phys + sizeof (DOS_MCB) <= cpu->mem_size) {
+                
+                    DOS_MCB *next = (DOS_MCB *)&cpu->mem[next_phys];
+                    
+                    if ((next->type == 'M' || next->type == 'Z') && next->owner == 0) {
+                    
+                        max_size = (uint16_t) (mcb->size + 1 + next->size);
+                        
+                        if (requested <= max_size) {
+                        
+                            uint16_t remain = (uint16_t) (max_size - requested);
+                            
+                            uint16_t split_seg = 0;
+                            uint32_t split_phys = 0;
+                            
+                            DOS_MCB *split = NULL;
+                            
+                            if (remain > 0) {
+                            
+                                split_seg = (uint16_t) (mcb_seg + 1 + requested);
+                                split_phys = (uint32_t) split_seg << 4;
+                                
+                                if (split_phys + sizeof (DOS_MCB) > cpu->mem_size) {
+                                
+                                    dos_set_error (cpu, 0x0009);
+                                    break;
+                                
+                                }
+                                
+                                split = (DOS_MCB *)&cpu->mem[split_phys];
+                                
+                                split->type  = next->type;
+                                split->owner = 0;
+                                split->size  = (uint16_t) (remain - 1);
+                                
+                                mcb->type = 'M';
+                            
+                            } else {
+                                mcb->type = next->type;
+                            }
+                            
+                            mcb->size = requested;
+                            
+                            {
+                            
+                                uint16_t expected_next = (uint16_t) (mcb_seg + 1 + mcb->size);
+                                
+                                if (remain > 0) {
+                                
+                                    if (expected_next != split_seg) {
+                                        printf ("MCB SPLIT BROKEN: %04X -> %04X (expected %04X)\n", mcb_seg, expected_next, split_seg);
+                                    }
+                                
+                                }
+                            
+                            }
+                            
+                            if (!is_valid_mcb (cpu, mcb_seg)) {
+                            
+                                dos_set_error (cpu, 0x0009);
+                                break;
+                            
+                            }
+                            
+                            if (remain > 0) {
+                            
+                                if (!is_valid_mcb (cpu, split_seg)) {
+                                
+                                    dos_set_error (cpu, 0x0009);
+                                    break;
+                                
+                                }
+                            
+                            }
+                            
+                            cpu->flags &= (uint16_t) ~FLAG_CF;
+                            break;
+                        
+                        }
+                    
+                    }
+                
+                }
+                
+                dos_set_error (cpu, 0x0008);
+                cpu->regs[REG_BX] = max_size;
+                
+                break;
+            
+            }
+        
+        }
+        
+        case 0x4C:              /* Terminate */
+        
+            cpu->halted = 1;
+            break;
+        
+        case 0x4E: {                    /* Find First Matching File */
+        
+            uint16_t addr = cpu->regs[REG_DX];
+            char pattern[260], resolved[260];
+            
+            uint8_t search_attr = (uint8_t) (cpu->regs[REG_CX] & 0xFF);
+            uint16_t dta_seg, dta_off;
+            
+            char *slash;
+            const char *search_pattern;
+            
+            size_t len;
+            int slot;
+            
+            uint32_t dta_phys;
+            
+            dos_read_asciiz (cpu, cpu->ds, addr, pattern, sizeof (pattern));
+            dos_resolve_path (pattern, resolved, sizeof (resolved));
+            
+            slot = alloc_handle_search_slot ();
+            
+            if (slot < 0) {
+            
+                dos_set_error (cpu, 0x0004);                /* Too many open files */
+                break;
+            
+            }
+            
+            slash = strrchr (resolved, '/');
+            
+            if (slash) {
+            
+                len = (size_t) (slash - resolved);
+                
+                if (len >= sizeof (handle_searches[slot].dirpath)) {
+                    len = sizeof (handle_searches[slot].dirpath) - 1;
+                }
+                
+                memcpy (handle_searches[slot].dirpath, resolved, len);
+                handle_searches[slot].dirpath[len] = '\0';
+                
+                search_pattern = slash + 1;
+            
+            } else {
+            
+                strcpy (handle_searches[slot].dirpath, ".");
+                search_pattern = resolved;
+            
+            }
+            
+            len = strlen (search_pattern);
+            
+            if (len >= sizeof (handle_searches[slot].pattern)) {
+                len = sizeof (handle_searches[slot].pattern) - 1;
+            }
+            
+            memcpy (handle_searches[slot].pattern, search_pattern, len);
+            handle_searches[slot].pattern[len] = '\0';
+            
+            handle_searches[slot].dir = opendir (handle_searches[slot].dirpath[0] ? handle_searches[slot].dirpath : ".");
+            
+            if (!handle_searches[slot].dir) {
+            
+                handle_searches[slot].active = 0;
+                
+                dos_set_error (cpu, 0x0003);                /* Path not found */
+                break;
+            
+            }
+            
+            handle_searches[slot].attr = search_attr;
+            handle_searches[slot].active = 1;
+            
+            /* Store Slot in DTA Reserved Area (Offsets 0-20) */
+            dos_get_dta (cpu, &dta_seg, &dta_off);
+            dta_phys = get_phys (dta_seg, dta_off);
+            
+            mem_write8 (cpu->mem, cpu->mem_size, dta_phys + 0, 0xEA);      /* Signature */
+            mem_write8 (cpu->mem, cpu->mem_size, dta_phys + 1, (uint8_t) slot);
+            mem_write8 (cpu->mem, cpu->mem_size, dta_phys + 2, search_attr);
+            
+            /* Trigger the first search */
+            cpu->regs[REG_AX] = 0x4F00;                     /* Fake AX for internal jump */
+            goto handle_find_next;
+        
+        }
+        
+        case 0x4F: {                    /* Find Next Matching File */
+        handle_find_next: ;
+        
+            uint16_t dta_seg, dta_off;
+            
+            uint32_t dta_phys;
+            int slot;
+            
+            HANDLE_SEARCH *s;
+            
+            struct dirent *de;
+            int found = 0;
+            
+            dos_get_dta (cpu, &dta_seg, &dta_off);
+            dta_phys = get_phys (dta_seg, dta_off);
+            
+            /* Verify this DTA belongs to our emulator search */
+            if (mem_read8 (cpu->mem, cpu->mem_size, dta_phys + 0) != 0xEA) {
+                
+                dos_set_error (cpu, 0x0012);                /* No more files */
+                break;
+            
+            }
+            
+            slot = mem_read8 (cpu->mem, cpu->mem_size, dta_phys + 1);
+            
+            if (slot >= MAX_HANDLE_SEARCHES || !handle_searches[slot].active) {
+            
+                dos_set_error (cpu, 0x0012);
+                break;
+            
+            }
+            
+            s = &handle_searches[slot];
+            
+            while ((de = readdir (s->dir)) != NULL) {
+            
+                struct stat st;
+                
+                char full[520], dos_name[13];
+                uint8_t res_attr;
+                
+                if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) {
+                    continue;
+                }
+                
+                if (!dos_wildcard_match (s->pattern, de->d_name)) {
+                    continue;
+                }
+                
+                snprintf (full, sizeof (full), "%s/%s", s->dirpath, de->d_name);
+                
+                if (host_lstat (full, &st) != 0) {
+                    continue;
+                }
+                
+                res_attr = S_ISDIR (st.st_mode) ? 0x10 : 0x20;
+                
+                if (S_ISDIR (st.st_mode) && !(s->attr & 0x10)) {
+                    continue;
+                }
+                
+                dos_make_83_display_name_unique (s->dirpath, de->d_name, dos_name);
+                dos_write_dta_find_result (cpu, dos_name, (uint32_t) st.st_size, st.st_mtime, res_attr);
+                
+                found = 1;
+                break;
+            
+            }
+            
+            if (!found) {
+            
+                closedir (s->dir);
+                
+                s->active = 0;
+                dos_set_error (cpu, 0x0012);
+            
+            } else {
+            
+                cpu->regs[REG_AX] = 0;
+                dos_clear_success (cpu);
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x50: {                    /* Set current PSP */
+        
+            cpu->psp_seg = cpu->regs[REG_BX];
+            cpu->flags &= ~FLAG_CF;
+            
+            break;
+        
+        }
+        
+        case 0x51: {                    /* Get current PSP */
+        
+            cpu->regs[REG_BX] = cpu->psp_seg;
+            cpu->flags &= ~FLAG_CF;
+            
+            break;
+        
+        }
+        
+        case 0x56: {                    /* Rename File */
+            
+            char oldraw[260];
+            char newraw[260];
+            char oldname[260];
+            char newname[260];
+            
+            /**
+             * DOS AH=56h:
+             *   DS:DX -> old ASCIIZ filename
+             *   ES:DI -> new ASCIIZ filename
+             */
+            dos_read_asciiz (cpu, cpu->ds, cpu->regs[REG_DX], oldraw, sizeof (oldraw));
+            dos_read_asciiz (cpu, cpu->es, cpu->regs[REG_DI], newraw, sizeof (newraw));
+            
+            dos_resolve_path (oldraw, oldname, sizeof (oldname));
+            dos_resolve_path (newraw, newname, sizeof (newname));
+            
+            if (rename (oldname, newname) == 0) {
+            
+                dos_83_cache_invalidate ();
+                dos_clear_success (cpu);
+            
+            } else {
+                dos_set_error (cpu, 0x02);
+            }
+            
+            break;
+        
+        }
+        
+        case 0x57: {                    /* Get/Set file date and time */
+        
+            uint8_t subfn = get_reg8 (cpu, REG_AX); /* AL */
+            
+            switch (subfn) {
+                
+                case 0x00:              /* get */
+                
+                    cpu->regs[REG_CX] = 0; /* time */
+                    cpu->regs[REG_DX] = 0; /* date */
+                    
+                    cpu->flags &= ~FLAG_CF;
+                    break;
+                
+                case 0x01:              /* set */
+                
+                    cpu->flags &= ~FLAG_CF;
+                    break;
+                
+                default:
+                
+                    dos_set_error (cpu, 1);
+                    break;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x5D: {
+        
+            uint8_t al = (uint8_t) (cpu->regs[REG_AX] & 0xFF);
+            
+            switch (al) {
+                
+                case 0x00:
+                
+                    cpu->flags |= FLAG_CF;
+                    cpu->regs[REG_AX] = 0x0001;
+                    
+                    break;
+                
+                case 0x06:
+                
+                    cpu->flags &= ~FLAG_CF;
+                    cpu->ds = 0x0050;
+                    
+                    cpu->regs[REG_SI] = 0x0100;
+                    cpu->regs[REG_CX] = 0x0080;
+                    
+                    break;
+                
+                case 0x08:
+                case 0x09:
+                case 0x0A:
+                case 0x0B:
+                case 0x0C:
+                
+                    cpu->flags |= FLAG_CF;
+                    cpu->regs[REG_AX] = 0x0001;
+                    
+                    break;
+                
+                default:
+                
+                    printf ("Unhandled INT 21h AH=5Dh AL=%02X AX=%04X at %04X:%04X\n", al, cpu->regs[REG_AX], cpu->cs, cpu->ip);
+                    
+                    cpu->flags |= FLAG_CF;
+                    cpu->regs[REG_AX] = 0x0001;
+                    
+                    break;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x59: {                    /* Get Extended Error Info */
+        
+            cpu->regs[REG_AX] = dos_last_error;             /* extended error code */
+            cpu->regs[REG_BX] = 0x0000;                     /* error class */
+            cpu->regs[REG_CX] = 0x0000;                     /* suggested action */
+            cpu->regs[REG_DX] = 0x0000;                     /* error locus */
+            
+            dos_clear_success (cpu);
+            break;
+        
+        }
+        
+        case 0x62: {                    /* Get PSP address */
+        
+            cpu->regs[REG_BX] = cpu->psp_seg;
+            cpu->flags &= ~FLAG_CF;
+            
+            break;
+        
+        }
+        
+        case 0x63: {                    /* Get DBCS lead byte table */
+        
+            /**
+             * AH=63h AL=00h:
+             * 
+             *     Returns DS:SI -> table of lead byte ranges.
+             *     Empty table is: 00 00
+             */
+            
+            static uint16_t dbcs_seg = 0x0050;              /* PSP-ish/safe low area if valid in your setup */
+            static uint16_t dbcs_off = 0x00F0;
+            
+            mem_write8 (cpu->mem, cpu->mem_size, get_phys (dbcs_seg, dbcs_off), 0x00);
+            mem_write8 (cpu->mem, cpu->mem_size, get_phys (dbcs_seg, (uint16_t) (dbcs_off + 1)), 0x00);
+            
+            cpu->ds = dbcs_seg;
+            cpu->regs[REG_SI] = dbcs_off;
+            
+            cpu->regs[REG_AX] = 0x6300;
+            cpu->flags &= (uint16_t) ~FLAG_CF;
+            
+            break;
+        
+        }
+        
+        case 0x65: {                    /* Get extended country info */
+        
+            uint8_t al = cpu->regs[REG_AX] & 0xFF;
+            
+            /* Most programs call AL=01h (country info) */
+            if (al == 0x01) {
+            
+                uint16_t seg = cpu->ds;
+                uint16_t off = cpu->regs[REG_DX];
+                
+                /* Minimal safe structure (just zero it) */
+                for (int i = 0; i < 32; i++) {
+                    mem_write8 (cpu->mem, cpu->mem_size, get_phys (seg, (uint16_t) (off + i)), 0);
+                }
+                
+                cpu->regs[REG_AX] = 0x0001;                 /* success */
+                cpu->flags &= ~FLAG_CF;
+            
+            } else {
+            
+                /* Unsupported subfunction â€” still succeed */
+                cpu->flags &= ~FLAG_CF;
+                cpu->regs[REG_AX] = 0;
+            
+            }
+            
+            break;
+        
+        }
+        
+        case 0x69: {                    /* Get/Set Disk Serial Number */
+        
+            uint8_t al = get_reg8 (cpu, 0);
+            uint8_t bh = (uint8_t) (cpu->regs[REG_BX] >> 8);
+            uint8_t bl = (uint8_t) cpu->regs[REG_BX];
+            uint16_t off = cpu->regs[REG_DX];
+            uint32_t phys;
+            
+            int i;
+            
+            /* Only the emulated C: drive is available (0 = default). */
+            if (bl != 0 && bl != 3) {
+            
+                dos_set_error (cpu, 0x000F);                /* Invalid drive */
+                break;
+            
+            }
+            
+            if (bh != 0 || al > 1) {
+            
+                dos_set_error (cpu, 0x0001);                /* Invalid function */
+                break;
+            
+            }
+            
+            if (al == 0x00) {
+            
+                /* DS:DX -> 25-byte extended BPB information block. */
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, off), 0);
+                mem_write8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, (uint16_t) (off + 1)), 0);
+                
+                for (i = 0; i < 4; i++) {
+                
+                    phys = get_phys (cpu->ds, (uint16_t) (off + 2 + i));
+                    mem_write8 (cpu->mem, cpu->mem_size, phys, (uint8_t) (dos_volume_serial >> (i * 8)));
+                
+                }
+                
+                for (i = 0; i < 11; i++) {
+                
+                    phys = get_phys (cpu->ds, (uint16_t) (off + 6 + i));
+                    mem_write8 (cpu->mem, cpu->mem_size, phys, dos_volume_label[i]);
+                
+                }
+                
+                for (i = 0; i < 8; i++) {
+                
+                    phys = get_phys (cpu->ds, (uint16_t) (off + 17 + i));
+                    mem_write8 (cpu->mem, cpu->mem_size, phys, dos_volume_fs[i]);
+                
+                }
+            
+            } else {
+            
+                if (mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, off)) != 0 || mem_read8 (cpu->mem, cpu->mem_size, get_phys (cpu->ds, (uint16_t) (off + 1))) != 0) {
+                
+                    dos_set_error (cpu, 0x0001);            /* Unsupported info level */
+                    break;
+                
+                }
+                
+                dos_volume_serial = 0;
+                
+                for (i = 0; i < 4; i++) {
+                
+                    phys = get_phys (cpu->ds, (uint16_t) (off + 2 + i));
+                    dos_volume_serial |= (uint32_t) mem_read8 (cpu->mem, cpu->mem_size, phys) << (i * 8);
+                
+                }
+                
+                for (i = 0; i < 11; i++) {
+                
+                    phys = get_phys (cpu->ds, (uint16_t) (off + 6 + i));
+                    dos_volume_label[i] = mem_read8 (cpu->mem, cpu->mem_size, phys);
+                
+                }
+                
+                for (i = 0; i < 8; i++) {
+                
+                    phys = get_phys (cpu->ds, (uint16_t) (off + 17 + i));
+                    dos_volume_fs[i] = mem_read8 (cpu->mem, cpu->mem_size, phys);
+                
+                }
+            
+            }
+            
+            set_reg8 (cpu, 0, al);
+            
+            dos_clear_success (cpu);
+            break;
+        
+        }
+        
+        default:
+        
+            printf ("Unhandled INT 21h Function: AH=0x%02X\n", ah);
+            
+            dos_set_error (cpu, 0x0001); /* invalid function */
+            break;
+    
+    }
+
+}
+
+static void int_10 (CPU8086 *cpu) {
+
+    uint8_t ah = get_reg8 (cpu, 4);
+    
+    switch (ah) {
+    
+        case 0x0E: {
+        
+            uint8_t al = get_reg8 (cpu, 0);
+            dos_write_console_char (stdout, al);
+            
+            fflush (stdout);
+            break;
+        
+        }
+        
+        case 0x02:
+        
+            /* set cursor position: accept and ignore */
+            break;
+        
+        case 0x0F:                      /* get current video mode */
+        
+            set_reg8 (cpu, 0, 0x03);    /* 80x25 text mode */
+            set_reg8 (cpu, 5, 0x00);    /* page */
+            
+            set_reg8 (cpu, 4, 80);      /* columns in AH */
+            break;
+        
+        default:
+        
+            printf ("Unknown Video Function 0x%02X\n", ah);
+            break;
+    
+    }
+
+}
+
+static void int_13 (CPU8086 *cpu) {
+
+    uint8_t ah = get_reg8 (cpu, 4);
+    
+    switch (ah) {
+    
+        case 0x00:                      /* reset disk system */
+        
+            set_reg8 (cpu, 4, 0x00);
+            cpu->flags &= (uint16_t) ~FLAG_CF;
+            break;
+        
+        case 0x02:                      /* read sectors */
+        
+            /* Stub success for now */
+            set_reg8 (cpu, 4, 0x00);    /* AH=status */
+            set_reg8 (cpu, 0, get_reg8 (cpu, REG_AX));      /* preserve AL sector count if you want */
+            cpu->flags &= (uint16_t) ~FLAG_CF;
+            break;
+        
+        case 0x08:                      /* get drive parameters */
+        
+            set_reg8 (cpu, 4, 0x00);                        /* success */
+            
+            cpu->regs[REG_CX] = 0x0001;                     /* tiny fake geometry */
+            cpu->regs[REG_DX] = 0x0001;
+            
+            cpu->flags &= (uint16_t) ~FLAG_CF;
+            break;
+        
+        default:
+        
+            printf ("Unknown Disk Function 0x%02X\n", ah);
+            
+            set_reg8 (cpu, 4, 0x01);  /* status */
+            cpu->flags |= FLAG_CF;
+            
+            break;
+    
+    }
+
+}
+
+static void int_15 (CPU8086 *cpu) {
+
+    if (cpu->regs[REG_AX] == 0x1022 && cpu->regs[REG_BX] == 0x0000) {
+    
+        /* TopView/DESQview version check: no multitasker is installed. */
+        cpu->regs[REG_BX] = 0x0000;
+    
+    }
+    
+    /* Unsupported BIOS function. */
+    set_reg8 (cpu, 4, 0x86);
+    cpu->flags |= FLAG_CF;
+
+}
+
+static void int_16 (CPU8086 *cpu) {
+
+    uint8_t ah = get_reg8 (cpu, 4);
+    
+    switch (ah) {
+    
+        case 0x00: {                    /* read key */
+        
+            int ch = host_read_key (cpu);
+            
+            if (ch == EOF) {
+                ch = 0;
+            }
+            
+            cpu->regs[REG_AX] = (uint16_t) (uint8_t) ch;
+            break;
+        
+        }
+        
+        case 0x01:                      /* check key */
+        
+            cpu->flags |= FLAG_ZF;      /* no key available */
+            break;
+        
+        default:
+        
+            printf ("Unknown Keyboard Function 0x%02X\n", ah);
+            break;
+    
+    }
+
+}
+
+/**
+ * The IVT contains real guest addresses. All 256 vectors initially point
+ * to resident stubs (INT FFh / IRET / NOP). The stub-local INT FFh is the
+ * native-services bridge; other INT FFh instructions use the guest IVT.
+ */
+void init_ivt (CPU8086 *cpu) {
+
+    const char *env_long_paths = getenv ("DOS_LONG_PATHS");
+    const char *env_p = getenv ("DOS_FCB_START");
+    
+    uint32_t stub_base = (uint32_t) DOS_IVT_STUB_SEG << 4;
+    unsigned int i;
+    
+    for (i = 0; i < 256; i++) {
+    
+        uint32_t entry = stub_base + (i * 4);
+        
+        mem_write16 (cpu->mem, cpu->mem_size, i * 4, i * 4);
+        mem_write16 (cpu->mem, cpu->mem_size, i * 4 + 2, DOS_IVT_STUB_SEG);
+        
+        mem_write8 (cpu->mem, cpu->mem_size, entry + 0, 0xCD);
+        mem_write8 (cpu->mem, cpu->mem_size, entry + 1, 0xFF);
+        mem_write8 (cpu->mem, cpu->mem_size, entry + 2, 0xCF);
+        mem_write8 (cpu->mem, cpu->mem_size, entry + 3, 0x90);
+    
+    }
+    
+    dos_long_paths = env_long_paths && strcmp (env_long_paths, "1") == 0;
+    
+    if (env_p) {
+    
+        dos_drive_root[0] = '\0';
+        dos_append_bounded (dos_drive_root, sizeof (dos_drive_root), env_p);
+    
+    }
+    
+    file_handles[0] = stdin;
+    file_handles[1] = stdout;
+    file_handles[2] = stderr;
+
+}
+
+void handle_interrupt (CPU8086 *cpu, uint8_t interrupt_number) {
+
+    uint32_t vector = (uint32_t) interrupt_number * 4;
+    
+    uint16_t new_ip = mem_read16 (cpu->mem, cpu->mem_size, vector);
+    uint16_t new_cs = mem_read16 (cpu->mem, cpu->mem_size, vector + 2);
+    
+    push16 (cpu, cpu->flags);
+    push16 (cpu, cpu->cs);
+    push16 (cpu, cpu->ip);
+    
+    cpu->flags &= ~(0x0100 | FLAG_IF);
+    
+    cpu->cs = new_cs;
+    cpu->ip = new_ip;
+
+}
+
+void native_interrupt (CPU8086 *cpu, uint8_t interrupt_number) {
+
+    uint16_t sp = cpu->regs[REG_SP];
+    uint16_t ss = cpu->ss;
+    
+    uint32_t flags_addr = get_phys (ss, (uint16_t) (sp + 4));
+    uint16_t saved_flags = mem_read16 (cpu->mem, cpu->mem_size, flags_addr);
+    
+    /**
+     * The existing C services expect the caller's flags, and return flags
+     * by modifying cpu->flags. Carry their results across the guest IRET.
+     */
+    cpu->flags = saved_flags;
+    
+    switch (interrupt_number) {
+    
+        case 0x00:
+        
+            int_0 (cpu);
+            break;
+        
+        case 0x01:
+        
+            int_1 (cpu);
+            break;
+        
+        case 0x02:
+        
+            int_2 (cpu);
+            break;
+        
+        case 0x03:
+        
+            int_3 (cpu);
+            break;
+        
+        case 0x04:
+        
+            int_4 (cpu);
+            break;
+        
+        case 0x05:
+        
+            int_5 (cpu);
+            break;
+        
+        case 0x06:
+        
+            int_6 (cpu);
+            break;
+        
+        case 0x07:
+        
+            int_7 (cpu);
+            break;
+        
+        case 0x10:
+        
+            int_10 (cpu);
+            break;
+        
+        case 0x13:
+        
+            int_13 (cpu);
+            break;
+        
+        case 0x15:
+        
+            int_15 (cpu);
+            break;
+        
+        case 0x16:
+        
+            int_16 (cpu);
+            break;
+        
+        case 0x20:
+        
+            int_20 (cpu);
+            break;
+        
+        case 0x21:
+        
+            int_21 (cpu);
+            break;
+        
+        case 0x2F:
+        
+            int_2F (cpu);
+            break;
+        
+        default:
+        
+            printf ("Unhandled Interrupt: 0x%02X\n", interrupt_number);
+            break;
+    
+    }
+    
+    mem_write16 (cpu->mem, cpu->mem_size, flags_addr, cpu->flags);
+    cpu->flags &= ~(0x0100 | FLAG_IF);
+
+}
diff --git a/interrupts.h b/interrupts.h
new file mode 100644 (file)
index 0000000..082e29b
--- /dev/null
@@ -0,0 +1,15 @@
+/******************************************************************************
+ * @file            interrupts.h
+ *****************************************************************************/
+#ifndef     _INTERRUPTS_H
+#define     _INTERRUPTS_H
+
+#include    "cpu.h"
+
+/* Guest IVT and native DOS entry points. */
+void init_ivt (CPU8086 *cpu);
+
+void handle_interrupt (CPU8086 *cpu, uint8_t interrupt_number);
+void native_interrupt (CPU8086 *cpu, uint8_t interrupt_number);
+
+#endif      /* _INTERRUPTS_H */
diff --git a/loader.c b/loader.c
new file mode 100644 (file)
index 0000000..9ec754b
--- /dev/null
+++ b/loader.c
@@ -0,0 +1,219 @@
+/******************************************************************************
+ * @file            loader.c
+ *****************************************************************************/
+#include    <stdint.h>
+#include    <stdio.h>
+#include    <string.h>
+
+#include    "cpu.h"
+#include    "loader.h"
+#include    "memory.h"
+
+int load_com_file (const char *path, CPU8086 *cpu, uint8_t *mem, size_t mem_size, uint16_t load_seg, size_t *out_size) {
+
+    FILE *fp;
+    
+    size_t nread;
+    size_t max_len;
+    
+    uint16_t load_addr = (load_seg << 4);
+    
+    if (out_size != NULL) {
+        *out_size = 0;
+    }
+    
+    if (path == NULL || mem == NULL || (size_t) load_addr >= mem_size) {
+        return 0;
+    }
+    
+    fp = fopen (path, "rb");
+    
+    if (fp == NULL) {
+        return 0;
+    }
+    
+    max_len = mem_size - (size_t) load_addr;
+    memset (mem, 0, mem_size);
+    
+    nread = fread (mem + load_addr, 1, max_len, fp);
+    fclose (fp);
+    
+    load_addr += nread;
+    
+    cpu->cs = cpu->ss = load_seg - 0x10;
+    cpu->ip = 0x0100;                   /* Code starts at offset 256 */
+    
+    cpu->regs[REG_SP] = 0xFFFE;         /* Stack at the top */
+    
+    if (out_size != NULL) {
+        *out_size = nread;
+    }
+    
+    return 1;
+
+}
+
+typedef struct __attribute__ ((packed)) {
+
+    uint16_t signature;                 /* Must be 0x5A4D ("MZ") */
+    uint16_t extra_bytes;               /* Bytes on last page */
+    uint16_t pages;                     /* 512-byte pages in file */
+    uint16_t relocations;               /* Number of relocation entries */
+    uint16_t header_size;               /* Size of header in 16-byte paragraphs */
+    uint16_t min_alloc;                 /* Minimum extra paragraphs needed */
+    uint16_t max_alloc;                 /* Maximum extra paragraphs needed */
+    uint16_t initial_ss;                /* Initial relative SS */
+    uint16_t initial_sp;                /* Initial SP */
+    uint16_t checksum;                  /* Usually 0 */
+    uint16_t initial_ip;                /* Initial IP */
+    uint16_t initial_cs;                /* Initial relative CS */
+    uint16_t reloc_table;               /* File offset to relocation table */
+    uint16_t overlay_num;               /* Usually 0 */
+
+} MZHeader;
+
+int load_mz_file( const char *path, CPU8086 *cpu, uint16_t base_seg) {
+
+    uint32_t file_size, image_start, total_size, image_size, load_phys;
+    uint16_t load_seg, i;
+    
+    long file_size_long;
+    
+    FILE *fp;
+    MZHeader hdr;
+    
+    if (!(fp = fopen (path, "rb"))) {
+        return 0;
+    }
+    
+    if (fread (&hdr, sizeof (hdr), 1, fp) != 1) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    if (hdr.signature != 0x5A4D) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    if (fseek (fp, 0, SEEK_END) != 0) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    file_size_long = ftell (fp);
+    
+    if (file_size_long < 0) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    file_size = (uint32_t) file_size_long;
+    image_start = (uint32_t) hdr.header_size * 16;
+    
+    if (hdr.pages == 0) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    if (hdr.extra_bytes == 0) {
+        total_size = (uint32_t) hdr.pages * 512;
+    } else {
+        total_size = (uint32_t) (hdr.pages - 1) * 512 + hdr.extra_bytes;
+    }
+    
+    if (total_size < image_start || image_start > file_size) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    image_size = total_size - image_start;
+    
+    /**
+     * Load image at a single base segment. CS/IP and SS/SP stay relative
+     * to that load segment, exactly as defined by the MZ header.
+     */
+    load_seg = base_seg;
+    load_phys = (uint32_t) load_seg << 4;
+    
+    if (load_phys + image_size > cpu->mem_size) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    if (fseek (fp, (long) image_start, SEEK_SET) != 0) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    if (fread (&cpu->mem[load_phys], 1, image_size, fp) != image_size) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    if (fseek (fp, hdr.reloc_table, SEEK_SET) != 0) {
+    
+        fclose (fp);
+        return 0;
+    
+    }
+    
+    for (i = 0; i < hdr.relocations; i++) {
+    
+        uint16_t offset, segment;
+        
+        if (fread (&offset, 2, 1, fp) != 1 || fread (&segment, 2, 1, fp) != 1) {
+        
+            fclose (fp);
+            return 0;
+        
+        }
+        
+        {
+        
+            uint32_t fixup_addr = get_phys ((uint16_t) (load_seg + segment), offset);
+            uint16_t val;
+            
+            if (fixup_addr + 1 >= cpu->mem_size) {
+            
+                fclose (fp);
+                return 0;
+            
+            }
+            
+            val = mem_read16 (cpu->mem, cpu->mem_size, fixup_addr);
+            val = (uint16_t) (val + load_seg);
+            
+            mem_write16 (cpu->mem, cpu->mem_size, fixup_addr, val);
+        
+        }
+    
+    }
+    
+    cpu->cs = (uint16_t) (load_seg + hdr.initial_cs);
+    cpu->ip = hdr.initial_ip;
+    
+    cpu->ss = (uint16_t) (load_seg + hdr.initial_ss);
+    cpu->regs[REG_SP] = hdr.initial_sp;
+    
+    fclose (fp);
+    return 1;
+}
diff --git a/loader.h b/loader.h
new file mode 100644 (file)
index 0000000..0f0d390
--- /dev/null
+++ b/loader.h
@@ -0,0 +1,15 @@
+/******************************************************************************
+ * @file            loader.h
+ *****************************************************************************/
+#ifndef     _LOADER_H
+#define     _LOADER_H
+
+#include    <stddef.h>
+#include    <stdint.h>
+
+#include    "cpu.h"
+
+int load_com_file (const char *path, CPU8086 *cpu, uint8_t *mem, size_t mem_size, uint16_t load_addr, size_t *out_size);
+int load_mz_file (const char *path, CPU8086 *cpu, uint16_t base_seg);
+
+#endif      /* _LOADER_H */
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..14d9a34
--- /dev/null
+++ b/main.c
@@ -0,0 +1,301 @@
+/******************************************************************************
+ * @file            main.c
+ *****************************************************************************/
+#include    <ctype.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    "cpu.h"
+#include    "loader.h"
+#include    "interrupts.h"
+#include    "heap.h"
+#include    "memory.h"
+
+#define     MEM_SIZE                    (640UL << 10)
+#define     EXE_LOAD_SEG                0x0060
+
+static void usage (const char *argv0) {
+    fprintf (stderr, "Usage: %s [--trace] program.com\n", argv0);
+}
+
+static void psp_clear_fcb (CPU8086 *cpu, uint32_t psp_base, uint16_t off) {
+
+    int i;
+    mem_write8 (cpu->mem, cpu->mem_size, psp_base + off + 0, 0x00);
+    
+    for (i = 1; i < 16; i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, psp_base + off + i, 0x00);
+    }
+
+}
+
+static void psp_make_drive_fcb (CPU8086 *cpu, uint32_t psp_base, uint16_t off, char drive) {
+
+    uint8_t d;
+    int i;
+    
+    if (drive >= 'a' && drive <= 'z') {
+        drive -= 32;
+    }
+    
+    d = (uint8_t) (drive - 'A' + 1);
+    mem_write8 (cpu->mem, cpu->mem_size, psp_base + off + 0, d);
+    
+    for (i = 1; i <= 11; i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, psp_base + off + i, ' ');
+    }
+    
+    for (i = 12; i < 16; i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, psp_base + off + i, 0x00);
+    }
+
+}
+
+void init_psp (const char *path, CPU8086 *cpu, const char *args, uint16_t base_seg) {
+
+    uint32_t psp_base, env_base, p;
+    uint16_t env_seg;
+    
+    cpu->psp_seg = base_seg - 0x10;
+    psp_base = (uint32_t) cpu->psp_seg << 4;
+    
+    env_seg = cpu->psp_seg - 0x10;
+    env_base = (uint32_t) env_seg << 4;
+    
+    p = env_base;
+    
+    /* minimal valid environment: double NULL */
+    mem_write8 (cpu->mem, cpu->mem_size, p++, 0x00);
+    mem_write8 (cpu->mem, cpu->mem_size, p++, 0x00);
+    
+    /* Count of extra strings following the environment = 1 */
+    mem_write16 (cpu->mem, cpu->mem_size, p, 0x0001);
+    p += 2;
+    
+    for (const char *s = path; *s; s++) {
+    
+        char ch = *s;
+        
+        if (ch == '/') {
+            ch = '\\';
+        }
+        
+        mem_write8 (cpu->mem, cpu->mem_size, p++, (uint8_t) toupper((int) ch));
+    
+    }
+    
+    mem_write8 (cpu->mem, cpu->mem_size, p++, 0);
+    
+    /* PSP:0000 = INT 20h */
+    mem_write8 (cpu->mem, cpu->mem_size, psp_base + 0x00, 0xCD);
+    mem_write8 (cpu->mem, cpu->mem_size, psp_base + 0x01, 0x20);
+    
+    /* PSP:0002 = segment beyond allocation */
+    mem_write16 (cpu->mem, cpu->mem_size, psp_base + 0x02, DOS_MEMORY_END_SEG);
+    
+    /* PSP:0005 - CP/M CALL 5 compatibility and .COM segment size. */
+    mem_write8 (cpu->mem, cpu->mem_size, psp_base + 0x05, 0x9A);
+    
+    mem_write16 (cpu->mem, cpu->mem_size, psp_base + 0x06, 0xFFF0);
+    mem_write16 (cpu->mem, cpu->mem_size, psp_base + 0x08, 0xF00D);
+    
+    /* PSP:0016 = parent PSP */
+    mem_write16 (cpu->mem, cpu->mem_size, psp_base + 0x16, 0x0000);
+    
+    /* PSP:0018 = DOS Job File Table (JFT). */
+    for (int i = 0; i < 20; i++) {
+        mem_write8 (cpu->mem, cpu->mem_size, psp_base + 0x18 + i, (uint8_t) ((i < 5) ? i : 0xFF));
+    }
+    
+    /* PSP:0032/0034 = JFT length and pointer (DOS 3+). */
+    mem_write16 (cpu->mem, cpu->mem_size, psp_base + 0x32, 20);
+    mem_write16 (cpu->mem, cpu->mem_size, psp_base + 0x34, 0x0018);
+    mem_write16 (cpu->mem, cpu->mem_size, psp_base + 0x36, cpu->psp_seg);
+    
+    /* PSP:002C = environment segment */
+    mem_write16 (cpu->mem, cpu->mem_size, psp_base + 0x2C, env_seg);
+    
+    /* Command tail */
+    if (args) {
+    
+        uint8_t len = (uint8_t) strlen (args);
+        
+        if (len > 126) {
+            len = 126;
+        }
+        
+        mem_write8 (cpu->mem, cpu->mem_size, psp_base + 0x80, len);
+        
+        for (uint8_t i = 0; i < len; i++) {
+            mem_write8 (cpu->mem, cpu->mem_size, psp_base + 0x81 + i, (uint8_t) args[i]);
+        }
+        
+        mem_write8 (cpu->mem, cpu->mem_size, psp_base + 0x81 + len, 0x0D);
+    
+    } else {
+    
+        mem_write8 (cpu->mem, cpu->mem_size, psp_base + 0x80, 0);
+        mem_write8 (cpu->mem, cpu->mem_size, psp_base + 0x81, 0x0D);
+    
+    }
+    
+    psp_clear_fcb (cpu, psp_base, 0x5C);
+    psp_clear_fcb (cpu, psp_base, 0x6C);
+    
+    {
+    
+        const char *a = args;
+        
+        while (a && (*a == ' ' || *a == '\t')) {
+            a++;
+        }
+        
+        if (a && a[0] && ((a[0] >= 'A' && a[0] <= 'Z') || (a[0] >= 'a' && a[0] <= 'z')) && a[1] == ':') {
+            psp_make_drive_fcb (cpu, psp_base, 0x5C, a[0]);
+        }
+    
+    }
+    
+    /* Environment MCB */
+    {
+    
+        uint32_t env_mcb_phys = (uint32_t) (env_seg - 1) << 4;
+        
+        /* paragraphs owned by environment block, excluding the MCB itself */
+        uint16_t env_block_size = 0x000F;                   /* 15 paragraphs => 0x0040..0x004E */
+        
+        mem_write8 (cpu->mem, cpu->mem_size, env_mcb_phys + 0x00, 'M');
+        
+        mem_write16 (cpu->mem, cpu->mem_size, env_mcb_phys + 0x01, cpu->psp_seg);              /* owner = PSP */
+        mem_write16 (cpu->mem, cpu->mem_size, env_mcb_phys + 0x03, env_block_size);
+        
+        for (int i = 5; i < 16; i++) {
+            mem_write8 (cpu->mem, cpu->mem_size, env_mcb_phys + i, 0);
+        }
+    
+    }
+    
+    /* PSP MCB */
+    {
+    
+        uint32_t mcb_phys = (uint32_t) (cpu->psp_seg - 1) << 4;
+        uint16_t block_size = (uint16_t) (DOS_MEMORY_END_SEG - cpu->psp_seg);
+        
+        mem_write8 (cpu->mem, cpu->mem_size, mcb_phys + 0x00, 'Z');
+        mem_write16 (cpu->mem, cpu->mem_size, mcb_phys + 0x01, cpu->psp_seg);
+        mem_write16 (cpu->mem, cpu->mem_size, mcb_phys + 0x03, block_size);
+        
+        for (int i = 5; i < 16; i++) {
+            mem_write8 (cpu->mem, cpu->mem_size, mcb_phys + i, 0);
+        }
+    
+    }
+    
+    cpu->ds = cpu->es = cpu->psp_seg;
+
+}
+
+int main (int argc, char **argv) {
+
+    uint8_t *mem;
+    CPU8086 cpu;
+    
+    const char *path = NULL;
+    char cmd_tail[128] = {0};
+    
+    int trace = 0, rc;
+    size_t loaded = 0;
+    
+    int offset = 0, len, i;
+    unsigned long steps = 0;
+    
+    for (i = 1; i < argc; i++) {
+    
+        if (strcmp (argv[i], "--trace") == 0) {
+        
+            trace = 1;
+            continue;
+        
+        }
+        
+        if (path == NULL) {
+        
+            path = argv[i];
+            continue;
+        
+        }
+        
+        len = strlen (argv[i]);
+        
+        if (offset + len + 2 < 126) {
+        
+            strcat(cmd_tail, " ");
+            strcat(cmd_tail, argv[i]);
+            
+            offset += len + 1;
+        
+        }
+    
+    }
+
+    if (path == NULL) {
+    
+        usage (argv[0]);
+        return 1;
+    
+    }
+
+    mem = (uint8_t *) calloc ((MEM_SIZE), 1);
+    
+    if (mem == NULL) {
+    
+        fprintf (stderr, "Out of memory\n");
+        return 1;
+    
+    }
+    
+    cpu_reset (&cpu, mem, MEM_SIZE);
+    
+    if (load_mz_file (path, &cpu, EXE_LOAD_SEG << 4)) {
+        init_psp (path, &cpu, cmd_tail, EXE_LOAD_SEG);
+    } else if (load_com_file (path, &cpu, mem, MEM_SIZE, EXE_LOAD_SEG, &loaded)) {
+        init_psp (path, &cpu, cmd_tail, EXE_LOAD_SEG);
+    } else {
+    
+        fprintf (stderr, "Failed to load %s\n", path);
+        
+        free (mem);
+        return 1;
+    
+    }
+    
+    /* init_dos_heap(&cpu, 0x2000); */
+    init_ivt (&cpu);
+    
+    while (!cpu.halted) {
+    
+        if (trace) {
+        
+            printf ("step=%lu ", steps);
+            cpu_dump (&cpu);
+        
+        }
+        
+        rc = cpu_step (&cpu);
+        
+        if (rc <= 0) {
+        
+            free (mem);
+            return 1;
+        
+        }
+        
+        steps++;
+    
+    }
+    
+    free (mem);
+    return 0;
+
+}
diff --git a/memory.c b/memory.c
new file mode 100644 (file)
index 0000000..4409d98
--- /dev/null
+++ b/memory.c
@@ -0,0 +1,48 @@
+/******************************************************************************
+ * @file            memory.c
+ *****************************************************************************/
+#include    "memory.h"
+#include    "cpu.h"
+
+uint8_t mem_read8 (const uint8_t *mem, size_t mem_size, uint32_t addr) {
+
+    if (addr >= mem_size) {
+        return 0;
+    }
+    
+    return mem[addr];
+
+}
+
+uint16_t mem_read16 (uint8_t *mem, size_t size, uint32_t addr) {
+
+    uint8_t low = mem_read8 (mem, size, addr);
+    uint8_t high = mem_read8 (mem, size, (addr + 1) & 0xFFFFF); 
+    
+    return (uint16_t) low | ((uint16_t) high << 8);
+
+}
+
+int watch_suppress = 0;
+
+void mem_write8 (uint8_t *mem, size_t mem_size, uint32_t addr, uint8_t value) {
+    
+    if ((size_t) addr < mem_size) {
+        mem[addr] = value;
+    }
+
+}
+
+void mem_write16 (uint8_t *mem, size_t mem_size, uint32_t addr, uint32_t value) {
+
+    mem_write8 (mem, mem_size, addr, (uint8_t) (value & 0xff));
+    mem_write8 (mem, mem_size, (addr + 1) & 0xFFFFF, (uint8_t) (value >> 8));
+
+}
+
+void mem_write32 (uint8_t *mem, size_t mem_size, uint32_t addr, uint32_t value) {
+
+    mem_write16 (mem, mem_size, addr, (uint16_t) (value & 0xffff));
+    mem_write16 (mem, mem_size, (addr + 2) & 0xFFFFF, (uint16_t) (value >> 16) & 0xffff);
+
+}
diff --git a/memory.h b/memory.h
new file mode 100644 (file)
index 0000000..8c548d2
--- /dev/null
+++ b/memory.h
@@ -0,0 +1,20 @@
+/******************************************************************************
+ * @file            memory.h
+ *****************************************************************************/
+#ifndef     _MEMORY_H
+#define     _MEMORY_H
+
+#include    <stdint.h>
+#include    <stddef.h>
+
+#include    "cpu.h"
+extern int watch_suppress;
+
+uint8_t mem_read8 (const uint8_t *mem, size_t mem_size, uint32_t addr);
+uint16_t mem_read16 (uint8_t *mem, size_t mem_size, uint32_t addr);
+
+void mem_write8 (uint8_t *mem, size_t mem_size, uint32_t addr, uint8_t value);
+void mem_write16 (uint8_t *mem, size_t mem_size, uint32_t addr, uint32_t value);
+void mem_write32 (uint8_t *mem, size_t mem_size, uint32_t addr, uint32_t value);
+
+#endif      /* _MEMORY_H */
diff --git a/opcodes.c b/opcodes.c
new file mode 100644 (file)
index 0000000..4cf2c49
--- /dev/null
+++ b/opcodes.c
@@ -0,0 +1,51 @@
+/******************************************************************************
+ * @file            opcodes.c
+ *****************************************************************************/
+#include "opcodes.h"
+
+const char *opcode_name(uint8_t op) {
+    switch (op) {
+        case 0x00: return "ADD r/m8,r8";
+        case 0x01: return "ADD r/m16,r16";
+        case 0x02: return "ADD r8,r/m8";
+        case 0x03: return "ADD r16,r/m16";
+        case 0x04: return "ADD AL,imm8";
+        case 0x05: return "ADD AX,imm16";
+        case 0x28: return "SUB r/m8,r8";
+        case 0x29: return "SUB r/m16,r16";
+        case 0x2A: return "SUB r8,r/m8";
+        case 0x2B: return "SUB r16,r/m16";
+        case 0x2C: return "SUB AL,imm8";
+        case 0x2D: return "SUB AX,imm16";
+        case 0x30: return "XOR r/m8,r8";
+        case 0x31: return "XOR r/m16,r16";
+        case 0x32: return "XOR r8,r/m8";
+        case 0x33: return "XOR r16,r/m16";
+        case 0x34: return "XOR AL,imm8";
+        case 0x35: return "XOR AX,imm16";
+        case 0x38: return "CMP r/m8,r8";
+        case 0x39: return "CMP r/m16,r16";
+        case 0x3A: return "CMP r8,r/m8";
+        case 0x3B: return "CMP r16,r/m16";
+        case 0x3C: return "CMP AL,imm8";
+        case 0x3D: return "CMP AX,imm16";
+        case 0x7C: return "JL/JNGE rel8";
+        case 0x82: return "Grp1 r/m8,imm8";
+        case 0x86: return "XCHG r/m8,r8";
+        case 0x87: return "XCHG r/m16,r16";
+        case 0x88: return "MOV r/m8,r8";
+        case 0x89: return "MOV r/m16,r16";
+        case 0x8A: return "MOV r8,r/m8";
+        case 0x8B: return "MOV r16,r/m16";
+        case 0x90: return "NOP";
+        case 0xC3: return "RET";
+        case 0xC6: return "MOV r/m8,imm8";
+        case 0xC7: return "MOV r/m16,imm16";
+        case 0xCD: return "INT imm8";
+        case 0xE8: return "CALL rel16";
+        case 0xE9: return "JMP rel16";
+        case 0xEB: return "JMP short";
+        case 0xF4: return "HLT";
+        default: return "UNKNOWN";
+    }
+}
diff --git a/opcodes.h b/opcodes.h
new file mode 100644 (file)
index 0000000..d2e7d6a
--- /dev/null
+++ b/opcodes.h
@@ -0,0 +1,11 @@
+/******************************************************************************
+ * @file            opcodes.h
+ *****************************************************************************/
+#ifndef OPCODES_H
+#define OPCODES_H
+
+#include <stdint.h>
+
+const char *opcode_name(uint8_t op);
+
+#endif