From 4397e7fa499d97887b945d14604255c6fe503e41 Mon Sep 17 00:00:00 2001 From: Robert Pengelly Date: Sat, 17 Oct 2026 09:56:40 +0100 Subject: [PATCH] Initial commit --- .github/funding.yml | 1 + .github/workflows/mirror.yml | 18 + LICENSE | 24 + Makefile.unix | 29 + Makefile.w32 | 21 + README.md | 68 + cpu.c | 4564 ++++++++++++++++++++++++++ cpu.h | 65 + heap.c | 25 + heap.h | 26 + interrupts.c | 6008 ++++++++++++++++++++++++++++++++++ interrupts.h | 15 + loader.c | 219 ++ loader.h | 15 + main.c | 301 ++ memory.c | 48 + memory.h | 20 + opcodes.c | 51 + opcodes.h | 11 + 19 files changed, 11529 insertions(+) create mode 100644 .github/funding.yml create mode 100644 .github/workflows/mirror.yml create mode 100755 LICENSE create mode 100755 Makefile.unix create mode 100755 Makefile.w32 create mode 100755 README.md create mode 100644 cpu.c create mode 100644 cpu.h create mode 100644 heap.c create mode 100644 heap.h create mode 100644 interrupts.c create mode 100644 interrupts.h create mode 100644 loader.c create mode 100644 loader.h create mode 100644 main.c create mode 100644 memory.c create mode 100644 memory.h create mode 100644 opcodes.c create mode 100644 opcodes.h diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..ea28a94 --- /dev/null +++ b/.github/funding.yml @@ -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 index 0000000..085d5d3 --- /dev/null +++ b/.github/workflows/mirror.yml @@ -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 index 0000000..f50ef62 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/Makefile.unix b/Makefile.unix new file mode 100755 index 0000000..655490d --- /dev/null +++ b/Makefile.unix @@ -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 index 0000000..7ff9b1c --- /dev/null +++ b/Makefile.w32 @@ -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 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 index 0000000..8c03315 --- /dev/null +++ b/cpu.c @@ -0,0 +1,4564 @@ +/****************************************************************************** + * @file cpu.c + *****************************************************************************/ +#include +#include +#include + +#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 index 0000000..c3bdb70 --- /dev/null +++ b/cpu.h @@ -0,0 +1,65 @@ +/****************************************************************************** + * @file cpu.h + *****************************************************************************/ +#ifndef _CPU_H +#define _CPU_H + +#include +#include + +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 index 0000000..f61be50 --- /dev/null +++ b/heap.c @@ -0,0 +1,25 @@ +/****************************************************************************** + * @file heap.c + *****************************************************************************/ +#include + +#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 index 0000000..ff6f298 --- /dev/null +++ b/heap.h @@ -0,0 +1,26 @@ +/****************************************************************************** + * @file heap.h + *****************************************************************************/ +#ifndef _HEAP_H +#define _HEAP_H + +#include + +#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 index 0000000..f5009ac --- /dev/null +++ b/interrupts.c @@ -0,0 +1,6008 @@ +/****************************************************************************** + * @file interrupts.c + *****************************************************************************/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if defined (_WIN32) +# include +# include +# include +# include +#else +# include +# include +# include +#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 index 0000000..082e29b --- /dev/null +++ b/interrupts.h @@ -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 index 0000000..9ec754b --- /dev/null +++ b/loader.c @@ -0,0 +1,219 @@ +/****************************************************************************** + * @file loader.c + *****************************************************************************/ +#include +#include +#include + +#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 index 0000000..0f0d390 --- /dev/null +++ b/loader.h @@ -0,0 +1,15 @@ +/****************************************************************************** + * @file loader.h + *****************************************************************************/ +#ifndef _LOADER_H +#define _LOADER_H + +#include +#include + +#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 index 0000000..14d9a34 --- /dev/null +++ b/main.c @@ -0,0 +1,301 @@ +/****************************************************************************** + * @file main.c + *****************************************************************************/ +#include +#include +#include +#include + +#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 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 index 0000000..8c548d2 --- /dev/null +++ b/memory.h @@ -0,0 +1,20 @@ +/****************************************************************************** + * @file memory.h + *****************************************************************************/ +#ifndef _MEMORY_H +#define _MEMORY_H + +#include +#include + +#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 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 index 0000000..d2e7d6a --- /dev/null +++ b/opcodes.h @@ -0,0 +1,11 @@ +/****************************************************************************** + * @file opcodes.h + *****************************************************************************/ +#ifndef OPCODES_H +#define OPCODES_H + +#include + +const char *opcode_name(uint8_t op); + +#endif -- 2.34.1