301 lines
11 KiB
Markdown
301 lines
11 KiB
Markdown
# 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));
|
|
|
|
// Rectangle outline
|
|
wdrvRectangle(drv, 10, 10, 100, 50, MAKE_RGB(255, 255, 0));
|
|
|
|
// 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);
|
|
```
|
|
|
|
### Text Rendering
|
|
|
|
```c
|
|
// Opaque text: white on blue (built-in 8x16 font)
|
|
wdrvExtTextOut(drv, 10, 10, "Hello", 5,
|
|
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 170), true, NULL);
|
|
|
|
// Load and use a TrueType font
|
|
WdrvFontT ttf = wdrvLoadFontTtf("LIBSANS.TTF", 20);
|
|
wdrvExtTextOut(drv, 10, 30, "TrueType", 8,
|
|
MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), true, ttf);
|
|
wdrvUnloadFont(ttf);
|
|
|
|
// Load from Windows .FON file
|
|
WdrvFontT fon = wdrvLoadFontFon("COURE.FON", 1);
|
|
wdrvExtTextOut(drv, 10, 50, "Courier", 7,
|
|
MAKE_RGB(0, 255, 255), MAKE_RGB(0, 0, 0), true, fon);
|
|
wdrvUnloadFont(fon);
|
|
```
|
|
|
|
### 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` | 250 | Public API: initialization, driver loading, drawing, errors |
|
|
| `windrv.c` | 4478 | 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` | 334 | DDI structures: GDIINFO, PDEVICE, DRAWMODE, brushes, pens, fonts |
|
|
| `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: rectangles, pixels, lines, blits, text, fonts, colors |
|
|
| `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 |
|
|
| `fon/*.FON` | Windows 3.x bitmap fonts (.FON containers) |
|
|
| `ttf/*.TTF` | TrueType fonts (Liberation family) |
|
|
| `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.
|
|
- **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.
|