// 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. // dvxPlat.h -- Platform abstraction layer for DVX GUI // // All OS-specific and CPU-specific code is isolated behind this // interface. To port DVX to a new platform, implement a new // dvxPlatformXxx.c against this header. // // Two implementations exist: // dvxPlatformDos.c -- DJGPP/DPMI: real VESA VBE, INT 33h mouse, // INT 16h keyboard, INT 9 keyboard hook, PIT / INT 8 // timer service, rep movsd/stosl asm spans // dvxPlatformHost.c -- native Linux test backend: offscreen // framebuffer, queue-driven input, fake clock // with simulated timer interrupts // // Code shared by every backend lives in dvxPlatformUtil.c (logging, // path/file helpers, glob matching, Alt scancode table, exit routing), // dvxPlatformIrq.c (held-key table, timer service; interrupt-time steps // in the private dvxPlatIsr.h) and dvxMemTrack.c (per-app allocation // tracking). // // The abstraction covers video mode setup, framebuffer flushing, // optimized memory spans, mouse and keyboard input, system information, // crash recovery, and DXE symbol export, plus small file-system helpers // (filename validation, mkdir -p, chdir with drive, copy, read, glob) // where the C standard library is not enough or not portable. // // Design rule: functions in this header must be stateless or manage their // own internal state. They must not reference AppContextT or any layer // above dvxTypes.h. This ensures the platform layer can be compiled and // tested independently. #ifndef DVX_PLAT_H #define DVX_PLAT_H #include "dvxTypes.h" #include "dvxMem.h" #include #include // Maximum file path length. 260 matches DOS MAX_PATH. #define DVX_MAX_PATH 260 // ============================================================ // Keyboard event // ============================================================ // // Separating ASCII value from scancode handles the DOS keyboard model // where extended keys (arrows, F-keys) produce a zero ASCII byte followed // by a scancode. The platform layer normalizes this into a single struct. typedef struct { int32_t ascii; // ASCII value, 0 for extended/function keys int32_t scancode; // PC scan code (0x48=Up, 0x50=Down, etc.) int32_t modifiers; // KEY_MOD_* state for this key (key-down events only) } PlatformKeyEventT; // ============================================================ // Logging // ============================================================ // Append a line to the DVX log file. Lives in dvxPlatformUtil.c, so it // is linked into dvx.exe (and exported to all modules) as well as into // every host-side tool and test binary. void dvxLog(const char *fmt, ...); // The DVX log file in the DVX folder: the loader starts it and dvxLog // appends to it. #define DVX_LOG_FILE "dvx.log" // Set the path dvxLog appends to. The pointer is retained, not copied, // so it must outlive every subsequent dvxLog call (a string literal is // the intended argument). Defaults to DVX_LOG_FILE in the working // directory until this is called (and for a NULL path). void platformSetLogPath(const char *path); // Keep the log in memory instead of a file (DVX on read-only media with // no TEMP folder, or SETUP.EXE, which never writes to its disk): the // latest PLATFORM_LOG_MEM_MAX bytes. platformSetLogPath goes back to a // file. #define PLATFORM_LOG_MEM_MAX 16384 void platformSetLogMemory(void); // The log kept in memory ("" when there is none). const char *platformLogMemory(void); // Whether DVX's own folder can be written to. The loader tests it at // startup (platformDirIsWritable); on read-only media (a CD-ROM, a // write-protected disk) settings are kept in memory until DVX exits, the // help files are used as they are, and the shell says so once. bool platformIsReadOnly(void); void platformSetReadOnly(bool readOnly); // True when a file can be created in dir (a scratch file is made and // removed again). bool platformDirIsWritable(const char *dir); // The folder for scratch files: the TEMP (or else TMP) environment // variable, in native separators (a copy, valid until the next call); // NULL when neither is set. const char *platformTempDir(void); // ============================================================ // Timing // ============================================================ // Monotonic tick counter for UI timing (double-click, tooltip delay, // cursor blink, timer widgets). Ticks advance at platformClockHz() // per second. On DOS this wraps clock()/CLOCKS_PER_SEC; on the host // test backend it is a fake clock advanced only by hostClockAdvance() // so timing-dependent behaviour is deterministic under test. // // Unsigned so that "now - then" is wrap-safe; always compare intervals // via subtraction, never compare raw tick values with < or >. typedef uint32_t PlatformTicksT; PlatformTicksT platformClock(void); PlatformTicksT platformClockHz(void); // Fine-grained, free-running timestamp for input-timing entropy (not a // clock: units and wrap point are backend-specific). DOS returns the TSC // when the CPU has one, else BIOS ticks with the PIT counter in the low // 16 bits. uint64_t platformEntropyStamp(void); // Convert a millisecond interval to ticks at the platform rate. #define PLATFORM_MS_TO_TICKS(ms) ((PlatformTicksT)(((uint64_t)(ms) * platformClockHz()) / 1000)) // Convert ticks to milliseconds: the "now" the protocol engines (conn, // xfer) take is PLATFORM_TICKS_TO_MS(platformClock()). #define PLATFORM_TICKS_TO_MS(ticks) ((uint32_t)(((uint64_t)(ticks) * 1000) / platformClockHz())) // ============================================================ // Timing -- high-resolution clock and the timer service // ============================================================ // // One timer interrupt serves everything. On DOS the service owns PIT // channel 0 (the 8254 timer, rate generator mode) and the INT 8 vector: // it runs the interrupt as fast as the fastest timer needs (at most // PLATFORM_TIMER_MAX_HZ, at the BIOS 18.2 Hz while no timer is // registered) and passes on exactly one interrupt per 65536 PIT input // clocks to the BIOS handler, so the BIOS tick count, the DOS time of day // and clock() keep their normal rate. The host backend simulates the same // interrupts, driven by hostClockAdvance. #define PLATFORM_PIT_HZ 1193182 // PIT input clock #define PLATFORM_PIT_BIOS_DIVISOR 65536 // PIT clocks per BIOS tick (18.2 Hz) #define PLATFORM_TIMER_MAX_HZ 1000 // highest rate a timer may ask for #define PLATFORM_TIMER_FAST_MAX_HZ 30000 // highest rate platformTimerAddFast accepts #define PLATFORM_TIMER_SLOTS 16 // timers that can exist at once #define PLATFORM_US_PER_SEC 1000000 // Microseconds since the timer service started (0 before), monotonic. On // DOS the PIT clocks counted by the interrupt plus the live channel 0 // count (about 0.84 us steps), or, on a CPU with a time-stamp counter, // the TSC since the last interrupt scaled by the TSC rate measured over // the interrupt period before it, so it never drifts from the PIT. Not // for use inside a timer callback. uint64_t platformMicros(void); // A timer callback. It runs INSIDE THE TIMER INTERRUPT, with interrupts // disabled, on a small interrupt stack: keep it to a few dozen // microseconds, do not call the C library, DVX or DOS, do not use floating // point, and only touch code and data pinned with platformLockRegion // (on DOS a page fault here is fatal). Setting flags or counters, filling // a buffer or writing I/O ports is what it is for. typedef void (*PlatformTimerFnT)(void *userData); // Diagnostic counters of the timer service. typedef struct { uint32_t rateHz; // current interrupt rate, rounded uint32_t divisor; // PIT channel 0 reload value uint32_t interrupts; // timer interrupts since the service started uint32_t biosTicks; // of those, passed on to the BIOS } PlatformTimerStatsT; // Start / stop the timer service. Init programs the PIT for the BIOS // rate and takes the timer interrupt; Shutdown gives the PIT and the // vector back as it found them (BIOS square-wave mode, 18.2 Hz) and drops // every timer. Init also arranges for Shutdown to run at exit() and on // a crash that is not recovered. A second Init does nothing. void platformTimerInit(void); void platformTimerShutdown(void); // Call fn(userData) hz times a second (1..PLATFORM_TIMER_MAX_HZ, exact on // average: the calls fall on timer interrupts, so single intervals jitter // by up to one interrupt period; the interrupt rate changes at once, the // PIT restarting with the part of the period already run counted, and // the first call comes one interval on). ownerAppId is recorded for // platformTimerRemoveOwner (0 for a library that removes its own timers). // Returns a timer id, or -1 when hz is out of range, fn is NULL, all // PLATFORM_TIMER_SLOTS are in use or the service is not running. int32_t platformTimerAdd(uint32_t hz, PlatformTimerFnT fn, void *userData, int32_t ownerAppId); // platformTimerAdd for a device that must be fed at an audio rate (the PC // speaker's digital playback): hz may be up to PLATFORM_TIMER_FAST_MAX_HZ. // The whole timer interrupt then runs that often, so fn must be very // short (a few port writes); every other rule of platformTimerAdd holds. // For a call on every interrupt, ask for PLATFORM_PIT_HZ / divisor. int32_t platformTimerAddFast(uint32_t hz, PlatformTimerFnT fn, void *userData, int32_t ownerAppId); // Stop one timer; the interrupt rate drops when it was the fastest. When // this returns the callback is not running and will not run again. void platformTimerRemove(int32_t timerId); // Stop every timer registered for ownerAppId (the shell calls this when an // app ends). void platformTimerRemoveOwner(int32_t ownerAppId); // Current counters of the timer service. void platformTimerStats(PlatformTimerStatsT *out); // Disable interrupts and return the previous state, for // platformIrqRestore: brackets the few instructions that change data an // interrupt handler reads. Nothing to do on the host. void platformIrqRestore(uint32_t state); uint32_t platformIrqSave(void); // Shutdown hooks: fn runs when DVX ends without its normal shutdown // (exit() or a crash with no recovery point), before the timer service and // the keyboard hook are given back, so a module that drives hardware from // an interrupt (a sound card playing by DMA) can stop it. A module removes // its hook before it is unloaded. At most PLATFORM_SHUTDOWN_HOOKS at once; // Add returns false when full (adding one already present is a no-op). #define PLATFORM_SHUTDOWN_HOOKS 8 typedef void (*PlatformShutdownFnT)(void); bool platformShutdownHookAdd(PlatformShutdownFnT fn); void platformShutdownHookRemove(PlatformShutdownFnT fn); // ============================================================ // System lifecycle // ============================================================ // One-time platform initialisation (a second call does nothing new). On // DOS this disables Ctrl+C and Ctrl+Break (runtime, SIGINT and DOS break // checking) so the user cannot kill the GUI from inside a graphics mode, // and installs a DOS critical-error (INT 24h) handler that answers Fail: // a drive that is not ready, a write-protected disk or a removed disk makes // the DOS call return an error instead of waiting at "Abort, Retry, Fail" // on the hidden text screen. DOS restores its own handler when DVX exits. void platformInit(void); // Cooperative yield -- give up the CPU timeslice when the event loop has // nothing to do. On DOS this calls __dpmi_yield() to be friendly to // multitaskers (Windows 3.x, OS/2). void platformYield(void); // ============================================================ // Video // ============================================================ // Probe for a suitable video mode, enable it, map the framebuffer, and // allocate the system RAM backbuffer. On DOS this involves VBE BIOS calls // and DPMI physical memory mapping. Fills in all DisplayT fields on success. int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp); // Restore the previous video mode and free all video resources. On DOS // this restores VGA text mode (mode 3) and frees the DPMI memory mapping. void platformVideoShutdown(DisplayT *d); // Enumerate the graphics modes platformVideoInit can set (on DOS: LFB // modes at 8/15/16/32 bpp), once per distinct width/height/depth. Feeds // the video-mode list offered to the user; platformVideoInit selects its // own best match independently. void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData); // Program the VGA/VESA DAC palette registers (8-bit mode only). pal // points to the full 256-entry RGB table (3 bytes per entry); entries // firstEntry..firstEntry+count-1 are written, clamped to the table. void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count); // VGA mode 13h: the boot splash, and the full-screen fallback #define VGA13_W 320 #define VGA13_H 200 // Full-screen exclusive video (dvxExclusiveBegin). Begin sets the // smallest mode of at least requestedW x requestedH (preferredBpp first, // any depth for 0; on DOS VGA mode 13h up to 320x200 and Mode X up to // 320x240 at 8 bpp when no VBE mode is that small) and fills d with its // own linear backbuffer; the desktop's display and buffers are left as // they are. End frees d's buffers and sets the desktop's mode (and 8-bit // palette) again; call it after a failed Begin too. Flush puts a // backbuffer rectangle on the screen (Mode X: a whole-screen flush flips // pages at the next retrace and waits for it). Capture reads what is on // the screen into out (width x height bytes) where the backbuffer is not // a copy of it (Mode X), else returns false. WaitRetrace waits for the // start of the next vertical retrace. int32_t platformVideoExclusiveBegin(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp); bool platformVideoExclusiveCapture(const DisplayT *d, uint8_t *out); int32_t platformVideoExclusiveEnd(DisplayT *d, const DisplayT *desktop); void platformVideoExclusiveFlush(const DisplayT *d, const RectT *r); void platformVideoWaitRetrace(void); // ============================================================ // Framebuffer flush // ============================================================ // Copy a rectangle from the system RAM backbuffer (d->backBuf) to the // display surface (d->lfb). On DOS this copies to real video memory via // the LFB mapping -- the critical path where PCI bus write speed matters. // Each scanline is copied as a contiguous block; rep movsd on DOS gives // near-optimal bus utilization for aligned 32-bit writes. void platformFlushRect(const DisplayT *d, const RectT *r); // ============================================================ // Optimised memory operations (span fill / copy) // ============================================================ // // These are the innermost loops of the renderer -- called once per // scanline of every rectangle fill, blit, and text draw. On DOS they // use inline assembly: rep stosl for fills (one instruction fills an // entire scanline) and rep movsd for copies. // // Three variants per operation (8/16/32 bpp) because the fill semantics // differ by depth: 8-bit fills a byte per pixel, 16-bit fills a word // (must handle odd counts), and 32-bit fills a dword. The copy variants // differ only in the byte count computation (count * bytesPerPixel). // drawInit() selects the right function pointers into BlitOpsT at startup. void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count); void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count); void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count); void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count); void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count); void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count); // ============================================================ // Input -- Mouse // ============================================================ // Initialize the mouse driver and constrain movement to the screen bounds. // On DOS this resets the INT 33h driver, sets the X/Y range, and centers // the cursor. Safe to call again after a video-mode change: the speed, // acceleration and wheel settings applied earlier are re-applied. void platformMouseInit(int32_t screenW, int32_t screenH); // Button bits platformMousePoll reports (MOUSE_LEFT/RIGHT/MIDDLE) #define MOUSE_BUTTON_MASK (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE) // Poll the current mouse state. Buttons is a bitmask: bit 0 = left, // bit 1 = right, bit 2 = middle. Polling (rather than event-driven // callbacks) is the natural model for a cooperative event loop -- the // main loop polls once per frame and compares with the previous state // to detect press/release edges. void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons); // Detect and activate mouse wheel support. Returns true if the mouse // driver supports the CuteMouse Wheel API (INT 33h AX=0011h). This // call also activates wheel reporting -- after it returns true, function // 03h will return wheel delta in BH. Must be called after platformMouseInit. bool platformMouseWheelInit(void); // Read the accumulated wheel delta since the last call. Positive = scroll // down, negative = scroll up. Returns 0 if no wheel movement or if wheel // is not supported. The delta is cleared on each read (accumulated by the // driver between polls). int32_t platformMouseWheelPoll(void); // Set the double-speed threshold in mickeys/second. When the mouse // moves faster than this, cursor movement is doubled by the driver. // A very high value (e.g. 10000) effectively disables acceleration. void platformMouseSetAccel(int32_t threshold); // Set the mickey-to-pixel ratio. Controls base cursor speed. // horizMickeys/vertMickeys: mickeys per 8 pixels of cursor movement. // Higher values = slower cursor. Default is typically 8 horiz, 16 vert. void platformMouseSetMickeys(int32_t horizMickeys, int32_t vertMickeys); // Move the mouse cursor to an absolute screen position. Used to clamp the // cursor to window edges during resize operations. On DOS the driver // cursor is moved too (INT 33h fn 04h) only when tracking absolute // position; in mickey mode the pending motion counters are flushed. void platformMouseWarp(int32_t x, int32_t y); // ============================================================ // Input -- Keyboard // ============================================================ // Return the current modifier key state in BIOS shift-state format: // bits 0-1 = either shift, bit 2 = ctrl, bit 3 = alt. On DOS this // uses INT 16h AH=12h (get extended shift status) and returns the low byte. int32_t platformKeyboardGetModifiers(void); // The Shift/Ctrl/Alt bits of the BIOS shift-state byte. #define KEY_MOD_ALL (KEY_MOD_SHIFT | KEY_MOD_CTRL | KEY_MOD_ALT) // ASCII byte the enhanced BIOS reports for grey (extended) keys. #define KEY_ASCII_GREY_PREFIX 0xE0 // Non-blocking read of the next key from the keyboard buffer. Returns // true if a key was available. On DOS this uses INT 16h AH=11h (check) // + AH=10h (read). Extended keys (0xE0 prefix from enhanced keyboard) // are normalized by zeroing the ASCII byte so the scancode identifies // them unambiguously, and Shift/Ctrl/Alt+F1..F12 and Alt+grey navigation // keys are reported as the plain key's scancode with evt->modifiers // holding the modifier the BIOS code proved was down (see // platformNormalizeKeyEvent). bool platformKeyboardRead(PlatformKeyEventT *evt); // Map the BIOS scancodes for Shift/Ctrl/Alt+F1..F12 onto the plain F-key // scancodes (F1-F10 0x3B-0x44, F11 0x85, F12 0x86) and Alt+grey // Home/Up/PgUp/Left/Right/End/Down/PgDn/Ins/Del (0x97-0xA3) onto the plain // navigation scancodes (0x47-0x53). Any other scancode is returned // unchanged. int32_t platformNormalizeFKeyScan(int32_t scancode); // Applied by every backend's platformKeyboardRead. A grey-key ASCII // byte (KEY_ASCII_GREY_PREFIX) becomes 0. For an extended key (ascii 0) // then folds the scancode as platformNormalizeFKeyScan does and sets // evt->modifiers to the modifier the BIOS code implies plus any // lower-priority modifier in liveMods (Alt over Ctrl over Shift), so a // modifier released before the key is polled is not lost. Otherwise // the scancode is kept and evt->modifiers = liveMods. void platformNormalizeKeyEvent(PlatformKeyEventT *evt, int32_t liveMods); // ============================================================ // Input -- Raw keys and the held-key table // ============================================================ // // The keyboard hook (on DOS a chain on the INT 9 hardware interrupt that // reads port 60h; the BIOS still stores typed keys for // platformKeyboardRead) decodes every set-1 byte the keyboard sends: // 0xE0 prefixes, the fake Shift codes the keyboard wraps around grey keys // (dropped), the Pause burst (one press and one release) and keyboard // replies such as ACK and overrun (dropped). Every key going down or up // updates the held-key table and is queued as a RawKeyEventT. Raw codes // are KEY_RAW_CODE(scancode, extended) (dvxTypes.h). // Size of the raw key event queue; when it is full new events are dropped // (the held-key table is still updated). #define PLATFORM_KEY_EVENT_QUEUE 64 // Install / remove the keyboard hook. Init before the other functions of // this section, Shutdown before exit. A second Init does nothing. On DOS // the hook's code and data are pinned with platformLockRegion; if that // fails there is no hook (no events, every key reads as up). void platformKeyHookInit(void); void platformKeyHookShutdown(void); // Next queued raw key event, oldest first; false when none is waiting. bool platformKeyEventRead(RawKeyEventT *evt); // True while the key with this raw code is held down. bool platformKeyIsDown(int32_t rawCode); // Copy the held-key table: out[rawCode] is 1 while that key is down. void platformKeyStateGet(uint8_t out[KEY_RAW_COUNT]); // Mark every key up (queued events are kept). A key that is still held // reads as down again at its next typematic repeat. void platformKeyStateClear(void); // ============================================================ // Input -- Test injection // ============================================================ // // Used by the test script runner (DVX /SCRIPT=...), the installer and // BASIC's SysSendKey (keys only). Injected input enters at the lowest // level each backend has, so it travels the same code as real input. // Queue one keystroke exactly as the BIOS would store it: ascii is the // character (0, or 0xE0 for grey keys, for extended keys), scancode the // BIOS scancode (e.g. 0x54 for Shift+F1) and modifiers the BIOS // shift-state byte (bits 0-1 shift, bit 2 ctrl, bit 3 alt) that // platformKeyboardGetModifiers reports from now on. On DOS the key goes // into the BIOS keyboard buffer (INT 16h AH=05h) and the modifier bits // into the BIOS shift-state byte. Returns false if the buffer is full. bool platformInjectKey(int32_t ascii, int32_t scancode, int32_t modifiers); // Set the reported modifier state without queueing a key. void platformInjectModifiers(int32_t modifiers); // Feed one set-1 byte (an 0xE0 prefix, a make code or a break code) to // the keyboard hook's decoder as if the keyboard had sent it: the // held-key table and the raw event queue change, the BIOS keyboard // buffer does not. void platformInjectScan(uint8_t scan); // Override the mouse position and buttons platformMousePoll reports // until the next call. The driver is still polled, so its state machine // keeps running; only the reported values are replaced. void platformInjectMouse(int32_t x, int32_t y, int32_t buttons); // Translate an Alt+key scancode to its corresponding ASCII character. // When Alt is held, DOS doesn't provide the ASCII value -- only the // scancode. This function contains a lookup table mapping scancodes // to their unshifted letter/digit. Returns 0 for scancodes that don't // map to a printable character (e.g. Alt+F1). char platformAltScanToChar(int32_t scancode); // The BIOS scan code INT 16h gives for DVX key code key with modifiers mod // before the keyboard path folds it onto a plain key: Shift / Ctrl / Alt // with F1..F12, Alt with Home..Delete and the grey keypad / and Enter, and // Alt with a letter or digit have codes of their own there. -1 when the // key with those modifiers has none (its own key code stands). For // programs that read the raw codes, such as BASIC's INKEY$. int32_t platformBiosScanCode(int32_t key, int32_t mod); // ============================================================ // System information // ============================================================ // Maximum size of the formatted system information text #define PLATFORM_SYSINFO_MAX 4096 // Gather hardware information (CPU, clock, memory, DOS/DPMI version, // video, mouse, disk drives) and return as a pre-formatted text string. // The display pointer provides the current video mode info. Returns a // pointer to a static buffer valid for the lifetime of the process. // On DOS this uses CPUID, RDTSC, DPMI, INT 21h, INT 33h, and VBE. const char *platformGetSystemInfo(const DisplayT *display); // ============================================================ // File system // ============================================================ // Longest long file name (platformValidateLongName) #define PLATFORM_LONG_NAME_MAX 255 // The short (8.3) form of an existing path, for what reads names before a // long-name driver loads (AUTOEXEC.BAT) or cannot take blanks (a DOS // command line): on a drive with long names DOS gives every part's short // name; elsewhere, and when DOS cannot, path itself (platformPathTidy: // native separators, so "C:/DVX" works the same). False when out is too // small. bool platformShortPath(const char *path, char *out, int32_t outSize); struct stat; // The size stat reported for a file. DOS sizes are 32-bit unsigned (a // FAT32 file reaches 4 GB - 1) where the DOS C library's st_size is a // signed 32-bit off_t, so a file past 2 GB reads as negative there. uint64_t platformStatSize(const struct stat *st); // True when the drive holding path (NULL or "": the current drive) takes // long file names: on DOS when a long-name driver (Windows, DOSLFN) is // loaded and covers that drive; elsewhere always. Without them DOS names // are 8.3. bool platformLongNames(const char *path); // Validate a name for a new file or folder in folder dir (NULL or "": the // current one) against the rules of its disk. On DOS: long names where // platformLongNames(dir) (platformValidateLongName), else 8.3 // (platformValidateShortName), and dir with the name must fit // DVX_MAX_PATH; elsewhere: not empty, . or .., no separators or control // characters. Returns NULL if the name is valid, or a human-readable // error string describing why it's invalid. const char *platformValidateFilename(const char *dir, const char *name); // The DOS name rules by themselves (NULL or why not): a long name (1 to // PLATFORM_LONG_NAME_MAX characters, spaces, several dots and + , ; = [ ] // allowed, none of control characters and \ / : * ? " < > |, no dot or // space at the end) or an 8.3 name; neither may be a device name (CON, // PRN, AUX, NUL, CLOCK$, COMn, LPTn) before its first dot. const char *platformValidateLongName(const char *name); const char *platformValidateShortName(const char *name); // True when a long name may hold character c (not a control character or // one of \ / : * ? " < > |). bool platformLongNameChar(char c); // Query current system memory. Sets totalKb and freeKb to the total // and free physical memory in kilobytes. Returns false if unavailable. bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb); // Per-app memory tracking is declared and documented in dvxMem.h. // Create a directory and all parent directories (like mkdir -p). // Returns 0 on success, -1 on failure (including a path that does not fit // in DVX_MAX_PATH). Existing directories are not an error. int32_t platformMkdirRecursive(const char *path); // Change the working directory, including drive letter on DOS. Standard // chdir() does not switch drives under DJGPP; this wrapper switches drive // first when the path has a drive-letter prefix (e.g. "A:\DVX") and // restores the previous drive if the chdir fails. Returns 0 on success, // -1 on failure (path not found, bad drive letter). int32_t platformChdir(const char *path); // Free the backbuffer and palette without restoring text mode. Used // when switching between graphics modes live. void platformVideoFreeBuffers(DisplayT *d); // Return a pointer to the last directory separator in path // (platformPathIsSep), or NULL if none is found. char *platformPathDirEnd(const char *path); // out = the folder part of path in native separators: everything before // the last separator, a root keeping its own ("C:\\X.TXT" -> "C:\\", since // "C:" alone is the drive's current folder), the drive of "C:X.TXT", or // "." when path names no folder. False when it does not fit. bool platformPathDirName(const char *path, char *out, int32_t outSize); // True for a path rooted at a separator ('/' or '\\') or qualified with // a drive letter ("C:..."). bool platformPathIsAbsolute(const char *path); // True when c separates the parts of a path: '/' and '\\' alike, on every // platform, so a path a program writes with '/' works on DOS exactly as // with '\\' (DOS and the host build also share INI files, projects and // program paths). The one rule all of DVX's path handling follows; the // price is that a host file name cannot hold a literal '\\'. bool platformPathIsSep(char c); // When path is dir itself or lies below it, the rest of path after dir // and its separator ("" for dir itself); NULL otherwise. Compared as // platformPathSame does. const char *platformPathIsUnder(const char *path, const char *dir); // out = dir and name with one separator between them (none added when // dir ends in one), every separator native; an absolute name, or an empty // dir, gives name alone. False when it does not fit in outSize. bool platformPathJoin(char *out, int32_t outSize, const char *dir, const char *name); // Rewrite path's separators as the platform's own (DVX_PATH_SEP), in // place, before it is shown: on DOS every '/' becomes '\\' (the C // library hands back "c:/dvx"). Returns path (NULL stays NULL). char *platformPathNative(char *path); // True when a and b name the same place by DOS rules, on every platform // (DVX's own paths mix spellings such as "APPS/X.APP" and "apps\\x.app"): // the separators interchangeable, a trailing one ignored, case ignored. // (Text only: "." or a relative path is not resolved.) bool platformPathSame(const char *a, const char *b); // platformPathNative, then a run of separators made one (a leading pair, // a network "\\\\server", kept) and a trailing one dropped unless it is a // root's ("C:\\", "\\"): the form for a path handed to DOS directly, // without the C library's own clean-up. In place; returns path. char *platformPathTidy(char *path); // Scratch-file extension markers for crash-safe rewrites // (platformPathSibling + platformReplaceFile). The sibling's extension is // the marker followed by the leading characters of the original one, so an // 8.3 name stays 8.3 and files that differ only in extension get distinct // scratch names: CONFIG\DVX.INI -> CONFIG\DVX.$IN / CONFIG\DVX.~IN, // FOO.BAS -> FOO.$BA, FOO.FRM -> FOO.$FR. #define PLATFORM_TMP_EXT ".$" #define PLATFORM_BAK_EXT ".~" #define PLATFORM_SIBLING_EXT_MAX 3 // characters in a sibling's extension (8.3) // out = path with its file-name extension replaced by ext, followed by as // many leading characters of the original extension as fit in // PLATFORM_SIBLING_EXT_MAX (a full-length ext replaces it outright; with // no extension ext is appended). False when the result does not fit in // outSize or names path itself (compared case-insensitively). bool platformPathSibling(char *out, int32_t outSize, const char *path, const char *ext); // Swap a fully written tmpPath in for path. The original is renamed // aside to bakPath first (DOS rename cannot overwrite) and removed only // once the replacement is in place; if the swap fails the original is // restored and tmpPath removed. False when the swap failed or path is a // directory (never moved aside). A crash between the two renames leaves // only bakPath; while path is missing a leftover bakPath is never // deleted, and prefsLoad falls back to reading it. bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath); // Return a pointer to the leaf (basename) portion of path: after the last // separator, or after a drive ("C:A.TXT" -> "A.TXT"). Never returns // NULL -- with neither, the whole path is the basename. const char *platformPathBaseName(const char *path); // Byte-for-byte file copy. Returns false on any open/read/write failure; // a partially written destination is removed so no truncated copy is // left behind. A copy onto itself (platformPathSame) does nothing and // returns true. bool platformCopyFile(const char *srcPath, const char *dstPath); // Slurp a whole file into a freshly-malloc'd, NUL-terminated buffer. // Returns NULL on error (file missing, OOM, read truncated). On success, // *outLen (if non-NULL) receives the byte length (not counting the NUL). // Caller must free() the returned buffer. Binary-safe: the NUL is past // the end of the reported length. char *platformReadFile(const char *path, int32_t *outLen); // Advance past leading ' ' and '\t' characters. Returns s unchanged if // it's NULL or already points at a non-whitespace byte. const char *dvxSkipWs(const char *s); // Bounded formatted append into buf at offset pos, never exceeding // bufSize. Returns the new position, or -1 if the result would truncate, // vsnprintf failed, or pos was already out of range. Passing a -1 back in // as pos propagates, collapsing a chain of appends to one truncation check. int32_t dvxStrAppendf(char *buf, int32_t bufSize, int32_t pos, const char *fmt, ...) __attribute__((format(printf, 4, 5))); // Strip trailing ' ', '\t', '\r', '\n' from buf in place. Returns the // new length. buf may be NULL or empty. int32_t dvxTrimRight(char *buf); // dst = src without leading blanks (' ', '\t') or trailing whitespace, // truncated to fit dstSize. A NULL src gives "". Returns the length. int32_t dvxCopyTrimmed(char *dst, int32_t dstSize, const char *src); // Command line arguments are separated by blanks; one with blanks inside // is written in double quotes. #define DVX_ARG_QUOTE '"' #define DVX_ARG_BLANKS " \t" // Copy the next argument of command line p into out (without its quotes; // *quoted tells whether it had them) and return the text after it, or // NULL when only blanks are left. Loop until NULL to walk every argument. const char *dvxNextArg(const char *p, char *out, int32_t outSize, bool *quoted); // Append arg to the command line in buf at pos as one argument that // dvxNextArg reads back whole: in quotes when it holds blanks (a long // name such as C:\My Programs\Thing.app). Returns the new length, or -1 // when it does not fit (as dvxStrAppendf). int32_t dvxArgAppend(char *buf, int32_t bufSize, int32_t pos, const char *arg); // Case-insensitive check that `name` ends with `ext` (which should // include the leading dot, e.g. ".app"). Returns false if either // argument is NULL or name is shorter than ext. bool dvxHasExt(const char *name, const char *ext); // Read all entries from `dirPath` (except "." and "..") into a // heap-allocated stb_ds array of malloc'd strings. Returns NULL on // open failure. An empty directory returns a valid empty array; // check arrlen() for count. Caller must pass the result to // dvxReadDirFree when done. char **dvxReadDir(const char *dirPath); // Free an array returned by dvxReadDir: frees every entry and the // stb_ds array header. Safe to call with NULL. void dvxReadDirFree(char **entries); // The platform's native directory separator as a string literal. Use // with string-literal concatenation in format strings or path constants: // snprintf(buf, sz, "%s" DVX_PATH_SEP "%s", dir, name); // #define SOMEDIR "CONFIG" DVX_PATH_SEP "WPAPER" // DJGPP accepts both '/' and '\\' for fopen/stat/etc., so either would // work, but '\\' is what DOS users expect to see in displayed paths. // Every other platform uses '/'. #ifdef __DJGPP__ #define DVX_PATH_SEP "\\" #else #define DVX_PATH_SEP "/" #endif // Simple glob pattern matching for filenames. Case-insensitive. // Supports * (zero or more chars) and ? (one char). bool platformGlobMatch(const char *pattern, const char *name); // Wildcard filter that matches every file ("*.*" also matches names // without a dot, as on DOS). #define PLATFORM_FILTER_ALL "*.*" // True when name matches filter: platformGlobMatch patterns separated by // ';', ',' or blanks ("*.txt;*.bat", "*.bmp, *.jpg"). A pattern equal to // PLATFORM_FILTER_ALL, and a NULL or blank filter, match everything. // Used by the file dialog and the file operations library. bool platformFilterMatch(const char *name, const char *filter); // ============================================================ // Drives and disk space // ============================================================ // Most drives a system can have (A: through Z:), and the longest drive // root ("C:\" on DOS, "/" elsewhere) plus its NUL. #define PLATFORM_DRIVE_MAX 26 #define PLATFORM_DRIVE_ROOT_MAX 4 typedef enum { PlatformDriveFixedE, PlatformDriveRemovableE, PlatformDriveRemoteE // network drive or CD-ROM } PlatformDriveTypeE; typedef struct { char root[PLATFORM_DRIVE_ROOT_MAX]; PlatformDriveTypeE type; } PlatformDriveT; // Fill out[0..max) with the drives that exist, in letter order, and return // the count. Never touches the media (DOS: IOCTL calls only), so empty // floppy and CD-ROM drives are listed without delay; the second letter of // a shared floppy drive (B: on a one-floppy system) is left out, as using // it would make DOS ask for a disk swap. Elsewhere: one drive, "/". int32_t platformDriveList(PlatformDriveT *out, int32_t max); // Size and free space in bytes of the disk holding path (on DOS the drive // letter of path, or the current drive; elsewhere the nearest existing // folder of path). False when unknown (no such drive, no disk). bool platformDiskSpace(const char *path, uint64_t *totalBytes, uint64_t *freeBytes); // Letter of the drive DOS started from ('C'), whose root holds // AUTOEXEC.BAT; 0 where there is none (every platform but DOS). char platformBootDrive(void); // Return the platform's native line ending string. // "\r\n" on DOS. const char *platformLineEnding(void); // Remove every '\r' from buf[0..len) in place and NUL-terminate the // result, so buf must hold at least len + 1 bytes. Returns the new length. int32_t platformStripLineEndings(char *buf, int32_t len); // ============================================================ // DXE module support // ============================================================ // Symbol-name decoration for dlsym(). DJGPP's COFF toolchain prefixes // every C symbol with an underscore and its dlsym() expects the // decorated name; ELF platforms use the bare name. DVX_SYM("appMain") // yields the correct string literal for the current platform. Code that // builds names at runtime (snprintf) uses DVX_SYM_PREFIX instead. #ifdef __DJGPP__ #define DVX_SYM_PREFIX "_" #else #define DVX_SYM_PREFIX "" #endif #define DVX_SYM(name) DVX_SYM_PREFIX name // Register platform and C runtime symbols with the dynamic module // loader so that DXE modules can resolve them at load time. On DOS // this calls dlregsym() with the full DJGPP libc/libm/libgcc/platform // export table. void platformRegisterDxeExports(void); // Pin / unpin a code (isCode) or data range into physical memory so an // interrupt handler can touch it without faulting under a paging host. // Shared by the key-up INT 9 hook and the serial driver's ISR. Returns // 0 on success, -1 on failure. No-ops returning 0 on hosts without // paging concerns. int32_t platformLockRegion(const void *addr, uint32_t size, bool isCode); int32_t platformUnlockRegion(const void *addr, uint32_t size, bool isCode); // ============================================================ // Crash recovery // ============================================================ // Log function signature for crash diagnostics. typedef void (*PlatformLogFnT)(const char *fmt, ...); // Install signal handlers for fatal exceptions (SIGSEGV, SIGFPE, SIGILL). // On crash, logs platform-specific diagnostics (register dump on DJGPP) // via logFn, stores the signal number in *crashSignal, then longjmps to // recoveryBuf. The caller is responsible for app-level diagnostics in // the setjmp recovery block. void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn); // Log platform-specific crash diagnostics (signal name and register dump // on DJGPP). Called internally by the crash handler before longjmp; // also available for manual invocation if needed. void platformLogCrashDetail(int sig, PlatformLogFnT logFn); // ============================================================ // Program exit // ============================================================ // // Loaded modules do not get the C library's exit() and abort(): on DOS the // DXE export table maps them to platformAppExit and platformAppAbort, and // the host build links every app with the same substitution (see // HOST_APP_LDFLAGS in common.mk). They call the hook the shell installs, // which ends just the program that called them, the way End Task does, // when a program is running; otherwise (or with no hook) they end the // process as the C library functions would. // Exit status reported for abort() #define PLATFORM_ABORT_STATUS 3 // Called with the exit status; returns only when no program is running. typedef void (*PlatformAppExitFnT)(int status); void platformSetAppExitHook(PlatformAppExitFnT fn); // exit() / abort() as seen by loaded modules. void platformAppAbort(void) __attribute__((noreturn)); void platformAppExit(int status) __attribute__((noreturn)); // Out-of-memory hook for stb_ds (STBDS_OUT_OF_MEMORY, set in // stb_ds_wrap.h). arrput/hmput/shput have no error channel, so a failed // growth logs "out of memory (N bytes) in dynamic array" via dvxLog and // raises SIGSEGV: under platformInstallCrashHandler that takes the normal // crash-recovery path (the shell kills the current app, or with no app // current returns to its main loop). With no handler the process // terminates. // Never returns. void platformOutOfMemory(size_t bytes) __attribute__((noreturn)); // ============================================================ // VGA splash screen (mode 13h, 320x200, 256-color) // ============================================================ // SPLASH.RAW format (written by the bmp2raw tool): SPLASH_PAL_BYTES of // palette (R, G, B per entry, 6-bit DAC values), then SPLASH_PIXEL_BYTES // of 8-bit pixels, top-to-bottom scanlines. #define SPLASH_W VGA13_W #define SPLASH_H VGA13_H #define SPLASH_BPP 8 #define SPLASH_PAL_ENTRIES 256 #define SPLASH_PAL_CHANNELS 3 #define SPLASH_PAL_BYTES (SPLASH_PAL_ENTRIES * SPLASH_PAL_CHANNELS) #define SPLASH_PIXEL_BYTES (SPLASH_W * SPLASH_H) #define SPLASH_DAC_SHIFT 2 // 8-bit RGB -> 6-bit DAC value // Enter VGA mode 13h (SPLASH_W x SPLASH_H x 256). void platformSplashInit(void); // Return to text mode 03h. void platformSplashShutdown(void); // Load and display a SPLASH.RAW file (format above). // Returns true on success, false if the file could not be loaded. bool platformSplashLoadRaw(const char *path); // Fill a rectangle on the VGA mode 13h screen. void platformSplashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color); // Palette index of the loaded splash image closest to an 8-bit RGB color, // so overlays drawn on the splash do not depend on the image's palette // layout. 0 when no splash palette is loaded. uint8_t platformSplashNearestColor(uint8_t r, uint8_t g, uint8_t b); // ============================================================ // DXE symbol overrides // ============================================================ // A name/function-pointer pair for registering symbol overrides with // the dynamic module loader. On DJGPP this maps directly to the // dxe_symbol_table struct used by dlregsym(). typedef struct { const char *name; void *func; } PlatformSymOverrideT; // Register function pointer overrides for subsequently loaded modules. // entries is an array of {name, funcPtr} pairs terminated by {NULL, NULL}. // On DJGPP this wraps dlregsym(). On platforms without DXE, this is a // no-op (RTLD_GLOBAL or equivalent handles symbol resolution). Nothing // in DVX itself calls it: it is SDK API for embedders and test harnesses // that need to substitute a symbol (e.g. a stub) before loading a module. void platformRegisterSymOverrides(const PlatformSymOverrideT *entries); #endif // DVX_PLAT_H