// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// 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 OR COPYRIGHT HOLDERS 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.

// dvxPlatformDos.c  -- DOS/DJGPP platform implementation for DVX GUI
//
// All BIOS calls, DPMI functions, port I/O, inline assembly, and
// DOS-specific file handling are isolated in this single file.
//
// This file is the ONLY place where DJGPP headers (dpmi.h, go32.h,
// sys/nearptr.h, etc.) appear. Every other DVX module calls through
// the dvxPlatform.h interface, so porting to a new OS (Linux/SDL,
// Win32, or bare-metal ARM) requires replacing only this file and
// nothing else. The abstraction covers five domains:
//   1. VESA VBE video init / mode set / LFB mapping
//   2. Backbuffer-to-LFB flush using rep movsl
//   3. Span fill/copy primitives using inline asm (rep stosl / rep movsl)
//   4. Mouse input via INT 33h driver
//   5. Keyboard input via BIOS INT 16h
//
// Why BIOS INT 16h for keyboard instead of direct port I/O (scancode
// reading from port 0x60): BIOS handles typematic repeat, keyboard
// translation tables, and extended key decoding. Direct port I/O would
// require reimplementing all of that, and the DPMI host already hooks
// IRQ1 to feed the BIOS buffer. The BIOS approach is simpler and more
// portable across emulators (DOSBox, 86Box, PCem all handle it correctly).
//
// Why INT 33h for mouse: same rationale  -- the mouse driver handles
// PS/2 and serial mice transparently, and every DOS emulator provides
// a compatible driver. Polling via function 03h avoids the complexity
// of installing a real-mode callback for mouse events.

#include "dvxPlat.h"
#include "dvxPal.h"

#include <ctype.h>
#include <dir.h>
#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <math.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <sys/dxe.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

// DJGPP-specific headers  -- this is the ONLY file that includes these
#include <dpmi.h>
#include <go32.h>
#include <pc.h>
#include <sys/exceptn.h>
#include <sys/nearptr.h>
#include <sys/farptr.h>
#include <sys/movedata.h>

// VESA mode scoring weights: higher score = preferred mode
#define MODE_SCORE_16BPP       100  // 16bpp: fastest span fill (half the bytes of 32bpp)
#define MODE_SCORE_15BPP       90   // 15bpp: slightly below 16 (some BIOSes conflate them)
#define MODE_SCORE_32BPP       85   // 32bpp: true color but slower bus traffic
#define MODE_SCORE_8BPP        70   // 8bpp: palette management adds complexity
#define MODE_SCORE_PREF_BPP    20   // bonus if bpp matches user preference
#define MODE_SCORE_EXACT_RES   10   // bonus/penalty for exact/oversize resolution

// RDTSC calibration: measure over this many BIOS timer ticks
#define CLOCK_MEAS_TICKS 3

// Per-app memory tracking header magic
#define DVX_ALLOC_MAGIC 0xDEADBEEFUL

// Key-up detection ring buffer size
#define KEYUP_BUF_SIZE 16

// Worst-case byte span of int9Handler that must be locked into physical
// memory against DPMI paging.  Matches the rs232 driver's ISR_SIZE; the
// handler is small but a generous span guarantees full coverage.
#define KEYUP_ISR_SIZE 2048

// PS/2 scancode prefix and fake-shift bytes.  0xE0 precedes a single
// extended-key scancode; 0xE1 precedes the two 2-byte halves of the
// Pause/Break burst (E1 1D 45 E1 9D C5), which carries no real break code.
// The grey navigation keys are bracketed by "fake shift" releases (0xAA /
// 0xB6 emitted right after an 0xE0) that must not be reported as genuine
// Shift key-ups, while a standalone 0xAA / 0xB6 (real Left / Right Shift
// release) must be.
#define PS2_SCAN_PREFIX_E0      0xE0
#define PS2_SCAN_PREFIX_E1      0xE1
#define PS2_SCAN_FAKE_SHIFT_LE  0xAA
#define PS2_SCAN_FAKE_SHIFT_RE  0xB6

// Number of scancodes to swallow after each 0xE1 prefix in the Pause burst.
#define PS2_E1_TAIL_BYTES       2

// Sentinel for sLfbLinearAddr/sLfbMappedSize meaning "no LFB currently mapped".
#define LFB_NOT_MAPPED  0

// VGA splash screen (mode 13h) constants
#define SPLASH_VGA_W    320
#define SPLASH_VGA_H    200
#define SPLASH_VGA_ADDR 0xA0000

// 8-bit palette: 256 entries of 3 bytes (RGB) each
#define PALETTE_ENTRIES 256
#define PALETTE_BYTES   (PALETTE_ENTRIES * 3)

// Max VBE mode numbers we read from the BIOS mode list
#define MAX_VBE_MODES   256

// DOS drive numbers (1=A, 2=B, 3=C ...): enumerate fixed drives C: through Z:
#define FIRST_FIXED_DRIVE 3
#define MAX_DRIVE_NUMBER  26

// Mouse absolute-promotion thresholds (see the cursor-state block and
// platformMousePoll).  A driver that ignores the range we set clamps absolute
// reads into a low-res "trap" box (~640x200 on ctmouse); an absolute X/Y at or
// below the trap dimension is indistinguishable from a trap and never promotes.
// The height (240) sits above the observed ~200 trap with a guard band yet far
// below an honoured report (up to screenH-1).  Two consecutive out-of-trap
// polls are required: a hard-clamping driver cannot emit even one, so false
// promotion on real hardware is impossible while DOSBox costs at most a frame.
#define MOUSE_TRAP_BOX_W      640
#define MOUSE_TRAP_BOX_H      240
#define MOUSE_PROMOTE_CONSEC  2

// Mouse button bitmask for INT 33h function 03h (BL bits 0-2 = L/R/M).
#define MOUSE_BUTTON_MASK     0x07


// Per-app memory tracking header prepended to each allocation
typedef struct {
    uint32_t magic;
    int32_t  appId;
    uint32_t size;
    uint32_t pad;
} DvxAllocHeaderT;


// ============================================================
// Prototypes
// ============================================================

static void                    dvxMemGrow(int32_t appId);
static uint32_t                estimateClockMhz(void);
static int32_t                 findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d);
static void                    getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
static bool                    hasCpuid(void);
static void                    int9Handler(void);
static int32_t                 mapLfb(DisplayT *d, uint32_t physAddr);
static void                    platformCrashHandler(int sig);
void                           platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);
static dxe_symbol_table        sDxeExportTable[];
static int32_t                 setVesaMode(uint16_t mode);
static void                    sysInfoAppend(const char *fmt, ...);
static void                    unmapLfb(DisplayT *d);


// ============================================================
// Module state
// ============================================================

// Wheel state: set by platformMouseWheelInit, read by platformMousePoll
// and platformGetSystemInfo.  Declared here (above all functions) so
// every function in the file can see them.
static bool    sHasMouseWheel  = false;
static int32_t sLastWheelDelta = 0;

// Software-tracked mouse cursor.  sMouseRangeW/H are the clamp bounds, set to
// the VESA resolution in platformMouseInit.  Position comes from one of two
// schemes selected at RUNTIME (sMouseMode):
//
//   MICKEY (default): integrate INT 33h function 0Bh relative deltas into
//   sCurX/sCurY.  This is the ONLY scheme that works on real hardware / 86Box:
//   many DOS drivers (e.g. ctmouse) ignore the range functions 07h/08h in
//   unrecognised VESA modes and clamp absolute position (fn 03h) to a ~640x200
//   virtual screen, trapping the cursor in the upper-left of a high-res
//   display.  First fixed in commit 157d79f, silently reverted during the
//   refactor in 163959a.
//
//   ABSOLUTE: consume fn 03h position directly.  Required for DOSBox, which
//   honours fn 07h/08h but STARVES the fn 0Bh mickey counters when the host
//   mouse is uncaptured -- so the mickey scheme leaves the cursor dead there.
//
// DO NOT default to ABSOLUTE and DO NOT consume fn 03h position unless the
// driver has PROVEN at runtime it honours the high-res range: platformMousePoll
// starts in MICKEY and promotes to ABSOLUTE only after fn 03h reports a
// position outside the low-res trap box (which a trapped driver can never do).
// fn 07h/08h are issued at init purely as bait for this detector -- a trapped
// driver ignores them (the whole premise), an honouring driver then reports
// full-range coordinates.  Keep the promotion gate; removing it re-traps real
// hardware (the 163959a regression).
typedef enum {
    MouseModeMickeyE   = 0,
    MouseModeAbsoluteE = 1
} MouseModeE;

static int32_t    sMouseRangeW    = 0;
static int32_t    sMouseRangeH    = 0;
static int32_t    sCurX           = 0;
static int32_t    sCurY           = 0;
static MouseModeE sMouseMode      = MouseModeMickeyE;
static int32_t    sPromoteCounter = 0;

// Alt+key scan code to ASCII lookup table (indexed by BIOS scan code).
// INT 16h returns these scan codes with ascii=0 for Alt+key combos.
// Using a 256-byte lookup table instead of a switch or if-chain because
// this is called on every keypress and the table fits in a single cache
// line cluster. The designated initializer syntax leaves all other
// entries as zero, which is the "no mapping" sentinel.
static const char sAltScanToAscii[256] = {
    // Alt+letters
    [0x10] = 'q', [0x11] = 'w', [0x12] = 'e', [0x13] = 'r',
    [0x14] = 't', [0x15] = 'y', [0x16] = 'u', [0x17] = 'i',
    [0x18] = 'o', [0x19] = 'p', [0x1E] = 'a', [0x1F] = 's',
    [0x20] = 'd', [0x21] = 'f', [0x22] = 'g', [0x23] = 'h',
    [0x24] = 'j', [0x25] = 'k', [0x26] = 'l', [0x2C] = 'z',
    [0x2D] = 'x', [0x2E] = 'c', [0x2F] = 'v', [0x30] = 'b',
    [0x31] = 'n', [0x32] = 'm',
    // Alt+digits
    [0x78] = '1', [0x79] = '2', [0x7A] = '3', [0x7B] = '4',
    [0x7C] = '5', [0x7D] = '6', [0x7E] = '7', [0x7F] = '8',
    [0x80] = '9', [0x81] = '0',
};

// System information static buffer
static char    sSysInfoBuf[PLATFORM_SYSINFO_MAX];
static int32_t sSysInfoPos = 0;

// Per-app memory tracking  -- tracks every allocation made by DXE code
// via a 16-byte header prepended to each allocation. The DXE export
// table maps malloc/free/calloc/realloc/strdup to these wrappers so
// all DXE code is transparently tracked without #define macros.
//
// stb_ds is also tracked: the loader overrides STBDS_REALLOC/FREE
// to call dvxRealloc/dvxFree before including stb_ds.h.
//
// Cross-boundary safety: when dvxFree receives a pointer that was
// allocated by libc (not our wrapper), the magic check at ptr-16
// fails and we fall through to libc free. The DJGPP heap is a
// contiguous sbrk region so reading 16 bytes before any heap
// pointer is always valid memory (never unmapped).
int32_t  *dvxMemAppIdPtr = NULL;
static uint32_t *sAppMemUsed = NULL;
static int32_t   sAppMemCap  = 0;

// Key-up detection via INT 9 hook.  The BIOS keyboard interrupt (INT 16h)
// only reports key presses.  To detect key releases we chain INT 9 (the
// hardware keyboard IRQ) and read the scan code directly from port 0x60.
// Break codes have bit 7 set.  We queue them in a small ring buffer that
// platformKeyUpRead drains.
//
// All state the ISR touches lives in one struct so a single DPMI lock pins
// every byte contiguously; independent statics give no contiguity guarantee
// (a page fault inside the handler under a paging host would be fatal).
typedef struct {
    PlatformKeyEventT buf[KEYUP_BUF_SIZE];
    volatile int32_t  head;
    volatile int32_t  tail;
    volatile uint8_t  scanPrefix; // pending 0xE0 prefix, or 0
    volatile int32_t  e1Pending;  // scancodes left to swallow after 0xE1
} KeyUpStateT;

static KeyUpStateT        sKeyUp = { 0 };
static _go32_dpmi_seginfo sOldInt9;
static _go32_dpmi_seginfo sNewInt9;
static bool               sKeyUpInstalled = false;

// Crash recovery state  -- set up by platformInstallCrashHandler and used
// by platformCrashHandler to longjmp back to the caller's recovery point.
static jmp_buf       *sPlatformCrashJmp    = NULL;
static volatile int  *sPlatformCrashSignal = NULL;
static PlatformLogFnT sPlatformLogFn       = NULL;

// Linear address and size of the currently mapped physical LFB, saved by
// mapLfb so platformVideoFreeBuffers / platformVideoShutdown can unlock and
// free the DPMI physical mapping.  This is the RAW linear address returned by
// __dpmi_physical_address_mapping, NOT d->lfb (which has
// __djgpp_conventional_base folded in).  LFB_NOT_MAPPED means nothing mapped.
static uint32_t sLfbLinearAddr = LFB_NOT_MAPPED;
static uint32_t sLfbMappedSize = 0;


void *dvxCalloc(size_t nmemb, size_t size) {
    // Guard against nmemb*size overflowing size_t (calloc contract).
    if (size != 0 && nmemb > SIZE_MAX / size) {
        return NULL;
    }

    size_t total = nmemb * size;
    void *ptr    = dvxMalloc(total);

    if (ptr) {
        memset(ptr, 0, total);
    }

    return ptr;
}


void dvxFree(void *ptr) {
    if (!ptr) {
        return;
    }

    DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)ptr - 1;

    if (hdr->magic != DVX_ALLOC_MAGIC) {
        // Not a tracked allocation -- pass through to real free
        free(ptr);
        return;
    }

    int32_t appId = hdr->appId;

    if (appId >= 0 && appId < sAppMemCap) {
        sAppMemUsed[appId] -= hdr->size;
    }

    hdr->magic = 0;
    free(hdr);
}


void *dvxMalloc(size_t size) {
    int32_t appId = dvxMemAppIdPtr ? *dvxMemAppIdPtr : 0;

    // Guard the header addition against size_t wrap, which would otherwise
    // hand back a non-NULL pointer over an undersized block.
    if (size > SIZE_MAX - sizeof(DvxAllocHeaderT)) {
        return NULL;
    }

    DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)malloc(sizeof(DvxAllocHeaderT) + size);

    if (!hdr) {
        return NULL;
    }

    hdr->magic = DVX_ALLOC_MAGIC;
    hdr->appId = appId;
    hdr->size  = (uint32_t)size;
    hdr->pad   = 0;

    if (appId >= 0) {
        dvxMemGrow(appId);

        if (appId < sAppMemCap) {
            sAppMemUsed[appId] += (uint32_t)size;
        }
    }

    return hdr + 1;
}


uint32_t dvxMemGetAppUsage(int32_t appId) {
    if (appId < 0 || appId >= sAppMemCap) {
        return 0;
    }

    return sAppMemUsed[appId];
}


static void dvxMemGrow(int32_t appId) {
    if (appId < sAppMemCap) {
        return;
    }

    int32_t newCap = appId + 16;
    uint32_t *newArr = (uint32_t *)realloc(sAppMemUsed, newCap * sizeof(uint32_t));

    if (!newArr) {
        return;
    }

    memset(newArr + sAppMemCap, 0, (newCap - sAppMemCap) * sizeof(uint32_t));
    sAppMemUsed = newArr;
    sAppMemCap  = newCap;
}


void dvxMemResetApp(int32_t appId) {
    if (appId >= 0 && appId < sAppMemCap) {
        sAppMemUsed[appId] = 0;
    }
}


void *dvxRealloc(void *ptr, size_t size) {
    if (!ptr) {
        return dvxMalloc(size);
    }

    if (size == 0) {
        dvxFree(ptr);
        return NULL;
    }

    DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)ptr - 1;

    if (hdr->magic != DVX_ALLOC_MAGIC) {
        // Not tracked -- pass through to real realloc
        return realloc(ptr, size);
    }

    // Guard the header addition against size_t wrap, which would otherwise
    // hand back a non-NULL pointer over an undersized block.
    if (size > SIZE_MAX - sizeof(DvxAllocHeaderT)) {
        return NULL;
    }

    int32_t  appId   = hdr->appId;
    uint32_t oldSize = hdr->size;

    DvxAllocHeaderT *newHdr = (DvxAllocHeaderT *)realloc(hdr, sizeof(DvxAllocHeaderT) + size);

    if (!newHdr) {
        return NULL;
    }

    if (appId >= 0 && appId < sAppMemCap) {
        sAppMemUsed[appId] -= oldSize;
        sAppMemUsed[appId] += (uint32_t)size;
    }

    newHdr->size = (uint32_t)size;
    return newHdr + 1;
}


char *dvxStrdup(const char *s) {
    if (!s) {
        return NULL;
    }

    size_t len = strlen(s) + 1;
    char *dup  = (char *)dvxMalloc(len);

    if (dup) {
        memcpy(dup, s, len);
    }

    return dup;
}


// RDTSC calibration via BIOS timer.
// Measures TSC ticks over 3 BIOS timer ticks (~165 ms).  The BIOS timer
// at 0040:006C increments at 18.2065 Hz (1193182 / 65536 Hz per tick).
// Using 3 ticks instead of 1 reduces jitter from interrupt latency and
// gives a more stable reading.

static uint32_t estimateClockMhz(void) {
    uint32_t biosTimerAddr = 0x46C;  // linear address 0000:046C = 0040:006C

    // Wait for tick boundary to synchronize
    uint32_t tick0 = _farpeekl(_dos_ds, biosTimerAddr);

    while (_farpeekl(_dos_ds, biosTimerAddr) == tick0) {
        // spin
    }

    // Read TSC at tick boundary
    uint32_t lo1;
    uint32_t hi1;

    __asm__ __volatile__ ("rdtsc" : "=a"(lo1), "=d"(hi1));

    uint32_t tickStart = _farpeekl(_dos_ds, biosTimerAddr);

    // Wait for CLOCK_MEAS_TICKS more ticks
    while ((_farpeekl(_dos_ds, biosTimerAddr) - tickStart) < CLOCK_MEAS_TICKS) {
        // spin
    }

    // Read TSC at end
    uint32_t lo2;
    uint32_t hi2;

    __asm__ __volatile__ ("rdtsc" : "=a"(lo2), "=d"(hi2));

    uint64_t tsc1  = ((uint64_t)hi1 << 32) | lo1;
    uint64_t tsc2  = ((uint64_t)hi2 << 32) | lo2;
    uint64_t delta = tsc2 - tsc1;

    // Each BIOS tick = 65536 / 1193182 seconds = 54925.4 microseconds
    uint32_t mhz = (uint32_t)(delta / (CLOCK_MEAS_TICKS * 54925ULL));

    return mhz;
}


// Enumerates all VESA VBE modes and selects the best match for the
// requested resolution and color depth using a scoring algorithm.
//
// The approach is: call VBE function 0x4F00 to get the controller
// info block (which contains a pointer to the mode list), then call
// VBE function 0x4F01 for each mode to get its attributes. Each mode
// is scored by getModeInfo() and the highest-scoring mode wins.
//
// Why scoring instead of exact-match: real VESA BIOSes vary wildly in
// what modes they expose. Some have 640x480x16 but not x32; some only
// have 800x600. The scoring heuristic picks the closest usable mode
// rather than failing outright if the exact requested mode is absent.
//
// All VBE info block reads use DJGPP's far pointer API (_farpeekb/w/l)
// to access the DPMI transfer buffer (__tb), which lives in the first
// 1MB of address space (conventional memory). VBE BIOS calls use
// real-mode interrupts via __dpmi_int(), so all data must pass through
// the transfer buffer.

static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d) {
    __dpmi_regs r;
    uint16_t    bestMode  = 0;
    int32_t     bestScore = -1;
    DisplayT    bestDisplay;

    memset(&bestDisplay, 0, sizeof(bestDisplay));

    // Get VBE controller info  -- the transfer buffer (__tb) is the DJGPP-
    // provided region in conventional memory that real-mode BIOS calls
    // can read/write. We split it into seg:off for the INT 10h call.
    uint32_t infoSeg = __tb >> 4;
    uint32_t infoOff = __tb & 0x0F;

    // Writing "VBE2" tells the BIOS we want VBE 2.0+ extended info.
    // Without this, we'd get VBE 1.x info which lacks LFB addresses.
    _farpokeb(_dos_ds, __tb + 0, 'V');
    _farpokeb(_dos_ds, __tb + 1, 'B');
    _farpokeb(_dos_ds, __tb + 2, 'E');
    _farpokeb(_dos_ds, __tb + 3, '2');

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x4F00;
    r.x.es = infoSeg;
    r.x.di = infoOff;
    __dpmi_int(0x10, &r);

    // VBE functions return 0x004F in AX on success. Any other value
    // means the function failed or isn't supported.
    if (r.x.ax != 0x004F) {
        fprintf(stderr, "VBE: Function 0x4F00 failed (AX=0x%04X)\n", r.x.ax);
        return -1;
    }

    // On success the BIOS overwrites "VBE2" with "VESA" in the buffer
    char sig[5];
    for (int32_t i = 0; i < 4; i++) {
        sig[i] = _farpeekb(_dos_ds, __tb + i);
    }
    sig[4] = '\0';

    if (strcmp(sig, "VESA") != 0) {
        fprintf(stderr, "VBE: Bad signature '%s'\n", sig);
        return -1;
    }

    // VBE 2.0+ is required for LFB (Linear Frame Buffer) support.
    // VBE 1.x only supports bank switching, which we explicitly don't
    // implement  -- the complexity isn't worth it for 486+ targets.
    uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4);
    if (vbeVersion < 0x0200) {
        fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n",
                vbeVersion >> 8, vbeVersion & 0xFF);
        return -1;
    }

    // The mode list is a far pointer (seg:off) at offset 14 in the info
    // block. It points to a null-terminated (0xFFFF) array of mode numbers
    // in conventional memory.  Copy to a local buffer first because
    // getModeInfo calls 4F01h which overwrites __tb, and some BIOSes
    // store the mode list inside the __tb region.
    uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14);
    uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16);
    uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff;

    uint16_t modes[MAX_VBE_MODES];
    int32_t  modeCount = 0;

    for (int32_t i = 0; i < MAX_VBE_MODES; i++) {
        uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2);

        if (mode == 0xFFFF) {
            break;
        }

        modes[modeCount++] = mode;
    }

    for (int32_t i = 0; i < modeCount; i++) {
        DisplayT  candidate;
        int32_t   score = 0;

        memset(&candidate, 0, sizeof(candidate));
        getModeInfo(modes[i], &candidate, &score, requestedW, requestedH, preferredBpp);

        if (score > bestScore) {
            bestScore   = score;
            bestMode    = modes[i];
            bestDisplay = candidate;
        }
    }

    if (bestScore < 0) {
        fprintf(stderr, "VBE: No suitable mode found for %ldx%ld\n", (long)requestedW, (long)requestedH);
        return -1;
    }

    *outMode = bestMode;
    *d       = bestDisplay;
    return 0;
}


// Queries VBE mode info (function 0x4F01) for a single mode and
// scores it against the requested parameters. The scoring algorithm:
//
//   Base score by bpp:  16-bit=100, 15-bit=90, 32-bit=85, 8-bit=70
//   +20 if bpp matches preferredBpp
//   +10 if exact resolution match, -10 if oversize
//   -1 (rejected) if mode lacks LFB, is text-mode, is below requested
//     resolution, or uses an unsupported bpp (e.g. 24-bit)
//
// 16-bit is preferred over 32-bit because it's twice as fast for
// span fill/copy on a 486/Pentium bus (half the bytes). 15-bit scores
// slightly below 16-bit because some VESA BIOSes report 15bpp modes
// as 16bpp with a dead high bit, causing confusion. 8-bit scores
// lowest because palette management adds complexity.
//
// 24-bit is explicitly rejected (not 8/15/16/32) because its 3-byte
// pixels can't use dword-aligned rep stosl fills without masking.
//
// The physical LFB address is temporarily stored in d->lfb as a raw
// integer cast  -- it will be properly mapped via DPMI in mapLfb() later.

static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
    __dpmi_regs r;

    *score = -1;

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x4F01;
    r.x.cx = mode;
    r.x.es = __tb >> 4;
    r.x.di = __tb & 0x0F;
    __dpmi_int(0x10, &r);

    if (r.x.ax != 0x004F) {
        return;
    }

    // VBE mode attribute word at offset 0:
    //   bit 7 = LFB available, bit 4 = graphics mode (not text)
    // Both are required  -- we never bank-switch and never want text modes.
    uint16_t attr = _farpeekw(_dos_ds, __tb + 0);

    if (!(attr & 0x0080)) {
        return;
    }

    if (!(attr & 0x0010)) {
        return;
    }

    int32_t  w        = _farpeekw(_dos_ds, __tb + 18);
    int32_t  h        = _farpeekw(_dos_ds, __tb + 20);
    int32_t  bpp      = _farpeekb(_dos_ds, __tb + 25);
    int32_t  pitch    = _farpeekw(_dos_ds, __tb + 16);
    uint32_t physAddr = _farpeekl(_dos_ds, __tb + 40);

    // Must match or exceed requested resolution
    if (w < requestedW || h < requestedH) {
        return;
    }

    // Must be a supported bpp
    if (bpp != 8 && bpp != 15 && bpp != 16 && bpp != 32) {
        return;
    }

    // Score this mode
    int32_t s = 0;

    if (bpp == 16) {
        s = MODE_SCORE_16BPP;
    } else if (bpp == 15) {
        s = MODE_SCORE_15BPP;
    } else if (bpp == 32) {
        s = MODE_SCORE_32BPP;
    } else if (bpp == 8) {
        s = MODE_SCORE_8BPP;
    }

    // Prefer the user's preferred bpp
    if (bpp == preferredBpp) {
        s += MODE_SCORE_PREF_BPP;
    }

    // Exact resolution match is preferred
    if (w == requestedW && h == requestedH) {
        s += MODE_SCORE_EXACT_RES;
    } else {
        s -= MODE_SCORE_EXACT_RES;
    }

    *score = s;

    // Fill in display info
    d->width  = w;
    d->height = h;
    d->pitch  = pitch;

    d->format.bitsPerPixel  = bpp;
    d->format.bytesPerPixel = (bpp + 7) / 8;

    // Read the channel mask layout from the VBE mode info block.
    // These offsets (31-36) define the bit position and size of each
    // color channel. This is essential because the channel layout
    // varies: some cards use RGB565, others BGR565, etc.
    if (bpp >= 15) {
        int32_t redSize   = _farpeekb(_dos_ds, __tb + 31);
        int32_t redPos    = _farpeekb(_dos_ds, __tb + 32);
        int32_t greenSize = _farpeekb(_dos_ds, __tb + 33);
        int32_t greenPos  = _farpeekb(_dos_ds, __tb + 34);
        int32_t blueSize  = _farpeekb(_dos_ds, __tb + 35);
        int32_t bluePos   = _farpeekb(_dos_ds, __tb + 36);

        d->format.redBits    = redSize;
        d->format.redShift   = redPos;
        d->format.redMask    = ((1U << redSize) - 1) << redPos;
        d->format.greenBits  = greenSize;
        d->format.greenShift = greenPos;
        d->format.greenMask  = ((1U << greenSize) - 1) << greenPos;
        d->format.blueBits   = blueSize;
        d->format.blueShift  = bluePos;
        d->format.blueMask   = ((1U << blueSize) - 1) << bluePos;
    }

    // Store physical address in lfb field temporarily (will be remapped)
    d->lfb = (uint8_t *)(uintptr_t)physAddr;
}


// Check if the CPUID instruction is available.
// The CPUID instruction exists if bit 21 (ID flag) of EFLAGS can be
// toggled.  On a 386 this bit is hardwired to 0.  On a 486 without
// CPUID it's also hardwired.  Only if we can flip it does CPUID exist.

static bool hasCpuid(void) {
    uint32_t before;
    uint32_t after;

    __asm__ __volatile__ (
        "pushfl\n\t"
        "popl  %%eax\n\t"
        "movl  %%eax, %0\n\t"
        "xorl  $0x200000, %%eax\n\t"
        "pushl %%eax\n\t"
        "popfl\n\t"
        "pushfl\n\t"
        "popl  %%eax\n\t"
        "movl  %%eax, %1\n\t"
        : "=r"(before), "=r"(after)
        :
        : "eax"
    );

    __asm__ __volatile__ (
        "pushl %0\n\t"
        "popfl"
        :
        : "r"(before)
    );

    return (before ^ after) & 0x200000;
}


// INT 9 handler: reads port 0x60 and queues break codes.
// DJGPP chains to the original handler automatically when using
// _go32_dpmi_chain_protected_mode_interrupt_vector.

static void int9Handler(void) {
    uint8_t scan = inportb(0x60);

    // Pause/Break emits a fixed E1 1D 45 E1 9D C5 burst with no real break
    // code.  Swallow the two scancodes following each 0xE1 prefix so 0x9D /
    // 0xC5 are never queued as phantom key-ups.
    if (sKeyUp.e1Pending > 0) {
        sKeyUp.e1Pending--;
        return;
    }

    if (scan == PS2_SCAN_PREFIX_E1) {
        sKeyUp.e1Pending = PS2_E1_TAIL_BYTES;
        return;
    }

    // 0xE0 marks the following byte as an extended-key scancode.
    if (scan == PS2_SCAN_PREFIX_E0) {
        sKeyUp.scanPrefix = PS2_SCAN_PREFIX_E0;
        return;
    }

    bool extended = (sKeyUp.scanPrefix == PS2_SCAN_PREFIX_E0);
    sKeyUp.scanPrefix = 0;

    // Drop the keyboard's fake-shift releases that bracket the grey keys; a
    // standalone 0xAA / 0xB6 (a genuine Shift release) still falls through.
    if (extended && (scan == PS2_SCAN_FAKE_SHIFT_LE || scan == PS2_SCAN_FAKE_SHIFT_RE)) {
        return;
    }

    // Break code: bit 7 set.
    if (scan & 0x80) {
        int32_t next = (sKeyUp.head + 1) % KEYUP_BUF_SIZE;

        if (next != sKeyUp.tail) {
            sKeyUp.buf[sKeyUp.head].scancode = scan & 0x7F;
            sKeyUp.buf[sKeyUp.head].ascii    = 0;

            sKeyUp.head = next;
        }
    }

    // Original handler is called automatically by DJGPP's chain wrapper
}


// Maps the video card's physical LFB address into the DPMI linear
// address space, then converts it to a near pointer for direct C
// access.
//
// The mapping process has three steps:
//   1. __dpmi_physical_address_mapping()  -- asks the DPMI host to
//      create a linear address mapping for the physical framebuffer.
//      This is necessary because DPMI runs in protected mode with
//      paging; physical addresses aren't directly accessible.
//   2. __dpmi_lock_linear_region()  -- pins the mapped pages so they
//      can't be swapped out. The LFB is memory-mapped I/O to the
//      video card; paging it would be catastrophic.
//   3. __djgpp_nearptr_enable()  -- disables DJGPP's default segment
//      limit checking so we can use plain C pointers to access the
//      LFB address. Without this, all LFB access would require far
//      pointer calls (_farpokeb etc.), which are much slower because
//      each one involves a segment register load.
//
// Why near pointers: the performance difference is dramatic.
// platformFlushRect() copies thousands of dwords per frame using
// rep movsl  -- this only works with near pointers. Far pointer access
// would add ~10 cycles per byte and make 60fps impossible on a 486.
//
// The final pointer calculation adds __djgpp_conventional_base, which
// is the offset DJGPP applies to convert linear addresses to near
// pointer addresses (compensating for the DS segment base).

static int32_t mapLfb(DisplayT *d, uint32_t physAddr) {
    __dpmi_meminfo info;
    uint32_t       fbSize = (uint32_t)d->pitch * (uint32_t)d->height;

    info.address = physAddr;
    info.size    = fbSize;

    if (__dpmi_physical_address_mapping(&info) != 0) {
        fprintf(stderr, "VBE: Failed to map LFB at 0x%08lX\n", (unsigned long)physAddr);
        return -1;
    }

    __dpmi_meminfo lockInfo;
    lockInfo.address = info.address;
    lockInfo.size    = fbSize;

    if (__dpmi_lock_linear_region(&lockInfo) != 0) {
        fprintf(stderr, "VBE: Failed to lock LFB linear region\n");
        __dpmi_free_physical_address_mapping(&info);
        return -1;
    }

    if (__djgpp_nearptr_enable() == 0) {
        fprintf(stderr, "VBE: Failed to enable near pointers\n");
        __dpmi_unlock_linear_region(&lockInfo);
        __dpmi_free_physical_address_mapping(&info);
        return -1;
    }

    // Save the RAW linear address (not d->lfb) so we can later unlock/free it.
    sLfbLinearAddr = info.address;
    sLfbMappedSize = fbSize;

    // Convert linear address to near pointer by adding the DS base offset
    d->lfb = (uint8_t *)(info.address + __djgpp_conventional_base);

    return 0;
}


char platformAltScanToChar(int32_t scancode) {
    if (scancode < 0 || scancode > 255) {
        return 0;
    }

    return sAltScanToAscii[scancode];
}


// Signal handler for fatal exceptions.
// DJGPP uses System V signal semantics where the handler is reset to
// SIG_DFL after each delivery, so we must re-install before doing
// anything else.  After logging platform-specific diagnostics and
// storing the signal number, we longjmp back to the caller's recovery
// point (typically the shell's main loop).

static void platformCrashHandler(int sig) {
    if (sPlatformLogFn) {
        platformLogCrashDetail(sig, sPlatformLogFn);
    }

    signal(sig, platformCrashHandler);

    if (sPlatformCrashSignal) {
        *sPlatformCrashSignal = sig;
    }

    if (sPlatformCrashJmp) {
        longjmp(*sPlatformCrashJmp, 1);
    }
}


// Copies a dirty rectangle from the system RAM backbuffer to the LFB.
// This is the critical path for display updates  -- the compositor calls
// it once per dirty rect per frame.
//
// Two code paths:
//   1. Full-width: if the rect spans the entire scanline (rowBytes ==
//      pitch), collapse all rows into a single large rep movsl. This
//      avoids per-row loop overhead and is the common case for full-
//      screen redraws.
//   2. Partial-width: copy each scanline individually with rep movsl,
//      advancing src/dst by pitch (not rowBytes) between rows.
//
// rep movsl is used instead of memcpy() because on 486/Pentium, GCC's
// memcpy may not generate the optimal dword-aligned string move, and
// the DJGPP C library's memcpy isn't always tuned for large copies.
// The explicit asm guarantees exactly the instruction sequence we want.
//
// __builtin_expect hints tell GCC to generate branch-free fast paths
// for the common cases (non-zero w/h, no trailing bytes).

void platformFlushRect(const DisplayT *d, const RectT *r) {
    int32_t bpp = d->format.bytesPerPixel;

    int32_t x = r->x;
    int32_t y = r->y;
    int32_t w = r->w;
    int32_t h = r->h;

    if (__builtin_expect(w <= 0 || h <= 0, 0)) {
        return;
    }

    int32_t  rowBytes = w * bpp;
    int32_t  pitch    = d->pitch;
    uint8_t *src      = d->backBuf + y * pitch + x * bpp;
    uint8_t *dst      = d->lfb + y * pitch + x * bpp;

    // Full-width flush: single large copy
    if (rowBytes == pitch) {
        int32_t totalBytes = pitch * h;
        int32_t dwords     = totalBytes >> 2;
        int32_t remainder  = totalBytes & 3;
        __asm__ __volatile__ (
            "rep movsl"
            : "+D"(dst), "+S"(src), "+c"(dwords)
            :
            : "memory"
        );
        while (remainder-- > 0) {
            *dst++ = *src++;
        }
    } else {
        // Partial scanlines  -- copy row by row with rep movsd
        int32_t dwords    = rowBytes >> 2;
        int32_t remainder = rowBytes & 3;
        for (int32_t i = 0; i < h; i++) {
            int32_t  dc = dwords;
            uint8_t *s  = src;
            uint8_t *dd = dst;
            __asm__ __volatile__ (
                "rep movsl"
                : "+D"(dd), "+S"(s), "+c"(dc)
                :
                : "memory"
            );
            if (__builtin_expect(remainder > 0, 0)) {
                int32_t rem = remainder;
                while (rem-- > 0) {
                    *dd++ = *s++;
                }
            }
            src += pitch;
            dst += pitch;
        }
    }
}


bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb) {
    __dpmi_free_mem_info memInfo;

    if (__dpmi_get_free_memory_information(&memInfo) != 0) {
        *totalKb = 0;
        *freeKb  = 0;
        return false;
    }

    *totalKb = (memInfo.total_number_of_physical_pages != 0xFFFFFFFFUL)
               ? memInfo.total_number_of_physical_pages * 4 : 0;
    *freeKb  = (memInfo.total_number_of_free_pages != 0xFFFFFFFFUL)
               ? memInfo.total_number_of_free_pages * 4 : 0;
    return true;
}


// Gathers all available hardware information and formats it as a
// human-readable text string.  Each section is separated by a blank
// line and headed with === Section ===.

const char *platformGetSystemInfo(const DisplayT *display) {
    __dpmi_regs r;

    sSysInfoPos    = 0;
    sSysInfoBuf[0] = '\0';

    sysInfoAppend("DVX System Information");
    sysInfoAppend("");

    // ---- CPU ----
    sysInfoAppend("=== CPU ===");

    // Get CPU type from DPMI (works even without CPUID)
    {
        __dpmi_version_ret dpmiVer;

        if (__dpmi_get_version(&dpmiVer) == 0) {
            sysInfoAppend("CPU Type: %d86", dpmiVer.cpu);
        }
    }

    if (!hasCpuid()) {
        sysInfoAppend("(no CPUID support)");
    } else {
        // Vendor string (CPUID leaf 0)
        uint32_t maxFunc;
        uint32_t ebx;
        uint32_t ecx;
        uint32_t edx;
        char vendor[13];

        __asm__ __volatile__ (
            "cpuid"
            : "=a"(maxFunc), "=b"(ebx), "=c"(ecx), "=d"(edx)
            : "a"(0)
        );

        memcpy(vendor + 0, &ebx, 4);
        memcpy(vendor + 4, &edx, 4);
        memcpy(vendor + 8, &ecx, 4);
        vendor[12] = '\0';

        sysInfoAppend("CPU Vendor: %s", vendor);

        // Family/model/stepping/features (CPUID leaf 1)
        if (maxFunc >= 1) {
            uint32_t eax;
            uint32_t features;

            __asm__ __volatile__ (
                "cpuid"
                : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(features)
                : "a"(1)
            );

            int32_t stepping = eax & 0x0F;
            int32_t model    = (eax >> 4) & 0x0F;
            int32_t family   = (eax >> 8) & 0x0F;

            if (family == 0x0F) {
                family += (eax >> 20) & 0xFF;
                model  += ((eax >> 16) & 0x0F) << 4;
            } else if (family == 6) {
                model += ((eax >> 16) & 0x0F) << 4;
            }

            sysInfoAppend("Family: %ld  Model: %ld  Stepping: %ld", (long)family, (long)model, (long)stepping);

            // Feature flags
            bool hasFpu  = (features & (1U << 0)) != 0;
            bool hasTsc  = (features & (1U << 4)) != 0;
            bool hasMmx  = (features & (1U << 23)) != 0;
            bool hasSse  = (features & (1U << 25)) != 0;
            bool hasSse2 = (features & (1U << 26)) != 0;

            char featureStr[128];
            int32_t fpos = 0;

            featureStr[0] = '\0';

            if (hasFpu) {
                fpos += snprintf(featureStr + fpos, sizeof(featureStr) - fpos, "FPU ");
            }
            if (hasTsc) {
                fpos += snprintf(featureStr + fpos, sizeof(featureStr) - fpos, "TSC ");
            }
            if (hasMmx) {
                fpos += snprintf(featureStr + fpos, sizeof(featureStr) - fpos, "MMX ");
            }
            if (hasSse) {
                fpos += snprintf(featureStr + fpos, sizeof(featureStr) - fpos, "SSE ");
            }
            if (hasSse2) {
                fpos += snprintf(featureStr + fpos, sizeof(featureStr) - fpos, "SSE2 ");
            }
            if (fpos > 0) {
                featureStr[fpos - 1] = '\0';
            }

            sysInfoAppend("Features: %s", featureStr[0] ? featureStr : "(none)");

            // Clock speed via RDTSC (Pentium+ only)
            if (hasTsc) {
                uint32_t mhz = estimateClockMhz();

                if (mhz > 0) {
                    sysInfoAppend("Clock: ~%lu MHz", (unsigned long)mhz);
                }
            }
        }

        // Brand string (CPUID extended leaves 0x80000002-0x80000004)
        uint32_t maxExtFunc;

        __asm__ __volatile__ (
            "cpuid"
            : "=a"(maxExtFunc), "=b"(ebx), "=c"(ecx), "=d"(edx)
            : "a"(0x80000000U)
        );

        if (maxExtFunc >= 0x80000004U) {
            char brand[49];
            uint32_t *b = (uint32_t *)brand;

            for (uint32_t func = 0x80000002U; func <= 0x80000004U; func++) {
                __asm__ __volatile__ (
                    "cpuid"
                    : "=a"(b[0]), "=b"(b[1]), "=c"(b[2]), "=d"(b[3])
                    : "a"(func)
                );

                b += 4;
            }

            brand[48] = '\0';

            const char *p = brand;

            while (*p == ' ') {
                p++;
            }

            if (*p) {
                sysInfoAppend("Brand: %s", p);
            }
        }
    }

    // ---- Memory ----
    sysInfoAppend("");
    sysInfoAppend("=== Memory ===");

    __dpmi_free_mem_info memInfo;

    if (__dpmi_get_free_memory_information(&memInfo) == 0) {
        if (memInfo.largest_available_free_block_in_bytes != 0xFFFFFFFFUL) {
            uint32_t largestKb = memInfo.largest_available_free_block_in_bytes / 1024;
            sysInfoAppend("Largest free block: %lu KB (%lu MB)", (unsigned long)largestKb, (unsigned long)(largestKb / 1024));
        }
        if (memInfo.total_number_of_physical_pages != 0xFFFFFFFFUL) {
            uint32_t totalKb = memInfo.total_number_of_physical_pages * 4;
            sysInfoAppend("Total physical: %lu KB (%lu MB)", (unsigned long)totalKb, (unsigned long)(totalKb / 1024));
        }
        if (memInfo.total_number_of_free_pages != 0xFFFFFFFFUL) {
            uint32_t freeKb = memInfo.total_number_of_free_pages * 4;
            sysInfoAppend("Free physical: %lu KB (%lu MB)", (unsigned long)freeKb, (unsigned long)(freeKb / 1024));
        }
        if (memInfo.linear_address_space_size_in_pages != 0xFFFFFFFFUL) {
            uint32_t linearKb = memInfo.linear_address_space_size_in_pages * 4;
            sysInfoAppend("Linear address space: %lu KB (%lu MB)", (unsigned long)linearKb, (unsigned long)(linearKb / 1024));
        }
        if (memInfo.size_of_paging_file_partition_in_pages != 0xFFFFFFFFUL) {
            uint32_t pagingKb = memInfo.size_of_paging_file_partition_in_pages * 4;
            sysInfoAppend("Paging file: %lu KB (%lu MB)", (unsigned long)pagingKb, (unsigned long)(pagingKb / 1024));
        }
        if (memInfo.free_linear_address_space_in_pages != 0xFFFFFFFFUL) {
            uint32_t freeLinearKb = memInfo.free_linear_address_space_in_pages * 4;
            sysInfoAppend("Free linear space: %lu KB", (unsigned long)freeLinearKb);
        }
    } else {
        sysInfoAppend("DPMI memory info unavailable");
    }

    // ---- DOS / DPMI ----
    sysInfoAppend("");
    sysInfoAppend("=== DOS ===");

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x3000;
    __dpmi_int(0x21, &r);
    sysInfoAppend("DOS Version: %ld.%02ld", (long)(r.x.ax & 0xFF), (long)((r.x.ax >> 8) & 0xFF));

    __dpmi_version_ret ver;

    if (__dpmi_get_version(&ver) == 0) {
        sysInfoAppend("DPMI Version: %d.%02d", ver.major, ver.minor);
        sysInfoAppend("DPMI Flags: %s%s%s",
            (ver.flags & 0x01) ? "32-bit " : "16-bit ",
            (ver.flags & 0x02) ? "V86 " : "",
            (ver.flags & 0x04) ? "VirtMem " : "");
    }

    const char *tmpDir = getenv("TEMP");

    if (!tmpDir) {
        tmpDir = getenv("TMP");
    }

    sysInfoAppend("Temp Path: %s", (tmpDir && tmpDir[0]) ? tmpDir : "(current directory)");

    // ---- Video ----
    sysInfoAppend("");
    sysInfoAppend("=== Video ===");

    // VBE controller info for version, video RAM, OEM string
    _farpokeb(_dos_ds, __tb + 0, 'V');
    _farpokeb(_dos_ds, __tb + 1, 'B');
    _farpokeb(_dos_ds, __tb + 2, 'E');
    _farpokeb(_dos_ds, __tb + 3, '2');

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x4F00;
    r.x.es = __tb >> 4;
    r.x.di = __tb & 0x0F;
    __dpmi_int(0x10, &r);

    if (r.x.ax == 0x004F) {
        uint16_t vbeVersion  = _farpeekw(_dos_ds, __tb + 4);
        uint16_t totalMem64k = _farpeekw(_dos_ds, __tb + 18);

        sysInfoAppend("VBE Version: %d.%d", vbeVersion >> 8, vbeVersion & 0xFF);
        sysInfoAppend("Video memory: %lu KB", (unsigned long)(totalMem64k * 64));

        // OEM string (real-mode far pointer at offset 6)
        uint16_t oemOff = _farpeekw(_dos_ds, __tb + 6);
        uint16_t oemSeg = _farpeekw(_dos_ds, __tb + 8);
        uint32_t oemAddr = ((uint32_t)oemSeg << 4) + oemOff;

        char oemStr[80];
        int32_t oemLen = 0;

        while (oemLen < 79) {
            char c = _farpeekb(_dos_ds, oemAddr + oemLen);

            if (c == 0) {
                break;
            }

            oemStr[oemLen++] = c;
        }

        oemStr[oemLen] = '\0';

        if (oemLen > 0) {
            sysInfoAppend("VBE OEM: %s", oemStr);
        }
    }

    if (display) {
        sysInfoAppend("Resolution: %ldx%ld", (long)display->width, (long)display->height);
        sysInfoAppend("Color depth: %ld bpp", (long)display->format.bitsPerPixel);
        sysInfoAppend("Pitch: %ld bytes", (long)display->pitch);

        uint32_t fbSize = (uint32_t)display->pitch * (uint32_t)display->height;
        sysInfoAppend("Framebuffer: %lu KB", (unsigned long)(fbSize / 1024));

        if (display->format.bitsPerPixel >= 15) {
            sysInfoAppend("Red: %ld bits @ bit %ld", (long)display->format.redBits, (long)display->format.redShift);
            sysInfoAppend("Green: %ld bits @ bit %ld", (long)display->format.greenBits, (long)display->format.greenShift);
            sysInfoAppend("Blue: %ld bits @ bit %ld", (long)display->format.blueBits, (long)display->format.blueShift);
        }
    }

    // ---- Mouse ----
    sysInfoAppend("");
    sysInfoAppend("=== Mouse ===");

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0000;
    __dpmi_int(0x33, &r);

    if (r.x.ax == 0xFFFF) {
        sysInfoAppend("Mouse: Detected (%ld buttons)", (long)r.x.bx);
    } else {
        sysInfoAppend("Mouse: Not detected");
    }

    sysInfoAppend("Wheel: %s", sHasMouseWheel ? "Yes (CuteMouse Wheel API)" : "No");

    // ---- Disk Drives ----
    sysInfoAppend("");
    sysInfoAppend("=== Disk Drives ===");

    for (int32_t drv = FIRST_FIXED_DRIVE; drv <= MAX_DRIVE_NUMBER; drv++) {
        // INT 21h AH=36h: Get disk free space
        memset(&r, 0, sizeof(r));
        r.x.ax = 0x3600;
        r.x.dx = drv;
        __dpmi_int(0x21, &r);

        if (r.x.ax == 0xFFFF) {
            continue;
        }

        uint32_t sectPerClust  = r.x.ax;
        uint32_t freeClusters  = r.x.bx;
        uint32_t bytesPerSect  = r.x.cx;
        uint32_t totalClusters = r.x.dx;

        uint32_t clusterSize = sectPerClust * bytesPerSect;
        uint32_t totalMb     = (uint32_t)((uint64_t)totalClusters * clusterSize / (1024 * 1024));
        uint32_t freeMb      = (uint32_t)((uint64_t)freeClusters * clusterSize / (1024 * 1024));

        // INT 21h AX=4408h: Check if drive is removable
        memset(&r, 0, sizeof(r));
        r.x.ax = 0x4408;
        r.x.bx = drv;
        __dpmi_int(0x21, &r);

        const char *driveType = "Unknown";

        if (!(r.x.flags & 0x01)) {
            driveType = (r.x.ax == 0) ? "Removable" : "Fixed";
        }

        char letter = 'A' + (drv - 1);
        sysInfoAppend("%c: %s  %lu MB total  %lu MB free", letter, driveType, (unsigned long)totalMb, (unsigned long)freeMb);
    }

    return sSysInfoBuf;
}


void platformInit(void) {
    // Disable Ctrl+C/Break at every level so the user can't
    // accidentally kill the GUI while in graphics mode.

    // 1. DJGPP-level: disable Ctrl+C detection in the runtime
    __djgpp_set_ctrl_c(0);

    // 2. C-level: ignore SIGINT if it somehow fires
    signal(SIGINT, SIG_IGN);

    // 3. DOS-level: disable break checking during INT 21h calls
    //    (function 33h, subfunction 01h, DL=0 = break off)
    __dpmi_regs r;
    memset(&r, 0, sizeof(r));
    r.x.ax = 0x3301;
    r.x.dx = 0x0000;
    __dpmi_int(0x21, &r);
}


void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn) {
    sPlatformCrashJmp    = recoveryBuf;
    sPlatformCrashSignal = crashSignal;
    sPlatformLogFn       = logFn;

    signal(SIGSEGV, platformCrashHandler);
    signal(SIGFPE,  platformCrashHandler);
    signal(SIGILL,  platformCrashHandler);
}


// Returns the current modifier key state via INT 16h function 12h
// (enhanced get extended shift flags). The low byte contains:
//   bit 0 = right shift, bit 1 = left shift
//   bit 2 = ctrl, bit 3 = alt
// The widget system uses these bits for keyboard accelerators
// (Alt+key) and text editing shortcuts (Ctrl+C/V/X).

int32_t platformKeyboardGetModifiers(void) {
    __dpmi_regs r;

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x1200;
    __dpmi_int(0x16, &r);

    return r.x.ax & 0xFF;
}


// Non-blocking keyboard read using enhanced INT 16h functions.
//
// Uses the "enhanced" functions (10h/11h) rather than the original
// (00h/01h) because the originals can't distinguish between grey
// and numpad arrow keys, and they don't report F11/F12. The enhanced
// functions have been standard since AT-class machines (1984).
//
// The two-step peek-then-read is necessary because function 10h
// (read key) blocks until a key is available  -- there's no non-blocking
// read in the BIOS API. Function 11h (check key) peeks without
// consuming, letting us poll without blocking the event loop.

bool platformKeyboardRead(PlatformKeyEventT *evt) {
    __dpmi_regs r;

    // Peek: function 11h sets ZF if buffer is empty
    r.x.ax = 0x1100;
    __dpmi_int(0x16, &r);

    // Test the Zero Flag (bit 6 of the flags register)
    if (r.x.flags & 0x40) {
        return false;
    }

    // Consume: function 10h removes the key from the BIOS buffer.
    // AH = scan code, AL = ASCII character (0 for extended keys).
    r.x.ax = 0x1000;
    __dpmi_int(0x16, &r);

    evt->scancode = (r.x.ax >> 8) & 0xFF;
    evt->ascii    = r.x.ax & 0xFF;

    // Enhanced INT 16h uses 0xE0 as the ASCII byte for grey/extended
    // keys (arrows, Home, End, Insert, Delete on 101-key keyboards).
    // Normalize to 0 so the rest of the codebase can use a single
    // "ascii == 0 means extended key, check scancode" convention.
    if (evt->ascii == 0xE0) {
        evt->ascii = 0;
    }

    return true;
}


void platformKeyUpInit(void) {
    unsigned long  codeAddr;
    unsigned long  dataAddr;
    __dpmi_meminfo codeRegion;
    __dpmi_meminfo dataRegion;

    if (sKeyUpInstalled) {
        return;
    }

    // The INT 9 handler runs at hardware-interrupt time, so its code and the
    // ring-buffer state it touches must be locked into physical memory.  A
    // page fault inside the ISR under a paging DPMI host (e.g. CWSDPMI) would
    // be fatal.  On lock failure we degrade gracefully: key-up detection is
    // simply unavailable rather than risking a crash.
    if (__dpmi_get_segment_base_address(_my_cs(), &codeAddr) != 0 ||
        __dpmi_get_segment_base_address(_my_ds(), &dataAddr) != 0) {
        fprintf(stderr, "KEYUP: Failed to read segment base addresses\n");
        return;
    }

    codeRegion.handle  = 0;
    codeRegion.size    = KEYUP_ISR_SIZE;
    codeRegion.address = codeAddr + (unsigned long)int9Handler;
    dataRegion.handle  = 0;
    dataRegion.size    = sizeof(sKeyUp);
    dataRegion.address = dataAddr + (unsigned long)&sKeyUp;

    if (__dpmi_lock_linear_region(&codeRegion) != 0) {
        fprintf(stderr, "KEYUP: Failed to lock handler code\n");
        return;
    }

    if (__dpmi_lock_linear_region(&dataRegion) != 0) {
        fprintf(stderr, "KEYUP: Failed to lock ISR state\n");
        __dpmi_unlock_linear_region(&codeRegion);
        return;
    }

    _go32_dpmi_get_protected_mode_interrupt_vector(9, &sOldInt9);

    sNewInt9.pm_offset   = (unsigned long)int9Handler;
    sNewInt9.pm_selector = _go32_my_cs();

    _go32_dpmi_chain_protected_mode_interrupt_vector(9, &sNewInt9);

    sKeyUpInstalled = true;
}


bool platformKeyUpRead(PlatformKeyEventT *evt) {
    if (sKeyUp.tail == sKeyUp.head) {
        return false;
    }

    *evt = sKeyUp.buf[sKeyUp.tail];
    sKeyUp.tail = (sKeyUp.tail + 1) % KEYUP_BUF_SIZE;
    return true;
}


void platformKeyUpShutdown(void) {
    unsigned long  codeAddr;
    unsigned long  dataAddr;
    __dpmi_meminfo region;

    if (!sKeyUpInstalled) {
        return;
    }

    _go32_dpmi_set_protected_mode_interrupt_vector(9, &sOldInt9);
    sKeyUpInstalled = false;

    // Unlock the regions pinned in platformKeyUpInit, after the IRQ is no
    // longer pointed at our handler.
    if (__dpmi_get_segment_base_address(_my_ds(), &dataAddr) == 0) {
        region.handle  = 0;
        region.size    = sizeof(sKeyUp);
        region.address = dataAddr + (unsigned long)&sKeyUp;
        __dpmi_unlock_linear_region(&region);
    }

    if (__dpmi_get_segment_base_address(_my_cs(), &codeAddr) == 0) {
        region.handle  = 0;
        region.size    = KEYUP_ISR_SIZE;
        region.address = codeAddr + (unsigned long)int9Handler;
        __dpmi_unlock_linear_region(&region);
    }
}


const char *platformLineEnding(void) {
    return "\r\n";
}


// Log signal name and CPU register dump

void platformLogCrashDetail(int sig, PlatformLogFnT logFn) {
    const char *sigName = "UNKNOWN";

    if (sig == SIGSEGV) {
        sigName = "SIGSEGV (page fault)";
    } else if (sig == SIGFPE) {
        sigName = "SIGFPE (floating point exception)";
    } else if (sig == SIGILL) {
        sigName = "SIGILL (illegal instruction)";
    }

    logFn("=== CRASH ===");
    logFn("Signal: %d (%s)", sig, sigName);

    // __djgpp_exception_state_ptr captures the full CPU register state
    // at the point of the exception.  This gives us the faulting EIP,
    // stack pointer, and all GPRs -- invaluable for post-mortem debugging.
    jmp_buf *estate = __djgpp_exception_state_ptr;

    if (estate) {
        struct __jmp_buf *regs = &(*estate)[0];
        logFn("EIP: 0x%08lx  CS: 0x%04x", regs->__eip, regs->__cs);
        logFn("EAX: 0x%08lx  EBX: 0x%08lx  ECX: 0x%08lx  EDX: 0x%08lx", regs->__eax, regs->__ebx, regs->__ecx, regs->__edx);
        logFn("ESI: 0x%08lx  EDI: 0x%08lx  EBP: 0x%08lx  ESP: 0x%08lx", regs->__esi, regs->__edi, regs->__ebp, regs->__esp);
        logFn("DS: 0x%04x  ES: 0x%04x  FS: 0x%04x  GS: 0x%04x  SS: 0x%04x", regs->__ds, regs->__es, regs->__fs, regs->__gs, regs->__ss);
        logFn("EFLAGS: 0x%08lx", regs->__eflags);
    }
}


// Initializes the INT 33h mouse driver. The mouse driver is a TSR
// (or emulated by the DOS environment) that tracks position and
// buttons independently of the application.
//
// Boots in MICKEY mode (relative integration), the only scheme safe on real
// hardware (see the cursor-state block).  We start the cursor at screen center
// and flush the mickey counters so the first poll begins clean.  The latch is
// reset unconditionally here because dvxApp re-calls this on every live video-
// mode change -- a promotion earned in a prior mode must never carry over.
//
// Functions 07h/08h tell the driver our true screen range.  This is NOT
// trusted positioning: it is bait for the runtime detector in platformMousePoll
// -- a driver that honours the range (DOSBox) then reports full-screen absolute
// coordinates, a driver that ignores it (ctmouse/86Box) clamps to a low-res
// trap and the detector never promotes.
//
// The hardware cursor is never shown  -- DVX composites its own software cursor
// on top of the backbuffer. We use INT 33h for button state (function 03h),
// mickey deltas (function 0Bh), and absolute position after promotion (03h).

void platformMouseInit(int32_t screenW, int32_t screenH) {
    __dpmi_regs r;

    sMouseRangeW    = screenW;
    sMouseRangeH    = screenH;
    sCurX           = screenW / 2;
    sCurY           = screenH / 2;
    sMouseMode      = MouseModeMickeyE;
    sPromoteCounter = 0;

    // Function 00h: reset driver, detect mouse hardware
    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0000;
    __dpmi_int(0x33, &r);

    // Function 07h: set horizontal range (bait for the absolute detector).
    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0007;
    r.x.cx = 0;
    r.x.dx = screenW - 1;
    __dpmi_int(0x33, &r);

    // Function 08h: set vertical range (bait for the absolute detector).
    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0008;
    r.x.cx = 0;
    r.x.dx = screenH - 1;
    __dpmi_int(0x33, &r);

    // Flush any stale mickey counters so the first poll starts clean.
    // Function 0Bh returns and resets the accumulated motion counters.
    memset(&r, 0, sizeof(r));
    r.x.ax = 0x000B;
    __dpmi_int(0x33, &r);
}


// Reads button state and wheel via function 03h, then updates the software
// cursor by whichever scheme sMouseMode selects (see the cursor-state block):
// MICKEY integrates function 0Bh relative deltas (real-hardware path), while
// ABSOLUTE consumes the function 03h position directly (DOSBox path).  Starts
// in MICKEY and promotes to ABSOLUTE once the driver proves -- via a function
// 03h position outside the low-res trap box -- that it honours the real range.

void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons) {
    __dpmi_regs r;

    // Function 03h: read button state, wheel, and absolute position in one go.
    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0003;
    __dpmi_int(0x33, &r);

    *buttons = r.x.bx & MOUSE_BUTTON_MASK;

    // BH = signed 8-bit wheel counter (cleared on read by the driver).
    // Accumulate so a second poll before platformMouseWheelPoll reads it
    // doesn't drop the first delta (dvxPlat.h: accumulated between polls).
    if (sHasMouseWheel) {
        sLastWheelDelta += (int32_t)(int8_t)(r.x.bx >> 8);
    }

    int32_t absX = (int16_t)r.x.cx;
    int32_t absY = (int16_t)r.x.dx;

    // While still on mickeys, watch the absolute report for proof the driver
    // honours the high-res range.  A trapped driver (ctmouse/86Box) clamps
    // every read inside the trap box and can never satisfy this; DOSBox does
    // as soon as the pointer leaves the top band.  The per-axis screen-vs-trap
    // guard keeps a genuine low-res mode (where trap and honest are
    // indistinguishable) from ever promoting.
    if (sMouseMode == MouseModeMickeyE) {
        bool outOfTrap = false;

        if (sMouseRangeH > MOUSE_TRAP_BOX_H && absY >= MOUSE_TRAP_BOX_H) {
            outOfTrap = true;
        }

        if (sMouseRangeW > MOUSE_TRAP_BOX_W && absX >= MOUSE_TRAP_BOX_W) {
            outOfTrap = true;
        }

        if (outOfTrap) {
            sPromoteCounter++;
        } else {
            sPromoteCounter = 0;
        }

        if (sPromoteCounter >= MOUSE_PROMOTE_CONSEC) {
            // The absolute branch below seeds sCurX/sCurY from absX/absY this
            // same poll, so the cursor does not teleport from its integrated
            // position.
            sMouseMode = MouseModeAbsoluteE;
        }
    }

    if (sMouseMode == MouseModeAbsoluteE) {
        // Consume the driver position directly.  Do NOT read function 0Bh
        // here: integrating mickeys on top of an absolute snap would double-
        // count motion in DOSBox's uncaptured (emulate=false) path.
        sCurX = absX;
        sCurY = absY;
    } else {
        // Function 0Bh: read mickey motion counters (signed 16-bit deltas,
        // cleared on read).  Accumulate into software cursor position.
        memset(&r, 0, sizeof(r));
        r.x.ax = 0x000B;
        __dpmi_int(0x33, &r);

        sCurX += (int16_t)r.x.cx;
        sCurY += (int16_t)r.x.dx;
    }

    if (sCurX < 0) {
        sCurX = 0;
    }

    if (sCurX >= sMouseRangeW) {
        sCurX = sMouseRangeW - 1;
    }

    if (sCurY < 0) {
        sCurY = 0;
    }

    if (sCurY >= sMouseRangeH) {
        sCurY = sMouseRangeH - 1;
    }

    *mx = sCurX;
    *my = sCurY;
}


// Sets the double-speed threshold via INT 33h function 13h.  When the
// mouse moves faster than this many mickeys per second, cursor movement
// is doubled.  A very high value (e.g. 10000) effectively disables
// acceleration.  Typical driver defaults are around 64 mickeys/sec.

void platformMouseSetAccel(int32_t threshold) {
    __dpmi_regs r;

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0013;
    r.x.dx = threshold;
    __dpmi_int(0x33, &r);
}


// Sets the mickey-to-pixel ratio via INT 33h function 0Fh.
// horizMickeys: number of mickeys per 8 pixels horizontally
// vertMickeys:  number of mickeys per 8 pixels vertically
//
// Higher values = slower cursor (more mickeys needed per pixel).
// Default is typically 8 horizontal, 16 vertical (1:1 and 2:1).

void platformMouseSetMickeys(int32_t horizMickeys, int32_t vertMickeys) {
    __dpmi_regs r;

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x000F;
    r.x.cx = horizMickeys;
    r.x.dx = vertMickeys;
    __dpmi_int(0x33, &r);
}


// Moves the software cursor to an absolute screen position. Used to clamp the
// cursor to window edges during resize so the pointer visually sticks to the
// border.  The driver-side follow-up depends on the tracking mode: in ABSOLUTE
// mode the next poll reads the driver position, so we must move the driver
// cursor too (function 04h) or the warp is immediately undone; in MICKEY mode
// we only flush the pending relative deltas (function 0Bh).

void platformMouseWarp(int32_t x, int32_t y) {
    __dpmi_regs r;

    sCurX = x;
    sCurY = y;

    if (sMouseMode == MouseModeAbsoluteE) {
        // Function 04h: move the driver cursor so the next 03h read agrees.
        memset(&r, 0, sizeof(r));
        r.x.ax = 0x0004;
        r.x.cx = x;
        r.x.dx = y;
        __dpmi_int(0x33, &r);
    } else {
        // Function 0Bh: flush pending mickeys so the next poll doesn't undo
        // the warp by applying stale deltas on top of it.
        memset(&r, 0, sizeof(r));
        r.x.ax = 0x000B;
        __dpmi_int(0x33, &r);
    }
}


// Detects and activates the CuteMouse Wheel API 1.0 (INT 33h AX=0011h).
// The driver returns AX=574Dh ('WM') if supported, with CX bit 0 set if
// a wheel is physically present. Calling this function also switches the
// driver from "wheelkey" mode (faking keypresses) to real wheel reporting
// via function 03h BH. Must be called after platformMouseInit.

bool platformMouseWheelInit(void) {
    __dpmi_regs r;

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0011;
    __dpmi_int(0x33, &r);

    // 0x574D = 'WM' (Wheel Mouse) magic signature
    sHasMouseWheel = (r.x.ax == 0x574D) && (r.x.cx & 0x0001);
    return sHasMouseWheel;
}


// Returns the wheel delta captured by the last platformMousePoll call.
// Positive = scroll down, negative = scroll up. Returns 0 if no wheel
// movement or if the wheel is not supported.

int32_t platformMouseWheelPoll(void) {
    int32_t delta = sLastWheelDelta;

    sLastWheelDelta = 0;
    return delta;
}


void platformRegisterDxeExports(void) {
    dlregsym(sDxeExportTable);
}


// PlatformSymOverrideT has the same layout as dxe_symbol_table
// ({const char *name, void *ptr}), so we cast directly.

void platformRegisterSymOverrides(const PlatformSymOverrideT *entries) {
    dlregsym((const dxe_symbol_table *)entries);
}


// Copies 'count' 16-bit pixels. Since each pixel is 2 bytes, we
// only need to check bit 1 of the address for dword alignment
// (bit 0 is always clear for 16-bit aligned data). A single
// leading pixel copy brings us to a dword boundary, then rep movsl
// copies pixel pairs as dwords.

void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) {
    // Copy one pixel to reach dword alignment if needed
    if (((uintptr_t)dst & 2) && count > 0) {
        *(uint16_t *)dst = *(const uint16_t *)src;
        dst += 2;
        src += 2;
        count--;
    }

    if (count >= 2) {
        int32_t dwordCount = count >> 1;
        __asm__ __volatile__ (
            "rep movsl"
            : "+D"(dst), "+S"(src), "+c"(dwordCount)
            :
            : "memory"
        );
        dst += dwordCount * 4;
        src += dwordCount * 4;
    }

    if (count & 1) {
        *(uint16_t *)dst = *(const uint16_t *)src;
    }
}


// 32-bit pixels are inherently dword-aligned, so no alignment
// preamble is needed  -- straight to rep movsl.

void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) {
    __asm__ __volatile__ (
        "rep movsl"
        : "+D"(dst), "+S"(src), "+c"(count)
        :
        : "memory"
    );
}


// Copies 'count' 8-bit pixels from src to dst using dword-aligned
// rep movsl for the bulk transfer.
//
// All span operations (Copy8/16/32, Fill8/16/32) follow the same
// pattern: align to a dword boundary, do the bulk as rep movsl or
// rep stosl, then handle the remainder. This pattern exists because
// on 486/Pentium, misaligned dword moves incur a 3-cycle penalty per
// access. Aligning first ensures the critical rep loop runs at full
// bus speed.
//
// rep movsl moves 4 bytes per iteration with hardware loop decrement,
// which is faster than a C for-loop  -- the CPU string move pipeline
// optimizes sequential memory access patterns.

void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) {
    // Align dst to a dword boundary with byte copies
    while (((uintptr_t)dst & 3) && count > 0) {
        *dst++ = *src++;
        count--;
    }

    if (count >= 4) {
        int32_t dwordCount = count >> 2;
        __asm__ __volatile__ (
            "rep movsl"
            : "+D"(dst), "+S"(src), "+c"(dwordCount)
            :
            : "memory"
        );
        dst += dwordCount * 4;
        src += dwordCount * 4;
    }

    int32_t rem = count & 3;
    while (rem-- > 0) {
        *dst++ = *src++;
    }
}


// Fills 'count' 16-bit pixels. Two pixels are packed into a dword
// (low half = first pixel, high half = second pixel) so rep stosl
// writes 2 pixels per iteration.

void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count) {
    uint16_t c = (uint16_t)color;

    // Handle odd leading pixel for dword alignment
    if (((uintptr_t)dst & 2) && count > 0) {
        *(uint16_t *)dst = c;
        dst += 2;
        count--;
    }

    // Fill pairs of pixels as 32-bit dwords
    if (count >= 2) {
        uint32_t dword      = ((uint32_t)c << 16) | c;
        int32_t  dwordCount = count >> 1;
        __asm__ __volatile__ (
            "rep stosl"
            : "+D"(dst), "+c"(dwordCount)
            : "a"(dword)
            : "memory"
        );
        dst += dwordCount * 4;
    }

    // Handle trailing odd pixel
    if (count & 1) {
        *(uint16_t *)dst = c;
    }
}


// 32-bit fill is the simplest case  -- each pixel is already a dword,
// so rep stosl writes exactly one pixel per iteration with no
// alignment or packing concerns.

void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count) {
    __asm__ __volatile__ (
        "rep stosl"
        : "+D"(dst), "+c"(count)
        : "a"(color)
        : "memory"
    );
}


// Fills 'count' 8-bit pixels with a single color value.
// The 8-bit value is replicated into all four bytes of a dword so
// that rep stosl writes 4 identical pixels per iteration. This is
// 4x faster than byte-at-a-time for large fills (window backgrounds,
// screen clears).

void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count) {
    uint8_t  c     = (uint8_t)color;
    uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);

    // Align to 4 bytes  -- skip if already aligned
    if (__builtin_expect((uintptr_t)dst & 3, 0)) {
        while (((uintptr_t)dst & 3) && count > 0) {
            *dst++ = c;
            count--;
        }
    }

    if (count >= 4) {
        int32_t dwordCount = count >> 2;
        __asm__ __volatile__ (
            "rep stosl"
            : "+D"(dst), "+c"(dwordCount)
            : "a"(dword)
            : "memory"
        );
        dst += dwordCount * 4;
    }

    int32_t rem = count & 3;
    while (rem-- > 0) {
        *dst++ = c;
    }
}


void platformSplashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color) {
    // Stage one filled scanline once, then block-move (rep movs) the clipped
    // span per row instead of poking each pixel individually.
    uint8_t rowBuf[SPLASH_VGA_W];
    memset(rowBuf, color, SPLASH_VGA_W);

    for (int32_t row = y; row < y + h; row++) {
        if (row < 0 || row >= SPLASH_VGA_H) {
            continue;
        }

        int32_t x0    = x < 0 ? 0 : x;
        int32_t x1    = (x + w) > SPLASH_VGA_W ? SPLASH_VGA_W : (x + w);
        int32_t spanW = x1 - x0;

        if (spanW <= 0) {
            continue;
        }

        dosmemput(rowBuf, spanW, SPLASH_VGA_ADDR + row * SPLASH_VGA_W + x0);
    }
}


void platformSplashInit(void) {
    __dpmi_regs r;
    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0013;
    __dpmi_int(0x10, &r);
}


bool platformSplashLoadRaw(const char *path) {
    FILE *fp = fopen(path, "rb");

    if (!fp) {
        return false;
    }

    // Read and set palette (768 bytes: 256 x RGB, 6-bit VGA values)
    uint8_t pal[PALETTE_BYTES];

    if (fread(pal, 1, PALETTE_BYTES, fp) != PALETTE_BYTES) {
        fclose(fp);
        return false;
    }

    outportb(0x3C8, 0);

    for (int32_t i = 0; i < PALETTE_BYTES; i++) {
        outportb(0x3C9, pal[i]);
    }

    // Read pixels directly into VGA memory (64000 bytes: 320x200)
    uint8_t rowBuf[SPLASH_VGA_W];

    for (int32_t y = 0; y < SPLASH_VGA_H; y++) {
        if (fread(rowBuf, 1, SPLASH_VGA_W, fp) != SPLASH_VGA_W) {
            fclose(fp);
            return false;
        }

        // One block move per scanline (rep movs) instead of 320 byte pokes
        dosmemput(rowBuf, SPLASH_VGA_W, SPLASH_VGA_ADDR + y * SPLASH_VGA_W);
    }

    fclose(fp);
    return true;
}


void platformSplashShutdown(void) {
    __dpmi_regs r;
    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0003;
    __dpmi_int(0x10, &r);
}


// DOS 8.3 filename validation.
//
// Validates that a filename conforms to DOS 8.3 conventions:
//   - Base name: 1-8 chars, extension: 0-3 chars, one dot max
//   - No spaces or special characters that DOS can't handle
//   - Not a reserved device name (CON, PRN, AUX, NUL, COMn, LPTn)
//
// The reserved name check compares the base name only (before the
// dot), case-insensitive, because DOS treats "CON.TXT" the same
// as the CON device  -- the extension is ignored for device names.
//
// Returns NULL on success, or a human-readable error string on failure.
// On non-DOS platforms, this function would be replaced with one that
// validates for that platform's filesystem rules.

const char *platformValidateFilename(const char *name) {
    static const char *reserved[] = {
        "CON", "PRN", "AUX", "NUL",
        "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
        "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
        NULL
    };

    if (!name || name[0] == '\0') {
        return "Filename must not be empty.";
    }

    // Split into base and extension
    const char *dot = strrchr(name, '.');
    int32_t baseLen;
    int32_t extLen;

    if (dot) {
        baseLen = (int32_t)(dot - name);
        extLen  = (int32_t)strlen(dot + 1);
    } else {
        baseLen = (int32_t)strlen(name);
        extLen  = 0;
    }

    if (baseLen < 1 || baseLen > 8) {
        return "Filename must be 1-8 characters before the extension.";
    }

    if (extLen > 3) {
        return "Extension must be 3 characters or fewer.";
    }

    // Check for invalid characters
    for (const char *p = name; *p; p++) {
        if (*p == '.') {
            continue;
        }

        if (*p < '!' || *p > '~') {
            return "Filename contains invalid characters.";
        }

        if (strchr(" \"*+,/:;<=>?[\\]|", *p)) {
            return "Filename contains invalid characters.";
        }
    }

    // Check for multiple dots
    if (dot && strchr(name, '.') != dot) {
        return "Filename may contain only one dot.";
    }

    // Check reserved device names (compare base only, case-insensitive)
    char base[9];
    int32_t copyLen = baseLen < 8 ? baseLen : 8;

    for (int32_t i = 0; i < copyLen; i++) {
        base[i] = toupper((unsigned char)name[i]);
    }

    base[copyLen] = '\0';

    for (const char **r = reserved; *r; r++) {
        if (strcmp(base, *r) == 0) {
            return "That name is a reserved device name.";
        }
    }

    return NULL;
}


void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData) {
    __dpmi_regs r;

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x4F00;
    r.x.es = __tb >> 4;
    r.x.di = __tb & 0x0F;

    // Write "VBE2" signature to request VBE 2.0+ info
    _farpokeb(_dos_ds, __tb + 0, 'V');
    _farpokeb(_dos_ds, __tb + 1, 'B');
    _farpokeb(_dos_ds, __tb + 2, 'E');
    _farpokeb(_dos_ds, __tb + 3, '2');

    __dpmi_int(0x10, &r);

    if (r.x.ax != 0x004F) {
        return;
    }

    uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14);
    uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16);
    uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff;

    // Copy mode numbers to a local buffer BEFORE calling 4F01h.
    // Some VBE BIOSes store the mode list inside the VBE info block
    // at __tb, which 4F01h overwrites when querying mode details.
    uint16_t modes[MAX_VBE_MODES];
    int32_t  modeCount = 0;

    for (int32_t i = 0; i < MAX_VBE_MODES; i++) {
        uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2);

        if (mode == 0xFFFF) {
            break;
        }

        modes[modeCount++] = mode;
    }

    // Now safe to use __tb for 4F01h queries
    for (int32_t i = 0; i < modeCount; i++) {
        memset(&r, 0, sizeof(r));
        r.x.ax = 0x4F01;
        r.x.cx = modes[i];
        r.x.es = __tb >> 4;
        r.x.di = __tb & 0x0F;
        __dpmi_int(0x10, &r);

        if (r.x.ax != 0x004F) {
            continue;
        }

        uint16_t attr = _farpeekw(_dos_ds, __tb + 0);

        // Only report LFB-capable graphics modes
        if (!(attr & 0x0080) || !(attr & 0x0010)) {
            continue;
        }

        int32_t w   = _farpeekw(_dos_ds, __tb + 18);
        int32_t h   = _farpeekw(_dos_ds, __tb + 20);
        int32_t bpp = _farpeekb(_dos_ds, __tb + 25);

        cb(w, h, bpp, userData);
    }
}


// Tears down the graphics mode and restores standard 80x25 text
// mode (BIOS mode 3) so the user gets their DOS prompt back.
// Also frees the backbuffer, palette, and disables near pointers
// (re-enables DJGPP's segment limit checking for safety).

void platformVideoFreeBuffers(DisplayT *d) {
    if (d->backBuf) {
        free(d->backBuf);
        d->backBuf = NULL;
    }

    if (d->palette) {
        free(d->palette);
        d->palette = NULL;
    }

    unmapLfb(d);
}


// Complete video initialization sequence:
//   1. findBestMode()  -- enumerate VESA modes and pick the best match
//   2. setVesaMode()  -- actually switch to the chosen mode with LFB
//   3. mapLfb()  -- DPMI-map the physical framebuffer into linear memory
//   4. Allocate system RAM backbuffer (same size as LFB)
//   5. Set up 8-bit palette if needed
//   6. Initialize clip rect to full display
//
// The backbuffer is allocated in system RAM rather than drawing
// directly to the LFB because: (a) reads from the LFB are extremely
// slow on ISA/VLB/PCI (uncached MMIO), so any compositing that reads
// pixels would crawl; (b) the dirty rect system only flushes changed
// regions, so most of the LFB is never touched per frame.

int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
    uint16_t bestMode;
    uint32_t physAddr;

    memset(d, 0, sizeof(*d));

    // Find the best VESA mode
    if (findBestMode(requestedW, requestedH, preferredBpp, &bestMode, d) != 0) {
        return -1;
    }

    // Save the physical address before we overwrite it
    physAddr = (uint32_t)(uintptr_t)d->lfb;

    // Set the mode
    if (setVesaMode(bestMode) != 0) {
        return -1;
    }

    // Map the LFB
    if (mapLfb(d, physAddr) != 0) {
        return -1;
    }

    // Allocate backbuffer
    uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
    d->backBuf = (uint8_t *)malloc(fbSize);

    if (!d->backBuf) {
        fprintf(stderr, "VBE: Failed to allocate %lu byte backbuffer\n", (unsigned long)fbSize);
        unmapLfb(d);
        __djgpp_nearptr_disable();
        return -1;
    }

    memset(d->backBuf, 0, fbSize);

    // Set up palette for 8-bit mode
    if (d->format.bitsPerPixel == 8) {
        d->palette = (uint8_t *)malloc(PALETTE_BYTES);

        if (!d->palette) {
            fprintf(stderr, "VBE: Failed to allocate palette\n");
            free(d->backBuf);
            d->backBuf = NULL;
            unmapLfb(d);
            __djgpp_nearptr_disable();
            return -1;
        }

        dvxGeneratePalette(d->palette);
        platformVideoSetPalette(d->palette, 0, PALETTE_ENTRIES);
    }

    // Initialize clip rect to full display
    d->clipX = 0;
    d->clipY = 0;
    d->clipW = d->width;
    d->clipH = d->height;

    return 0;
}


// Programs the VGA DAC palette registers via direct port I/O.
// Port 0x3C8 = write index, port 0x3C9 = data (auto-increments).
//
// The VGA DAC expects 6-bit values (0-63) but our palette stores
// 8-bit values (0-255), hence the >> 2 shift. This is standard
// VGA behavior dating back to the original IBM VGA in 1987.
//
// Direct port I/O is used instead of VBE function 09h (set palette)
// because the VGA DAC ports are faster (no BIOS call overhead) and
// universally compatible  -- even VBE 3.0 cards still have the standard
// VGA DAC at ports 0x3C8/0x3C9.

void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) {
    outportb(0x3C8, (uint8_t)firstEntry);

    for (int32_t i = 0; i < count; i++) {
        int32_t idx = (firstEntry + i) * 3;
        outportb(0x3C9, pal[idx + 0] >> 2);
        outportb(0x3C9, pal[idx + 1] >> 2);
        outportb(0x3C9, pal[idx + 2] >> 2);
    }
}


void platformVideoShutdown(DisplayT *d) {
    // INT 10h function 00h, mode 03h = 80x25 color text
    __dpmi_regs r;
    memset(&r, 0, sizeof(r));
    r.x.ax = 0x0003;
    __dpmi_int(0x10, &r);

    platformVideoFreeBuffers(d);

    __djgpp_nearptr_disable();
}


// Cooperative yield to the DPMI host. In a multitasking DOS
// environment (Windows 3.x DOS box or OS/2 VDM),
// this gives other tasks a chance to run. Under a single-tasking
// DPMI server (CWSDPMI) it's essentially a no-op, but it doesn't
// hurt. Called once per event loop iteration when idle.

void platformYield(void) {
    __dpmi_yield();
}


// Sets a VBE video mode via function 0x4F02. Bit 14 of the mode
// number tells the BIOS to enable the Linear Frame Buffer instead
// of the default banked mode. This is the only mode we support.

static int32_t setVesaMode(uint16_t mode) {
    __dpmi_regs r;

    memset(&r, 0, sizeof(r));
    r.x.ax = 0x4F02;
    r.x.bx = mode | 0x4000; // bit 14 = use LFB
    __dpmi_int(0x10, &r);

    if (r.x.ax != 0x004F) {
        fprintf(stderr, "VBE: Failed to set mode 0x%04X (AX=0x%04X)\n", mode, r.x.ax);
        return -1;
    }

    return 0;
}


// Formatted append to the system info buffer (newline-terminated).
static void sysInfoAppend(const char *fmt, ...) {
    char    line[PLATFORM_SYSINFO_MAX];
    va_list ap;
    int32_t pos;

    va_start(ap, fmt);
    vsnprintf(line, sizeof(line), fmt, ap);
    va_end(ap);

    // Accumulate the formatted line and its trailing newline through the
    // shared bounded appender.  The intermediate buffer is the full
    // sysinfo size, so it adds no truncation point the direct write lacked;
    // a -1 return leaves sSysInfoPos untouched (buffer full or truncated).
    pos = dvxStrAppendf(sSysInfoBuf, PLATFORM_SYSINFO_MAX, sSysInfoPos, "%s\n", line);

    if (pos >= 0) {
        sSysInfoPos = pos;
    }
}


// Unlocks and frees the DPMI physical LFB mapping recorded by mapLfb.
// Safe to call when nothing is mapped (sLfbLinearAddr == LFB_NOT_MAPPED)
// and idempotent: the sentinel is reset so a second call is a no-op.
// Uses the raw linear address, NOT d->lfb (which has the conventional
// base folded in).  Clears d->lfb so callers do not keep a stale pointer.
static void unmapLfb(DisplayT *d) {
    if (sLfbLinearAddr != LFB_NOT_MAPPED) {
        __dpmi_meminfo info;
        info.address = sLfbLinearAddr;
        info.size    = sLfbMappedSize;
        __dpmi_unlock_linear_region(&info);
        __dpmi_free_physical_address_mapping(&info);
        sLfbLinearAddr = LFB_NOT_MAPPED;
        sLfbMappedSize = 0;
    }

    d->lfb = NULL;
}


// ============================================================
// DXE3 export table
// ============================================================
//
// DJGPP's DXE3 modules have no implicit access to the host's symbol
// table.  Every function or variable a DXE needs must be explicitly
// listed here and registered via dlregsym() before any dlopen().
//
// Three categories:
//   1. Platform functions (all platform*() from this file)
//   2. C runtime (libc, libm, libgcc, DJGPP internals)
//   3. DXE3 runtime (dlopen/dlsym/dlclose/dlerror/dlregsym)

// DJGPP stdio internals
extern FILE __dj_stdin;
extern FILE __dj_stdout;
extern FILE __dj_stderr;

// libgcc 64-bit integer math helpers
extern long long __divdi3(long long, long long);
extern long long __moddi3(long long, long long);
extern long long __muldi3(long long, long long);
extern unsigned long long __udivdi3(unsigned long long, unsigned long long);
extern unsigned long long __udivmoddi4(unsigned long long, unsigned long long, unsigned long long *);
extern unsigned long long __umoddi3(unsigned long long, unsigned long long);

// DJGPP runtime internals (__djgpp_exception_state_ptr comes from
// <sys/exceptn.h> included above, with proper jmp_buf* typing)
extern void __dj_assert(const char *, const char *, int);
extern unsigned short __dj_ctype_flags[];
extern unsigned char __dj_ctype_tolower[];
extern unsigned char __dj_ctype_toupper[];

// GCC emulated thread-local storage
extern void *__emutls_get_address(void *);

// stb_ds internals (implementation compiled into dvx.exe via loaderMain.c)
// dvxLog lives in dvx.exe (loaderMain.c)
extern void dvxLog(const char *fmt, ...);

// DPMI/go32 internals needed by the serial driver (rs232)
#include <dpmi.h>
#include <go32.h>

extern void  *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap);
extern void   stbds_arrfreef(void *a);
extern void   stbds_hmfree_func(void *a, size_t elemsize);
extern void  *stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
extern void  *stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode);
extern void  *stbds_hmput_default(void *a, size_t elemsize);
extern void  *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
extern void  *stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode);
extern void  *stbds_shmode_func(size_t elemsize, int mode);
extern size_t stbds_hash_string(char *str, size_t seed);
extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
extern void   stbds_rand_seed(size_t seed);
extern char  *stbds_stralloc(void *a, char *str);
extern void   stbds_strreset(void *a);

// GCC runtime helpers (64-bit math, float conversion)
extern unsigned long long __fixunsdfdi(double);
extern unsigned long long __fixunssfdi(float);
extern long long __fixdfdi(double);
extern long long __fixsfdi(float);
extern double __floatdidf(long long);
extern float __floatdisf(long long);

DXE_EXPORT_TABLE(sDxeExportTable)

    // --- dvx helpers (lives in dvx.exe, used by all modules) ---
    DXE_EXPORT(dvxCalloc)
    DXE_EXPORT(dvxFree)
    DXE_EXPORT(dvxHasExt)
    DXE_EXPORT(dvxLog)
    DXE_EXPORT(dvxMalloc)
    DXE_EXPORT(dvxMemAppIdPtr)
    DXE_EXPORT(dvxMemGetAppUsage)
    DXE_EXPORT(dvxMemResetApp)
    DXE_EXPORT(dvxReadDir)
    DXE_EXPORT(dvxReadDirFree)
    DXE_EXPORT(dvxRealloc)
    DXE_EXPORT(dvxSkipWs)
    DXE_EXPORT(dvxStrAppendf)
    DXE_EXPORT(dvxStrdup)
    DXE_EXPORT(dvxTrimRight)

    // --- platform ---
    DXE_EXPORT(platformAltScanToChar)
    DXE_EXPORT(platformChdir)
    DXE_EXPORT(platformCopyFile)
    DXE_EXPORT(platformFlushRect)
    DXE_EXPORT(platformGetMemoryInfo)
    DXE_EXPORT(platformGetSystemInfo)
    DXE_EXPORT(platformGlobMatch)
    DXE_EXPORT(platformInit)
    DXE_EXPORT(platformInstallCrashHandler)
    DXE_EXPORT(platformKeyboardGetModifiers)
    DXE_EXPORT(platformKeyboardRead)
    DXE_EXPORT(platformKeyUpInit)
    DXE_EXPORT(platformKeyUpRead)
    DXE_EXPORT(platformKeyUpShutdown)
    DXE_EXPORT(platformLineEnding)
    DXE_EXPORT(platformLogCrashDetail)
    DXE_EXPORT(platformMkdirRecursive)
    DXE_EXPORT(platformMouseInit)
    DXE_EXPORT(platformMousePoll)
    DXE_EXPORT(platformMouseSetAccel)
    DXE_EXPORT(platformMouseSetMickeys)
    DXE_EXPORT(platformMouseWarp)
    DXE_EXPORT(platformMouseWheelInit)
    DXE_EXPORT(platformMouseWheelPoll)
    DXE_EXPORT(platformPathBaseName)
    DXE_EXPORT(platformPathDirEnd)
    DXE_EXPORT(platformReadFile)
    DXE_EXPORT(platformRegisterDxeExports)
    DXE_EXPORT(platformRegisterSymOverrides)
    DXE_EXPORT(platformSpanCopy8)
    DXE_EXPORT(platformSpanCopy16)
    DXE_EXPORT(platformSpanCopy32)
    DXE_EXPORT(platformSpanFill8)
    DXE_EXPORT(platformSpanFill16)
    DXE_EXPORT(platformSpanFill32)
    DXE_EXPORT(platformStripLineEndings)
    DXE_EXPORT(platformValidateFilename)
    DXE_EXPORT(platformVideoEnumModes)
    DXE_EXPORT(platformVideoFreeBuffers)
    DXE_EXPORT(platformVideoInit)
    DXE_EXPORT(platformVideoSetPalette)
    DXE_EXPORT(platformVideoShutdown)
    DXE_EXPORT(platformYield)

    // --- DPMI/go32 (needed by rs232 serial driver) ---
    DXE_EXPORT(__dpmi_free_physical_address_mapping)
    DXE_EXPORT(__dpmi_get_segment_base_address)
    DXE_EXPORT(__dpmi_lock_linear_region)
    DXE_EXPORT(__dpmi_unlock_linear_region)
    DXE_EXPORT(_go32_dpmi_allocate_iret_wrapper)
    DXE_EXPORT(_go32_dpmi_free_iret_wrapper)
    DXE_EXPORT(_go32_dpmi_get_protected_mode_interrupt_vector)
    DXE_EXPORT(_go32_dpmi_set_protected_mode_interrupt_vector)
    DXE_EXPORT(_go32_info_block)

    // --- memory ---
    // --- memory (tracked wrappers replace libc for DXE code) ---
    { "_calloc",  (void *)dvxCalloc },
    { "_free",    (void *)dvxFree },
    { "_malloc",  (void *)dvxMalloc },
    { "_realloc", (void *)dvxRealloc },

    // --- string / memory ops ---
    DXE_EXPORT(memchr)
    DXE_EXPORT(memcmp)
    DXE_EXPORT(memcpy)
    DXE_EXPORT(memmove)
    DXE_EXPORT(memset)
    DXE_EXPORT(strcasecmp)
    DXE_EXPORT(strcat)
    DXE_EXPORT(strchr)
    DXE_EXPORT(strcmp)
    DXE_EXPORT(strcpy)
    DXE_EXPORT(strcspn)
    { "_strdup",  (void *)dvxStrdup },
    DXE_EXPORT(strerror)
    DXE_EXPORT(strnicmp)
    DXE_EXPORT(stricmp)
    DXE_EXPORT(strlen)
    DXE_EXPORT(strncasecmp)
    DXE_EXPORT(strncat)
    DXE_EXPORT(strncmp)
    DXE_EXPORT(strncpy)
    DXE_EXPORT(strpbrk)
    DXE_EXPORT(strrchr)
    DXE_EXPORT(strspn)
    DXE_EXPORT(strstr)
    DXE_EXPORT(strtok)

    // --- ctype ---
    DXE_EXPORT(isalnum)
    DXE_EXPORT(isalpha)
    DXE_EXPORT(isascii)
    DXE_EXPORT(isdigit)
    DXE_EXPORT(islower)
    DXE_EXPORT(isprint)
    DXE_EXPORT(ispunct)
    DXE_EXPORT(isspace)
    DXE_EXPORT(isupper)
    DXE_EXPORT(isxdigit)
    DXE_EXPORT(tolower)
    DXE_EXPORT(toupper)

    // --- conversion ---
    DXE_EXPORT(abs)
    DXE_EXPORT(ldiv)
    DXE_EXPORT(atof)
    DXE_EXPORT(atoi)
    DXE_EXPORT(atol)
    DXE_EXPORT(labs)
    DXE_EXPORT(strtod)
    DXE_EXPORT(strtol)
    DXE_EXPORT(strtoul)

    // --- formatted I/O ---
    DXE_EXPORT(fprintf)
    DXE_EXPORT(perror)
    DXE_EXPORT(fputs)
    DXE_EXPORT(fscanf)
    DXE_EXPORT(printf)
    DXE_EXPORT(puts)
    DXE_EXPORT(snprintf)
    DXE_EXPORT(sprintf)
    DXE_EXPORT(sscanf)
    DXE_EXPORT(vfprintf)
    DXE_EXPORT(vprintf)
    DXE_EXPORT(vsnprintf)
    DXE_EXPORT(vsprintf)

    // --- character I/O ---
    DXE_EXPORT(fgetc)
    DXE_EXPORT(fgets)
    DXE_EXPORT(fputc)
    DXE_EXPORT(getc)
    DXE_EXPORT(putc)
    DXE_EXPORT(putchar)
    DXE_EXPORT(ungetc)

    // --- file I/O ---
    DXE_EXPORT(fclose)
    DXE_EXPORT(feof)
    DXE_EXPORT(ferror)
    DXE_EXPORT(fflush)
    DXE_EXPORT(fopen)
    DXE_EXPORT(fread)
    DXE_EXPORT(freopen)
    DXE_EXPORT(fseek)
    DXE_EXPORT(ftell)
    DXE_EXPORT(fwrite)
    DXE_EXPORT(remove)
    DXE_EXPORT(rename)
    DXE_EXPORT(rewind)
    DXE_EXPORT(tmpfile)
    DXE_EXPORT(tmpnam)

    // --- directory ---
    DXE_EXPORT(closedir)
    DXE_EXPORT(mkdir)
    DXE_EXPORT(opendir)
    DXE_EXPORT(readdir)
    DXE_EXPORT(rmdir)

    // --- filesystem ---
    DXE_EXPORT(access)
    DXE_EXPORT(chdir)
    DXE_EXPORT(chmod)
    DXE_EXPORT(fnmatch)
    DXE_EXPORT(getcwd)
    DXE_EXPORT(realpath)
    DXE_EXPORT(stat)
    DXE_EXPORT(unlink)

    // --- time ---
    DXE_EXPORT(clock)
    DXE_EXPORT(difftime)
    DXE_EXPORT(gmtime)
    DXE_EXPORT(localtime)
    DXE_EXPORT(mktime)
    DXE_EXPORT(strftime)
    DXE_EXPORT(asctime)
    DXE_EXPORT(ctime)
    DXE_EXPORT(sleep)
    DXE_EXPORT(time)
    DXE_EXPORT(usleep)

    // --- process / environment ---
    DXE_EXPORT(abort)
    DXE_EXPORT(atexit)
    DXE_EXPORT(exit)
    DXE_EXPORT(getenv)
    DXE_EXPORT(setenv)
    DXE_EXPORT(putenv)
    DXE_EXPORT(system)

    // --- sorting / searching ---
    DXE_EXPORT(bsearch)
    DXE_EXPORT(qsort)

    // --- random ---
    DXE_EXPORT(rand)
    DXE_EXPORT(srand)

    // --- setjmp / signal ---
    DXE_EXPORT(longjmp)
    DXE_EXPORT(setjmp)
    DXE_EXPORT(raise)
    DXE_EXPORT(signal)

    // --- libm ---
    DXE_EXPORT(acos)
    DXE_EXPORT(asin)
    DXE_EXPORT(atan)
    DXE_EXPORT(atan2)
    DXE_EXPORT(ceil)
    DXE_EXPORT(cos)
    DXE_EXPORT(exp)
    DXE_EXPORT(fabs)
    DXE_EXPORT(floor)
    DXE_EXPORT(fmod)
    DXE_EXPORT(frexp)
    DXE_EXPORT(ldexp)
    DXE_EXPORT(log)
    DXE_EXPORT(log10)
    DXE_EXPORT(modf)
    DXE_EXPORT(pow)
    DXE_EXPORT(sin)
    DXE_EXPORT(sinh)
    DXE_EXPORT(cosh)
    DXE_EXPORT(tanh)
    DXE_EXPORT(sqrt)
    DXE_EXPORT(strtof)
    DXE_EXPORT(tan)

    // --- errno ---
    DXE_EXPORT(errno)

    // --- libgcc 64-bit integer math ---
    DXE_EXPORT(__divdi3)
    DXE_EXPORT(__moddi3)
    DXE_EXPORT(__muldi3)
    DXE_EXPORT(__udivdi3)
    DXE_EXPORT(__udivmoddi4)
    DXE_EXPORT(__umoddi3)

    // --- DJGPP internals ---
    DXE_EXPORT(__dj_stdin)
    DXE_EXPORT(__dj_stdout)
    DXE_EXPORT(__dj_stderr)
    DXE_EXPORT(__dj_assert)
    DXE_EXPORT(__dj_ctype_flags)
    DXE_EXPORT(__dj_ctype_tolower)
    DXE_EXPORT(__dj_ctype_toupper)
    DXE_EXPORT(__djgpp_exception_state_ptr)

    // --- GCC internals ---
    DXE_EXPORT(__emutls_get_address)

    // --- DXE3 runtime ---
    DXE_EXPORT(dlopen)
    DXE_EXPORT(dlsym)
    DXE_EXPORT(dlclose)
    DXE_EXPORT(dlerror)
    DXE_EXPORT(dlregsym)

    // --- stb_ds (implementation lives in dvx.exe) ---
    DXE_EXPORT(stbds_arrgrowf)
    DXE_EXPORT(stbds_arrfreef)
    DXE_EXPORT(stbds_hmfree_func)
    DXE_EXPORT(stbds_hmget_key)
    DXE_EXPORT(stbds_hmget_key_ts)
    DXE_EXPORT(stbds_hmput_default)
    DXE_EXPORT(stbds_hmput_key)
    DXE_EXPORT(stbds_hmdel_key)
    DXE_EXPORT(stbds_shmode_func)
    DXE_EXPORT(stbds_hash_string)
    DXE_EXPORT(stbds_hash_bytes)
    DXE_EXPORT(stbds_rand_seed)
    DXE_EXPORT(stbds_stralloc)
    DXE_EXPORT(stbds_strreset)

    // POSIX file I/O (needed by SQLite and any DXE doing low-level I/O)
    DXE_EXPORT(close)
    DXE_EXPORT(fstat)
    DXE_EXPORT(fsync)
    DXE_EXPORT(ftruncate)
    DXE_EXPORT(getpid)
    DXE_EXPORT(gettimeofday)
    DXE_EXPORT(lseek)
    DXE_EXPORT(open)
    DXE_EXPORT(read)
    DXE_EXPORT(write)

    // Additional POSIX/libc commonly needed by library DXEs
    DXE_EXPORT(dup)
    DXE_EXPORT(dup2)
    DXE_EXPORT(fcntl)
    DXE_EXPORT(fileno)
    DXE_EXPORT(isatty)
    DXE_EXPORT(pipe)
    DXE_EXPORT(truncate)
    DXE_EXPORT(umask)

    // GCC runtime helpers (64-bit math, float conversion)
    DXE_EXPORT(__fixunsdfdi)
    DXE_EXPORT(__fixunssfdi)
    DXE_EXPORT(__fixdfdi)
    DXE_EXPORT(__fixsfdi)
    DXE_EXPORT(__floatdidf)
    DXE_EXPORT(__floatdisf)

DXE_EXPORT_END
