From: Robert Pengelly Date: Sat, 17 Oct 2026 08:56:40 +0000 (+0100) Subject: Initial commit X-Git-Url: https://git.candlhat.org/?a=commitdiff_plain;h=4397e7fa499d97887b945d14604255c6fe503e41;p=dosemu.git Initial commit --- 4397e7fa499d97887b945d14604255c6fe503e41 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