# win31drv A library and demo for loading and using real Windows 3.x display drivers (.DRV files) from 32-bit DOS programs compiled with DJGPP. Windows 3.1 shipped with hardware-accelerated display drivers for chipsets like the S3 Trio64 and Tseng ET4000. These drivers are 16-bit NE-format DLLs that implement the Windows DDI (Device Driver Interface). This project loads those drivers into protected mode memory, thunks between 32-bit and 16-bit code, stubs the Windows API functions the drivers import, and exposes a clean C API for drawing. ## Tested Drivers | Driver | Chipset | Resolution | DOSBox-X machine type | |---|---|---|---| | S3TRIO.DRV | S3 Trio64 | 800x600 8bpp | `svga_s3trio64` | | VBESVGA.DRV | VESA VBE 2.0 | 800x600 8bpp | `svga_s3trio64` | | VGA.DRV | Standard VGA | 640x480 4-plane | `svga_s3trio64` | | ET4000.DRV | Tseng ET4000 | 640x480 8bpp | `svga_et4000` | ## Building ### Prerequisites - **DJGPP cross-compiler**: GCC 12.2.0 targeting `i586-pc-msdosdjgpp`. Expected at `~/djgpp/djgpp/` (override with `DJGPP_PREFIX`). - **CWSDPMI**: Included in `tools/cwsdpmi.zip`, extracted automatically. - **DOSBox-X** (for testing): `flatpak run com.dosbox_x.DOSBox-X` ### Build ``` make # Builds libwindrv.a and demo.exe make clean # Removes all build artifacts ``` The build produces: - `win31drv/libwindrv.a` -- the library - `bin/demo.exe` -- the demo program - `bin/CWSDPMI.EXE` -- DPMI host (required to run the demo) Driver files from `drivers/` are copied to `bin/` during the build. ### Running Start DOSBox-X with the included configuration: ``` flatpak run com.dosbox_x.DOSBox-X -conf dosbox-x.conf ``` Or run manually inside DOSBox-X: ``` C:\BIN> demo.exe S3TRIO.DRV C:\BIN> demo.exe -d VBESVGA.DRV (with debug logging) ``` Output is logged to `OUTPUT.LOG` in the current directory. ## API Overview The public API is declared in `win31drv/windrv.h`. ### Lifecycle ```c #include "win31drv/windrv.h" wdrvInit(); // Initialize library WdrvHandleT drv = wdrvLoadDriver("S3TRIO.DRV"); wdrvEnable(drv, 0, 0, 0); // Set video mode (0 = driver defaults) // ... draw ... wdrvDisable(drv); // Restore text mode wdrvUnloadDriver(drv); // Free driver resources wdrvShutdown(); // Shut down library ``` ### Drawing ```c // Solid rectangle fill (BitBlt with PATCOPY) wdrvFillRect(drv, x, y, w, h, MAKE_RGB(255, 0, 0)); // Set/get individual pixels wdrvSetPixel(drv, x, y, MAKE_RGB(0, 255, 0)); uint32_t color = wdrvGetPixel(drv, x, y); // Polyline (Output DDI) Point16T pts[] = {{0, 0}, {100, 100}}; wdrvPolyline(drv, pts, 2, MAKE_RGB(0, 0, 255)); // Screen-to-screen blit WdrvBitBltParamsT bp = { .srcX=0, .srcY=0, .dstX=320, .dstY=240, .width=160, .height=120, .rop3=SRCCOPY }; wdrvBitBlt(drv, &bp); // Direct framebuffer access void *fb = wdrvGetFramebuffer(drv); int32_t pitch = wdrvGetPitch(drv); ``` ### Error Handling All functions return `WDRV_OK` (0) on success or a negative error code. Use `wdrvGetLastErrorString()` for a human-readable description. ## Architecture ### Component Overview ``` demo.c User program | v win31drv/windrv.c Public API, DDI call wrappers, interrupt handlers | +-- win31drv/neload.c NE executable loader (segments, relocations, exports) +-- win31drv/thunk.c 32-bit <-> 16-bit thunking via DPMI +-- win31drv/winstub.c Windows API stubs (KERNEL, GDI, USER, DIBENG) +-- win31drv/log.c Logging to file ``` ### How It Works 1. **NE Loading** (`neload.c`): The driver .DRV file is a 16-bit NE (New Executable) format DLL. The loader reads the MZ+NE headers, allocates 16-bit LDT descriptors for each segment via DPMI, loads segment data from the file, and processes relocation records to fix up inter-segment references and imported function addresses. 2. **Import Resolution** (`winstub.c`): Windows 3.x drivers import functions from KERNEL, GDI, USER, and sometimes DIBENG. The stub layer provides minimal implementations of ~80 functions: memory management (GlobalAlloc/Lock/Free, GlobalDOSAlloc), selector management (AllocSelector, PrestoChangoSelector, SetSelectorBase), system queries (GetVersion, GetWinFlags, GetPrivateProfileString), and more. Each stub is registered as a 16-bit callback via the thunking layer. 3. **Thunking** (`thunk.c`): The 32-to-16 bit thunking layer bridges DJGPP's 32-bit flat-memory environment with the driver's 16-bit segmented code. A small relay thunk in a 16-bit code segment handles the transition: it receives parameters via a shared data area, sets up a 16-bit stack with DS=SS=DGROUP, far-calls the target function (Pascal calling convention), and captures the DX:AX return value. For callbacks (16-bit driver calling 32-bit stubs), a software interrupt mechanism routes through a registered INT handler that switches to a 32-bit stack and invokes the C callback. 4. **DDI Wrappers** (`windrv.c`): The main module wraps each DDI function (Enable, Disable, BitBlt, Output, Pixel, RealizeObject, ColorInfo, SetPalette) with proper parameter marshalling. GDI objects (PDEVICE, physical brush, physical pen, draw mode) are embedded within the driver's DGROUP segment so all far pointers share the same selector, matching how Windows 3.1's GDI heap works. 5. **Interrupt Handlers** (`windrv.c`): Several raw assembly interrupt handlers bridge the gap between the driver's expectations and the DOS environment: - **INT 10h reflector**: Intercepts the driver's video BIOS calls, translates PM selectors to real-mode segments (with transfer buffer bouncing for extended memory), and reflects through DPMI to the real-mode BIOS. - **INT 64h proxy** (DPMI 0x300h): Works around a CWSDPMI bug where INT 31h AX=0300h fails from 16-bit code segments. The driver's DoInt10h is patched to use INT 64h instead. - **INT 2Fh handler**: Responds to the Windows Enhanced Mode installation check (AX=1600h) and VDS/VMM API calls that the driver issues during initialization. - **Exception handlers** (#GP, #PF): Capture fault state with full register dumps before DJGPP's handler (which may crash on faults from 16-bit code). ### Binary Patching The loader applies several patches to the driver's code segments at load time to adapt them for the DOS/DPMI environment: - **Prolog/epilog patching**: Converts Win16 `PROLOG_0` sequences (`mov ax, ds; nop; inc bp`) to load the correct DGROUP selector and removes the `inc bp`/`dec bp` frame markers that corrupt frame pointers outside of Windows' stack walker. - **DoInt10h INT 31h -> INT 64h**: Redirects the driver's DPMI simulate-real-mode-interrupt call to our proxy handler. - **BIOS data area access**: Replaces hardcoded `mov ax, 0040h` with our DPMI selector for the BIOS data area (real-mode segment 0x0040 is invalid in protected mode). - **VFLATD bypass**: Forces the DPMI linear framebuffer path instead of the VFLATD VxD path (which requires Windows). - **VFLATD stack bug**: Patches a 20-byte stack imbalance in the VFLATD initialization subroutine. - **WinFlags repatching**: For VGA-class drivers, changes `WF_ENHANCED` to `WF_STANDARD` after Enable reveals the driver type, preventing a hang in the VDD polling loop. ### DGROUP Layout Windows 3.x drivers expect all GDI objects to reside in DGROUP (the automatic data segment), and SS=DS=DGROUP during function execution. The library extends DGROUP to 64K and allocates objects at fixed offsets: ``` Original driver data: 0x0000 - varies GDI objects: PDEVICE: objBase + 0x0000 (4096 bytes) Physical brush: objBase + 0x1000 (128 bytes) Logical brush: objBase + 0x1080 (16 bytes) Draw mode: objBase + 0x1090 (48 bytes) Physical pen: objBase + 0x10C0 (128 bytes) Logical pen: objBase + 0x1140 (16 bytes) Physical color: objBase + 0x1150 (8 bytes) ``` ### S3-Specific Handling The S3 Trio64 driver writes an 8x8 dithered brush pattern to a fixed VRAM location during accelerated pattern fills. The library detects S3 hardware by probing CR30 (chip ID register) and shifts the CRTC display start down 10 scanlines so this scratch area is off-screen. All drawing Y coordinates are offset to compensate. The GP_STAT register (port 0x9AE8) is polled to wait for the graphics engine to become idle between operations. ## File Reference ### Library (`win31drv/`) | File | Lines | Description | |---|---|---| | `windrv.h` | 189 | Public API: initialization, driver loading, drawing, errors | | `windrv.c` | 3223 | DDI wrappers, GDI object management, interrupt handlers, binary patching | | `neload.h` | 118 | NE loader API | | `neload.c` | 962 | NE format parser: headers, segments, relocations, exports | | `neformat.h` | 215 | NE/MZ header structures, segment/relocation/entry table formats, DDI ordinals | | `thunk.h` | 133 | Thunk layer API | | `thunk.c` | 1021 | 32/16-bit relay, callback dispatch, DOS memory management | | `winstub.h` | 257 | Stub declarations, KERNEL/GDI/USER/DIBENG ordinal constants | | `winstub.c` | 1400 | ~80 Windows API stub implementations | | `winddi.h` | 261 | DDI structures: GDIINFO, PDEVICE, DRAWMODE, brushes, pens | | `wintypes.h` | 153 | Win16 basic types, far pointers, colors, ROP codes | | `log.h` | 37 | Logging API | | `log.c` | 83 | Log file output | ### Demo and Build | File | Description | |---|---| | `demo.c` | Demo program: loads driver, draws rectangles/pixels/lines/blits | | `Makefile` | Top-level build: compiles demo, links with libwindrv.a | | `win31drv/Makefile` | Library build: compiles sources into libwindrv.a | | `dosbox-x.conf` | DOSBox-X configuration for S3 Trio64 testing | | `tools/TEST.BAT` | Quick-test batch file for DOSBox-X | | `drivers/*.DRV` | Windows 3.x display driver binaries | | `tools/cwsdpmi.zip` | CWSDPMI DPMI host (extracted during build) | | `tools/lib/libfl.so.2` | Shared library needed by DJGPP binutils | ## Known Issues - **Mode mismatch**: The S3 driver configures 800x600 hardware but GDIINFO reports 640x480. Drawing works within the 640x480 region. - **ET4000 is software-only**: DOSBox-X does not emulate the ET4000 accelerator engine; all drawing is CPU-rendered by the driver. - **No text output**: ExtTextOut is not yet wired up. - **Single driver instance**: Only one driver can be loaded at a time. ## License The library code in this repository is original work. The Windows 3.x display driver binaries in `drivers/` are the property of their respective copyright holders and are included for interoperability testing purposes.