Initial commit.

This commit is contained in:
Scott Duensing 2026-02-21 18:01:54 -06:00
commit 628ef231b9
30 changed files with 8836 additions and 0 deletions

106
.claude/settings.local.json Normal file

File diff suppressed because one or more lines are too long

10
.gitattributes vendored Normal file
View file

@ -0,0 +1,10 @@
*.bmp filter=lfs diff=lfs merge=lfs -text
*.CUR filter=lfs diff=lfs merge=lfs -text
*.cur filter=lfs diff=lfs merge=lfs -text
*.ico filter=lfs diff=lfs merge=lfs -text
*.DRV filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.so.* filter=lfs diff=lfs merge=lfs -text
*.BMP filter=lfs diff=lfs merge=lfs -text
*.ICO filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text

12
.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
# Build artifacts
obj/
win31drv/obj/
win31drv/libwindrv.a
bin/
# Runtime logs
OUTPUT.LOG
# Editor backups
*~
*.swp

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "vbesvga.drv"]
path = vbesvga.drv
url = https://github.com/PluMGMK/vbesvga.drv.git

53
Makefile Normal file
View file

@ -0,0 +1,53 @@
# ============================================================================
# Makefile for windrv demo - Windows 3.x display driver loader for DJGPP/DOS
# ============================================================================
DJGPP_PREFIX ?= $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
CFLAGS = -Wall -Wextra -O2 -std=gnu99 -Iwin31drv
LDFLAGS =
# DJGPP binutils need libfl.so.2 which may not be installed system-wide
export LD_LIBRARY_PATH := $(realpath tools/lib):$(LD_LIBRARY_PATH)
OBJDIR = obj
BINDIR = bin
LIBDIR = win31drv
LIB = $(LIBDIR)/libwindrv.a
DEMO_SRC = demo.c
DEMO_OBJ = $(OBJDIR)/demo.o
DEMO_EXE = $(BINDIR)/demo.exe
.PHONY: all clean lib demo
all: lib demo
lib:
$(MAKE) -C $(LIBDIR)
demo: $(DEMO_EXE)
$(DEMO_EXE): $(DEMO_OBJ) lib | $(BINDIR)
$(CC) $(CFLAGS) -o $@ $(DEMO_OBJ) -L$(LIBDIR) -lwindrv $(LDFLAGS)
unzip -oj tools/cwsdpmi.zip bin/CWSDPMI.EXE -d $(BINDIR) 2>/dev/null; true
cp tools/TEST.BAT $(BINDIR)/
-cp -n drivers/*.DRV $(BINDIR)/ 2>/dev/null; true
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
$(BINDIR):
mkdir -p $(BINDIR)
# Dependencies
$(OBJDIR)/demo.o: demo.c win31drv/windrv.h win31drv/wintypes.h
clean:
$(MAKE) -C $(LIBDIR) clean
-rm -rf $(OBJDIR) $(BINDIR)

323
demo.c Normal file
View file

@ -0,0 +1,323 @@
// ============================================================================
// demo.c - Demonstration of using Windows 3.x display drivers from DOS
//
// This program loads a Windows 3.x accelerated display driver (.DRV file)
// and uses its hardware-accelerated functions to draw on screen.
//
// Usage: demo [-d] <driver.drv>
// -d Enable debug output
//
// Example:
// demo vga.drv
// demo -d s3trio.drv
// ============================================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <pc.h>
#include <dpmi.h>
#include "win31drv/windrv.h"
#include "win31drv/wintypes.h"
#include "win31drv/log.h"
__attribute__((noinline))
static void demoDrawing(WdrvHandleT drv);
static void printDriverInfo(WdrvHandleT drv);
static void printUsage(const char *progName);
static void setupPalette(WdrvHandleT drv);
int main(int argc, char *argv[])
{
const char *driverPath = NULL;
bool debug = false;
// Parse arguments
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-d") == 0) {
debug = true;
} else if (argv[i][0] != '-') {
driverPath = argv[i];
} else {
printUsage(argv[0]);
return 1;
}
}
if (!driverPath) {
printUsage(argv[0]);
return 1;
}
logInit("OUTPUT.LOG");
// Initialize the library
logMsg("Initializing windrv library...\n");
int32_t err = wdrvInit();
if (err != WDRV_OK) {
logErr("Failed to initialize: %s\n", wdrvGetLastErrorString());
return 1;
}
if (debug) {
wdrvSetDebug(true);
}
// Load the driver
logMsg("Loading driver: %s\n", driverPath);
WdrvHandleT drv = wdrvLoadDriver(driverPath);
if (!drv) {
logErr("Failed to load driver: %s\n", wdrvGetLastErrorString());
wdrvShutdown();
return 1;
}
// Print driver info
printDriverInfo(drv);
// Redirect stdout to the log file before entering SVGA mode.
// In graphics mode, console output through INT 21h can crash
// because the console handler tries to access VGA text memory
// that's now part of the SVGA framebuffer.
freopen("OUTPUT.LOG", "a", stdout);
// Enable the driver (set video mode)
logMsg("Enabling driver...\n");
err = wdrvEnable(drv, 0, 0, 0); // Use driver defaults
if (err != WDRV_OK) {
logErr("Failed to enable driver: %s\n", wdrvGetLastErrorString());
wdrvUnloadDriver(drv);
wdrvShutdown();
return 1;
}
// Set up a 256-color palette via the driver's SetPalette DDI.
// This updates both the driver's internal color table (used by
// RealizeObject for color matching) and the VGA DAC hardware.
setupPalette(drv);
// Run drawing demos
demoDrawing(drv);
logMsg("Drawing complete. Press any key...\n");
// Wait for a keypress so we can see the output
while (!kbhit()) {
// Busy-wait; kbhit() does keyboard I/O which keeps DOSBox-X responsive
}
(void)getkey(); // consume the key
// Disable the driver (restore text mode)
logMsg("Calling wdrvDisable...\n");
err = wdrvDisable(drv);
if (err != WDRV_OK) {
logMsg("wdrvDisable failed (err=%d), forcing text mode via INT 10h\n", (int)err);
__dpmi_regs dr;
memset(&dr, 0, sizeof(dr));
dr.x.ax = 0x0003; // INT 10h AH=00 AL=03: set 80x25 text mode
__dpmi_int(0x10, &dr);
}
// Clean up
logMsg("Unloading driver...\n");
wdrvUnloadDriver(drv);
wdrvShutdown();
logMsg("Done.\n");
logShutdown();
return 0;
}
static void printUsage(const char *progName)
{
logErr("Usage: %s [-d] <driver.drv>\n", progName);
logErr(" -d Enable debug output\n");
logErr("\nLoads a Windows 3.x display driver and demonstrates\n");
logErr("its accelerated drawing functions from DOS.\n");
}
static void printDriverInfo(WdrvHandleT drv)
{
WdrvInfoT info;
wdrvGetInfo(drv, &info);
logMsg("\n=== Driver Information ===\n");
logMsg(" Name: %s\n", info.driverName);
logMsg(" Version: %u.%u\n", info.driverVersion >> 8,
info.driverVersion & 0xFF);
logMsg(" Resolution: %" PRId32 "x%" PRId32 "\n", info.maxWidth, info.maxHeight);
logMsg(" Color depth: %" PRId32 " bpp\n", info.maxBpp);
logMsg(" Colors: %" PRId32 "\n", info.numColors);
logMsg(" Raster caps: 0x%04" PRIX32 "\n", info.rasterCaps);
logMsg(" Capabilities:\n");
logMsg(" BitBlt: %s\n", info.hasBitBlt ? "yes" : "no");
logMsg(" Output: %s\n", info.hasOutput ? "yes" : "no");
logMsg(" Pixel: %s\n", info.hasPixel ? "yes" : "no");
logMsg(" StretchBlt: %s\n", info.hasStretchBlt ? "yes" : "no");
logMsg(" ExtTextOut: %s\n", info.hasExtTextOut ? "yes" : "no");
logMsg(" SetPalette: %s\n", info.hasSetPalette ? "yes" : "no");
logMsg(" SetCursor: %s\n", info.hasSetCursor ? "yes" : "no");
}
// noinline: when inlined into main with -O2, the optimizer mishandles
// callee-saved registers across the thunk calls in the Demo 2 → Demo 3
// transition, causing the handle pointer to be corrupted.
__attribute__((noinline))
static void demoDrawing(WdrvHandleT drv)
{
WdrvInfoT info;
wdrvGetInfo(drv, &info);
int16_t screenW = (int16_t)info.maxWidth;
int16_t screenH = (int16_t)info.maxHeight;
if (screenW == 0) {
screenW = 640;
}
if (screenH == 0) {
screenH = 480;
}
logMsg("demoDrawing: screenW=%d screenH=%d hasBitBlt=%d hasOutput=%d hasPixel=%d\n",
screenW, screenH, info.hasBitBlt, info.hasOutput, info.hasPixel);
// Demo 1: Fill rectangles
if (info.hasBitBlt) {
logMsg("Demo 1: Fill rectangles\n");
// Clear screen to white
wdrvFillRect(drv, 0, 0, screenW, screenH, MAKE_RGB(255, 255, 255));
static const uint32_t vgaColors[] = {
MAKE_RGB( 0, 0, 0), // black
MAKE_RGB( 0, 0, 255), // blue
MAKE_RGB( 0, 255, 0), // green
MAKE_RGB( 0, 255, 255), // cyan
MAKE_RGB(255, 0, 0), // red
MAKE_RGB(255, 0, 255), // magenta
MAKE_RGB(255, 255, 0), // yellow
MAKE_RGB(170, 170, 170), // light gray
MAKE_RGB( 85, 85, 85), // dark gray
MAKE_RGB( 85, 85, 255), // light blue
MAKE_RGB( 85, 255, 85), // light green
MAKE_RGB( 85, 255, 255), // light cyan
MAKE_RGB(255, 85, 85), // light red
MAKE_RGB(255, 85, 255), // light magenta
MAKE_RGB(255, 255, 85), // yellow-ish
MAKE_RGB(128, 128, 128), // mid gray
};
int16_t boxW = screenW / 4;
int16_t boxH = screenH / 4;
for (int16_t row = 0; row < 4; row++) {
for (int16_t col = 0; col < 4; col++) {
int idx = row * 4 + col;
wdrvFillRect(drv, col * boxW + 4, row * boxH + 4,
boxW - 8, boxH - 8, vgaColors[idx]);
}
}
logMsg(" Drew %d colored rectangles\n", 16);
}
// Demo 2: Draw pixel patterns
if (info.hasPixel) {
logMsg("Demo 2: Pixel patterns (8x8)\n");
int pixCount = 0;
for (int16_t y = 0; y < 8 && y < screenH; y++) {
for (int16_t x = 0; x < 8 && x < screenW; x++) {
uint8_t r = (uint8_t)(x * 32);
uint8_t g = (uint8_t)(y * 32);
uint8_t b = (uint8_t)((x + y) * 16);
wdrvSetPixel(drv, x + screenW - 18, y + 10, MAKE_RGB(r, g, b));
pixCount++;
}
}
logMsg(" Drew %d pixels\n", pixCount);
}
// Demo 3: Draw lines using Output (polyline with realized pen)
if (info.hasOutput) {
logMsg("Demo 3: Lines (starburst)\n");
int16_t cx = screenW / 2;
int16_t cy = screenH / 2;
int16_t radius = (screenH < screenW ? screenH : screenW) / 3;
int lineCount = 0;
for (int angle = 0; angle < 360; angle += 15) {
int32_t dx = 0;
int32_t dy = 0;
int a = angle % 360;
int qa = a % 90;
int32_t s = (int32_t)qa * radius / 90;
int32_t c = (int32_t)(90 - qa) * radius / 90;
if (a < 90) {
dx = s;
dy = -c;
} else if (a < 180) {
dx = c;
dy = s;
} else if (a < 270) {
dx = -s;
dy = c;
} else {
dx = -c;
dy = -s;
}
Point16T pts[2];
pts[0].x = cx;
pts[0].y = cy;
pts[1].x = (int16_t)(cx + dx);
pts[1].y = (int16_t)(cy + dy);
uint32_t lineColor = MAKE_RGB(
(uint8_t)(angle * 255 / 360),
(uint8_t)(255 - angle * 255 / 360),
128);
wdrvPolyline(drv, pts, 2, lineColor);
lineCount++;
}
logMsg(" Drew %d lines\n", lineCount);
}
// Demo 4: Screen-to-screen blit test
if (info.hasBitBlt) {
logMsg("Demo 4: Screen-to-screen blit\n");
WdrvBitBltParamsT bp;
memset(&bp, 0, sizeof(bp));
bp.srcX = 0;
bp.srcY = 0;
bp.dstX = screenW / 2;
bp.dstY = screenH / 2;
bp.width = screenW / 4;
bp.height = screenH / 4;
bp.rop3 = SRCCOPY;
wdrvBitBlt(drv, &bp);
logMsg(" Screen blit done\n");
}
}
static void setupPalette(WdrvHandleT drv)
{
// The driver sets up its own palette during Enable (via VBE 4F09)
// and stores an internal color table that RealizeObject uses for
// color matching. We leave the palette as-is so the DAC and the
// internal table stay in sync. RealizeObject will find the best
// matching index, and the DAC will display the correct color.
(void)drv;
}

40
dosbox-x.conf Normal file
View file

@ -0,0 +1,40 @@
# DOSBox-X configuration for win31drv development
# S3 Trio64 SVGA with VESA support
[sdl]
output = opengl
windowresolution = 1024x768
[dosbox]
machine = svga_s3trio64
memsize = 64
quit warning = false
[cpu]
core = normal
cputype = pentium
cycles = max
[render]
aspect = true
scaler = none
[video]
vmemsize = 8
vmemsizekb = 0
vesa oldvbe = false
vesa oldvbe10 = false
[dos]
umb = true
xms = true
ems = true
[autoexec]
@echo off
mount c /home/scott/claude/windriver
c:
cd bin
DEMO.EXE -d VGA.DRV
rem exit

BIN
drivers/ET4000.DRV (Stored with Git LFS) Normal file

Binary file not shown.

BIN
drivers/S3TRIO.DRV (Stored with Git LFS) Normal file

Binary file not shown.

BIN
drivers/VBESVGA.DRV (Stored with Git LFS) Normal file

Binary file not shown.

BIN
drivers/VGA.DRV (Stored with Git LFS) Normal file

Binary file not shown.

1
tools/TEST.BAT Normal file
View file

@ -0,0 +1 @@
demo.exe -d VBESVGA.DRV

BIN
tools/cwsdpmi.zip (Stored with Git LFS) Normal file

Binary file not shown.

BIN
tools/lib/libfl.so.2 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
tools/lib/libfl.so.2.0.0 (Stored with Git LFS) Normal file

Binary file not shown.

1
vbesvga.drv Submodule

@ -0,0 +1 @@
Subproject commit 2da3782d3f17531f32bbd9d67a0c242656bb7b07

51
win31drv/Makefile Normal file
View file

@ -0,0 +1,51 @@
# ============================================================================
# Makefile for win31drv - Windows 3.x display driver library for DJGPP/DOS
# ============================================================================
DJGPP_PREFIX ?= $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
AR = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ar
CFLAGS = -Wall -Wextra -O2 -std=gnu99 -I.
# DOSBox-X's S3 Trio64 emulation corrupts specific memory addresses
# during 16-bit driver calls via thunkCall16. With -O2 GCSE enabled,
# the code layout places stack locals and register spills at addresses
# that overlap the corruption targets, causing wrong values in drawing
# parameters. Disabling GCSE for windrv.c changes the layout enough
# to avoid the overlap. Only windrv.c is affected (it has the drawing
# functions that call thunkCall16 with interleaved parameter setup).
WINDRV_CFLAGS = $(CFLAGS) -fno-gcse
# DJGPP binutils need libfl.so.2 which may not be installed system-wide
export LD_LIBRARY_PATH := $(realpath ../tools/lib):$(LD_LIBRARY_PATH)
OBJDIR = obj
SRCS = log.c neload.c thunk.c winstub.c windrv.c
OBJS = $(addprefix $(OBJDIR)/,$(SRCS:.c=.o))
LIB = libwindrv.a
.PHONY: all clean
all: $(LIB)
$(LIB): $(OBJS)
$(AR) rcs $@ $^
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
# Dependencies
$(OBJDIR)/log.o: log.c log.h
$(OBJDIR)/neload.o: neload.c neload.h neformat.h wintypes.h log.h
$(OBJDIR)/thunk.o: thunk.c thunk.h wintypes.h log.h
$(OBJDIR)/winstub.o: winstub.c winstub.h thunk.h wintypes.h log.h
$(OBJDIR)/windrv.o: windrv.c windrv.h wintypes.h winddi.h neformat.h neload.h thunk.h winstub.h log.h
$(CC) $(WINDRV_CFLAGS) -c -o $@ $<
clean:
-rm -rf $(OBJDIR) $(LIB)

83
win31drv/log.c Normal file
View file

@ -0,0 +1,83 @@
// ============================================================================
// log.c - Logging to file
// ============================================================================
#include <stdio.h>
#include <stdarg.h>
#include "log.h"
static FILE *gLogFile = NULL;
static LogPreIoFuncT gPreIoHook = NULL;
void logInit(const char *filename)
{
if (filename) {
gLogFile = fopen(filename, "w");
}
}
void logErr(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
logErrV(fmt, ap);
va_end(ap);
}
void logErrV(const char *fmt, va_list ap)
{
if (gLogFile) {
if (gPreIoHook) {
gPreIoHook();
}
vfprintf(gLogFile, fmt, ap);
fflush(gLogFile);
}
}
FILE *logGetFile(void)
{
return gLogFile;
}
void logMsg(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
logMsgV(fmt, ap);
va_end(ap);
}
void logMsgV(const char *fmt, va_list ap)
{
if (gLogFile) {
if (gPreIoHook) {
gPreIoHook();
}
vfprintf(gLogFile, fmt, ap);
fflush(gLogFile);
}
}
void logSetPreIoHook(LogPreIoFuncT func)
{
gPreIoHook = func;
}
void logShutdown(void)
{
if (gLogFile) {
fflush(gLogFile);
fclose(gLogFile);
gLogFile = NULL;
}
}

37
win31drv/log.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef LOG_H
#define LOG_H
#include <stdarg.h>
// ============================================================================
// Logging layer - writes to a log file
//
// Call logInit() with a filename to enable logging.
// ============================================================================
// Initialize logging. If filename is non-NULL, opens it for writing.
// Can be called before any other log function. If not called, output
// goes only to stdout/stderr.
void logInit(const char *filename);
// Shut down logging, flush and close the log file.
void logShutdown(void);
// Write to stdout and the log file (like printf).
void logMsg(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
// Write to stderr and the log file (like fprintf(stderr, ...)).
void logErr(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
// Varargs versions for use by other wrappers.
void logMsgV(const char *fmt, va_list ap);
void logErrV(const char *fmt, va_list ap);
// Get the underlying log file handle (or NULL if not initialized).
FILE *logGetFile(void);
// Register a pre-I/O hook called before every file write.
typedef void (*LogPreIoFuncT)(void);
void logSetPreIoHook(LogPreIoFuncT func);
#endif // LOG_H

214
win31drv/neformat.h Normal file
View file

@ -0,0 +1,214 @@
#ifndef NEFORMAT_H
#define NEFORMAT_H
#include <stdint.h>
// ============================================================================
// MZ (DOS) executable header - precedes the NE header
// ============================================================================
#define MZ_SIGNATURE 0x5A4D // 'MZ'
typedef struct __attribute__((packed)) {
uint16_t signature; // 0x00: 'MZ'
uint16_t lastPageBytes; // 0x02: bytes on last page
uint16_t pageCount; // 0x04: pages in file (512 bytes each)
uint16_t relocationCount; // 0x06: relocation entries
uint16_t headerParagraphs;// 0x08: header size in paragraphs
uint16_t minAlloc; // 0x0A: minimum extra paragraphs
uint16_t maxAlloc; // 0x0C: maximum extra paragraphs
uint16_t initSS; // 0x0E: initial SS
uint16_t initSP; // 0x10: initial SP
uint16_t checksum; // 0x12: checksum
uint16_t initIP; // 0x14: initial IP
uint16_t initCS; // 0x16: initial CS
uint16_t relocationOff; // 0x18: relocation table offset
uint16_t overlayNum; // 0x1A: overlay number
uint16_t reserved1[4]; // 0x1C: reserved
uint16_t oemId; // 0x24: OEM identifier
uint16_t oemInfo; // 0x26: OEM information
uint16_t reserved2[10]; // 0x28: reserved
uint32_t neHeaderOffset; // 0x3C: offset to NE header
} MzHeaderT;
// ============================================================================
// NE (New Executable) header
// ============================================================================
#define NE_SIGNATURE 0x454E // 'NE'
typedef struct __attribute__((packed)) {
uint16_t signature; // 0x00: 'NE'
uint8_t linkerMajor; // 0x02: linker version
uint8_t linkerMinor; // 0x03: linker revision
uint16_t entryTableOffset; // 0x04: offset to entry table (from NE header)
uint16_t entryTableSize; // 0x06: size of entry table
uint32_t fileCrc; // 0x08: file CRC
uint16_t moduleFlags; // 0x0C: module flags
uint16_t autoDataSegIndex; // 0x0E: auto data segment index (1-based)
uint16_t initialHeapSize; // 0x10: initial heap size
uint16_t initialStackSize; // 0x12: initial stack size
uint16_t entryPointIP; // 0x14: CS:IP entry point (IP)
uint16_t entryPointCS; // 0x16: CS:IP entry point (CS segment index)
uint16_t initialSP; // 0x18: SS:SP initial stack (SP)
uint16_t initialSS; // 0x1A: SS:SP initial stack (SS segment index)
uint16_t segmentCount; // 0x1C: number of segment table entries
uint16_t moduleRefCount; // 0x1E: number of module reference table entries
uint16_t nonResNameSize; // 0x20: size of non-resident name table
uint16_t segmentTableOffset; // 0x22: offset to segment table (from NE header)
uint16_t resourceTableOffset; // 0x24: offset to resource table (from NE header)
uint16_t resNameTableOffset; // 0x26: offset to resident name table (from NE)
uint16_t modRefTableOffset; // 0x28: offset to module reference table (from NE)
uint16_t importNameTableOffset; // 0x2A: offset to imported names table (from NE)
uint32_t nonResNameTableFileOffset; // 0x2C: file offset of non-resident name table
uint16_t movableEntryCount; // 0x30: number of movable entry points
uint16_t sectorAlignShift; // 0x32: sector alignment shift count
uint16_t resourceSegCount; // 0x34: number of resource segments
uint8_t targetOS; // 0x36: target operating system
uint8_t otherFlags; // 0x37: additional flags
uint16_t gangLoadAreaOffset; // 0x38: offset to gang-load area
uint16_t gangLoadAreaSize; // 0x3A: size of gang-load area
uint16_t swapAreaSize; // 0x3C: minimum code swap area size
uint16_t expectedWinVer; // 0x3E: expected Windows version
} NeHeaderT;
// NE module flags (moduleFlags field)
#define NE_FFLAGS_SINGLEDATA 0x0001 // Single shared DGROUP
#define NE_FFLAGS_MULTIPLEDATA 0x0002 // Multiple DGROUP (DLL with per-instance data)
#define NE_FFLAGS_GLOBALINIT 0x0004 // Global initialization
#define NE_FFLAGS_PROTMODE 0x0008 // Protected mode only
#define NE_FFLAGS_8086 0x0010 // 8086 instructions
#define NE_FFLAGS_80286 0x0020 // 80286 instructions
#define NE_FFLAGS_80386 0x0040 // 80386 instructions
#define NE_FFLAGS_80x87 0x0080 // uses 80x87
#define NE_FFLAGS_FULLSCREEN 0x0100 // full-screen application (not a DLL)
#define NE_FFLAGS_DLL 0x8000 // DLL or driver (not a task)
// NE target OS values
#define NE_OS_UNKNOWN 0x00
#define NE_OS_OS2 0x01
#define NE_OS_WINDOWS 0x02
#define NE_OS_DOS4 0x03
#define NE_OS_WIN386 0x04
// ============================================================================
// NE segment table entry
// ============================================================================
typedef struct __attribute__((packed)) {
uint16_t fileSectorOffset; // Logical sector offset in file (0 = no data)
uint16_t fileLength; // Length of segment in file (0 = 64K)
uint16_t flags; // Segment flags
uint16_t minAllocSize; // Minimum allocation size (0 = 64K)
} NeSegEntryT;
// Segment flags
#define NE_SEGF_DATA 0x0001 // Data segment (0 = code)
#define NE_SEGF_ALLOCATED 0x0002 // Loader has allocated memory
#define NE_SEGF_LOADED 0x0004 // Segment is loaded
#define NE_SEGF_MOVEABLE 0x0010 // Moveable segment
#define NE_SEGF_SHAREABLE 0x0020 // Shareable segment
#define NE_SEGF_PRELOAD 0x0040 // Preload segment
#define NE_SEGF_READONLY 0x0080 // Read-only (code) or execute-only (data)
#define NE_SEGF_HASRELOC 0x0100 // Has relocation data
#define NE_SEGF_DISCARD 0x1000 // Discardable
// ============================================================================
// NE relocation record
// ============================================================================
typedef struct __attribute__((packed)) {
uint8_t srcType; // Source (fixup) type
uint8_t flags; // Relocation flags
uint16_t srcOffset; // Offset within segment of the fixup location
uint16_t target1; // Module index (1-based) or segment number
uint16_t target2; // Ordinal/offset or offset within segment
} NeRelocT;
// Relocation source types (srcType field)
#define NE_RELOC_LOBYTE 0x00 // Low byte fixup
#define NE_RELOC_SEGMENT 0x02 // 16-bit segment fixup
#define NE_RELOC_FAR_ADDR 0x03 // 32-bit far pointer (seg:off) fixup
#define NE_RELOC_OFFSET 0x05 // 16-bit offset fixup
#define NE_RELOC_FAR48_ADDR 0x0B // 48-bit far pointer fixup
#define NE_RELOC_OFFSET32 0x0D // 32-bit offset fixup
// Relocation target flags (flags field)
#define NE_RELF_INTERNALREF 0x00 // Internal reference
#define NE_RELF_IMPORTORD 0x01 // Import by ordinal
#define NE_RELF_IMPORTNAME 0x02 // Import by name
#define NE_RELF_OSFIXUP 0x03 // OS fixup
#define NE_RELF_TARGET_MASK 0x03 // Mask for target type
#define NE_RELF_ADDITIVE 0x04 // Additive fixup (don't zero target first)
// ============================================================================
// NE entry table structures
// ============================================================================
// Entry table is a series of bundles. Each bundle starts with:
// BYTE count - number of entries in this bundle (0 = end of table)
// BYTE indicator - 0x00 = empty, 0xFF = moveable, else fixed segment number
// Fixed entry (indicator = segment number 1-254)
typedef struct __attribute__((packed)) {
uint8_t flags; // Entry flags
uint16_t offset; // Offset within segment
} NeFixedEntryT;
// Moveable entry (indicator = 0xFF)
typedef struct __attribute__((packed)) {
uint8_t flags; // Entry flags
uint16_t int3fh; // INT 3Fh instruction (0xCD3F)
uint8_t segIndex; // Segment number (1-based)
uint16_t offset; // Offset within segment
} NeMoveableEntryT;
// Entry flags
#define NE_ENTRY_EXPORTED 0x01 // Entry is exported
#define NE_ENTRY_SHDATA 0x02 // Entry uses shared data segment
// ============================================================================
// Display driver ordinal numbers (standard DDI exports)
// ============================================================================
#define DDI_ORD_BITBLT 1
#define DDI_ORD_COLORINFO 2
#define DDI_ORD_CONTROL 3
#define DDI_ORD_DISABLE 4
#define DDI_ORD_ENABLE 5
#define DDI_ORD_ENUMDFFONTS 6
#define DDI_ORD_ENUMOBJ 7
#define DDI_ORD_OUTPUT 8
#define DDI_ORD_PIXEL 9
#define DDI_ORD_REALIZEOBJECT 10
#define DDI_ORD_STRBLT 11
#define DDI_ORD_SCANLR 12
#define DDI_ORD_DEVICEMODE 13
#define DDI_ORD_EXTTEXTOUT 14
#define DDI_ORD_GETCHARWIDTH 15
#define DDI_ORD_DEVICEBITMAP 16
#define DDI_ORD_FASTBORDER 17
#define DDI_ORD_SETATTRIBUTE 18
#define DDI_ORD_DIBTODEVICE 19
#define DDI_ORD_CREATEBITMAP 20
#define DDI_ORD_DELETEBITMAP 21
#define DDI_ORD_SELECTBITMAP 22
#define DDI_ORD_BITMAPBITS 23
#define DDI_ORD_RECLIP 24
#define DDI_ORD_GETPALETTE 25
#define DDI_ORD_SETPALETTE 26
#define DDI_ORD_SETPALETTETRANS 27
#define DDI_ORD_UPDATECOLORS 28
#define DDI_ORD_STRETCHBLT 29
#define DDI_ORD_STRETCHDIBITS 30
#define DDI_ORD_SELECTPALETTE 31
#define DDI_ORD_INQUIRE 101
#define DDI_ORD_SETCURSOR 102
#define DDI_ORD_MOVECURSOR 103
#define DDI_ORD_CHECKCRSR 104
#define DDI_ORD_GETDRIVERRESID 450
// Maximum DDI ordinal we track
#define DDI_MAX_ORDINAL 500
#endif // NEFORMAT_H

962
win31drv/neload.c Normal file
View file

@ -0,0 +1,962 @@
// ============================================================================
// neload.c - NE (New Executable) format loader
//
// Loads Windows 3.x 16-bit DLLs/drivers into protected mode memory
// using DPMI to allocate LDT descriptors for 16-bit code/data segments.
// ============================================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <inttypes.h>
#include <dpmi.h>
#include <go32.h>
#include <sys/movedata.h>
#include <sys/nearptr.h>
#include "neload.h"
#include "wintypes.h"
#include "log.h"
// Forward declarations
static bool readHeaders(NeModuleT *mod, FILE *fp);
static bool loadSegments(NeModuleT *mod, FILE *fp, ImportResolverT resolver);
static bool allocateSegment(NeModuleT *mod, int segIdx, uint32_t size, bool isCode);
static bool loadSegmentData(NeModuleT *mod, int segIdx, FILE *fp, uint32_t fileOffset, uint32_t fileSize);
static bool processRelocations(NeModuleT *mod, int segIdx, FILE *fp, ImportResolverT resolver);
static bool parseEntryTable(NeModuleT *mod, FILE *fp);
static bool parseModuleReferences(NeModuleT *mod, FILE *fp);
static bool parseResidentNames(NeModuleT *mod, FILE *fp);
static void freeSegment(LoadedSegT *seg);
static uint16_t makeDescriptor16(uint32_t base, uint32_t limit, bool isCode);
static void dbgPrint(const char *fmt, ...);
static bool gDebug = false;
static void dbgPrint(const char *fmt, ...)
{
if (!gDebug) {
return;
}
va_list ap;
va_start(ap, fmt);
logErrV(fmt, ap);
va_end(ap);
}
bool neLoadModule(NeModuleT *mod, const char *filePath, ImportResolverT resolver)
{
memset(mod, 0, sizeof(NeModuleT));
FILE *fp = fopen(filePath, "rb");
if (!fp) {
logErr("neload: cannot open '%s'\n", filePath);
return false;
}
bool ok = false;
// Step 1: Read and validate MZ + NE headers
if (!readHeaders(mod, fp)) {
goto done;
}
// Step 2: Parse module reference table (imported module names)
if (!parseModuleReferences(mod, fp)) {
goto done;
}
// Step 3: Parse resident name table (module name + exports by name)
if (!parseResidentNames(mod, fp)) {
goto done;
}
// Step 4: Parse entry table (export ordinals -> segment:offset)
if (!parseEntryTable(mod, fp)) {
goto done;
}
// Step 5: Load segments, apply relocations
if (!loadSegments(mod, fp, resolver)) {
goto done;
}
// Resolve auto data segment selector
if (mod->neHeader.autoDataSegIndex > 0 &&
mod->neHeader.autoDataSegIndex <= mod->segmentCount) {
mod->autoDataSel = mod->segments[mod->neHeader.autoDataSegIndex - 1].selector;
}
mod->loaded = true;
ok = true;
done:
fclose(fp);
if (!ok) {
neUnloadModule(mod);
}
return ok;
}
void neUnloadModule(NeModuleT *mod)
{
for (uint16_t i = 0; i < mod->segmentCount; i++) {
freeSegment(&mod->segments[i]);
}
mod->segmentCount = 0;
mod->loaded = false;
}
bool neLookupExport(const NeModuleT *mod, uint16_t ordinal, uint16_t *seg, uint16_t *off, uint16_t *sel)
{
if (ordinal == 0 || ordinal >= NE_MAX_EXPORTS) {
return false;
}
if (mod->exports[ordinal].segIndex == 0) {
return false;
}
ExportEntryT *e = (ExportEntryT *)&mod->exports[ordinal];
uint16_t sIdx = e->segIndex - 1;
if (sIdx >= mod->segmentCount) {
return false;
}
if (seg) {
*seg = e->segIndex;
}
if (off) {
*off = e->offset;
}
if (sel) {
*sel = mod->segments[sIdx].selector;
}
return true;
}
uint16_t neLookupExportByName(const NeModuleT *mod, const char *name, const char *filePath)
{
// We need to re-read the resident name table from the file to search by name.
// The first entry is the module name; subsequent entries are exports.
FILE *fp = fopen(filePath, "rb");
if (!fp) {
return 0;
}
uint32_t tableOff = mod->neHeaderFileOffset + mod->neHeader.resNameTableOffset;
fseek(fp, tableOff, SEEK_SET);
uint16_t ordinal = 0;
while (1) {
uint8_t nameLen;
if (fread(&nameLen, 1, 1, fp) != 1 || nameLen == 0) {
break;
}
char entryName[256];
if (fread(entryName, 1, nameLen, fp) != nameLen) {
break;
}
entryName[nameLen] = '\0';
uint16_t ord;
if (fread(&ord, 2, 1, fp) != 1) {
break;
}
if (strcasecmp(entryName, name) == 0) {
ordinal = ord;
break;
}
}
fclose(fp);
return ordinal;
}
void neDumpModule(const NeModuleT *mod)
{
logErr("=== NE Module: %s ===\n", mod->moduleName);
logErr(" Segments: %u\n", mod->segmentCount);
logErr(" Auto data segment: %u\n", mod->neHeader.autoDataSegIndex);
logErr(" Module flags: 0x%04X", mod->neHeader.moduleFlags);
if (mod->neHeader.moduleFlags & NE_FFLAGS_DLL) {
logErr(" (DLL)");
}
logErr("\n");
logErr(" Target OS: 0x%02X\n", mod->neHeader.targetOS);
logErr(" Expected Windows version: %u.%u\n",
mod->neHeader.expectedWinVer >> 8,
mod->neHeader.expectedWinVer & 0xFF);
logErr(" Segments:\n");
for (uint16_t i = 0; i < mod->segmentCount; i++) {
LoadedSegT *s = (LoadedSegT *)&mod->segments[i];
logErr(" [%u] %s sel=0x%04X base=0x%08" PRIX32 " size=%" PRIu32,
i + 1,
s->isCode ? "CODE" : "DATA",
s->selector,
s->linearAddr,
s->size);
if (s->flags & NE_SEGF_PRELOAD) {
logErr(" PRELOAD");
}
if (s->flags & NE_SEGF_MOVEABLE) {
logErr(" MOVEABLE");
}
logErr("\n");
}
logErr(" Module references:\n");
for (uint16_t i = 0; i < mod->modRefCount; i++) {
logErr(" [%u] %s\n", i + 1, mod->modRefNames[i]);
}
logErr(" Exports:\n");
for (uint16_t i = 1; i < NE_MAX_EXPORTS; i++) {
if (mod->exports[i].segIndex != 0) {
logErr(" ord %u -> seg %u : 0x%04X (sel 0x%04X)\n",
i,
mod->exports[i].segIndex,
mod->exports[i].offset,
mod->exports[i].selector);
}
}
}
// ============================================================================
// Internal implementation
// ============================================================================
static bool readHeaders(NeModuleT *mod, FILE *fp)
{
// Read MZ header
MzHeaderT mz;
if (fread(&mz, sizeof(mz), 1, fp) != 1) {
logErr("neload: failed to read MZ header\n");
return false;
}
if (mz.signature != MZ_SIGNATURE) {
logErr("neload: not an MZ executable (sig=0x%04X)\n", mz.signature);
return false;
}
// Read NE header
mod->neHeaderFileOffset = mz.neHeaderOffset;
fseek(fp, mz.neHeaderOffset, SEEK_SET);
if (fread(&mod->neHeader, sizeof(NeHeaderT), 1, fp) != 1) {
logErr("neload: failed to read NE header\n");
return false;
}
if (mod->neHeader.signature != NE_SIGNATURE) {
logErr("neload: not an NE executable (sig=0x%04X)\n",
mod->neHeader.signature);
return false;
}
// Validate
if (mod->neHeader.segmentCount > NE_MAX_SEGMENTS) {
logErr("neload: too many segments (%u, max %u)\n",
mod->neHeader.segmentCount, NE_MAX_SEGMENTS);
return false;
}
mod->segmentCount = mod->neHeader.segmentCount;
mod->sectorAlignShift = mod->neHeader.sectorAlignShift;
if (mod->sectorAlignShift == 0) {
mod->sectorAlignShift = 9; // Default: 512-byte sectors
}
dbgPrint("neload: NE header at 0x%08" PRIX32 ", %u segments, %u align shift\n",
mod->neHeaderFileOffset, mod->segmentCount, mod->sectorAlignShift);
return true;
}
static bool parseModuleReferences(NeModuleT *mod, FILE *fp)
{
if (mod->neHeader.moduleRefCount == 0) {
return true;
}
mod->modRefCount = mod->neHeader.moduleRefCount;
if (mod->modRefCount > NE_MAX_MODREFS) {
logErr("neload: too many module references (%u, max %u)\n",
mod->modRefCount, NE_MAX_MODREFS);
return false;
}
// Read module reference table (array of offsets into imported name table)
uint32_t modRefTableOff = mod->neHeaderFileOffset + mod->neHeader.modRefTableOffset;
fseek(fp, modRefTableOff, SEEK_SET);
uint16_t nameOffsets[NE_MAX_MODREFS];
if (fread(nameOffsets, 2, mod->modRefCount, fp) != mod->modRefCount) {
logErr("neload: failed to read module reference table\n");
return false;
}
// Resolve each offset to a name from the imported name table
uint32_t importNameTableOff = mod->neHeaderFileOffset + mod->neHeader.importNameTableOffset;
for (uint16_t i = 0; i < mod->modRefCount; i++) {
uint32_t nameOff = importNameTableOff + nameOffsets[i];
fseek(fp, nameOff, SEEK_SET);
uint8_t nameLen;
if (fread(&nameLen, 1, 1, fp) != 1) {
logErr("neload: failed to read module name length\n");
return false;
}
if (nameLen > 31) {
nameLen = 31;
}
if (fread(mod->modRefNames[i], 1, nameLen, fp) != nameLen) {
logErr("neload: failed to read module name\n");
return false;
}
mod->modRefNames[i][nameLen] = '\0';
dbgPrint("neload: module ref [%u] = '%s'\n", i + 1, mod->modRefNames[i]);
}
return true;
}
static bool parseResidentNames(NeModuleT *mod, FILE *fp)
{
uint32_t tableOff = mod->neHeaderFileOffset + mod->neHeader.resNameTableOffset;
fseek(fp, tableOff, SEEK_SET);
bool firstEntry = true;
while (1) {
uint8_t nameLen;
if (fread(&nameLen, 1, 1, fp) != 1 || nameLen == 0) {
break;
}
char name[256];
if (fread(name, 1, nameLen, fp) != nameLen) {
break;
}
name[nameLen] = '\0';
uint16_t ordinal;
if (fread(&ordinal, 2, 1, fp) != 1) {
break;
}
if (firstEntry) {
// First entry is the module name (ordinal 0)
memcpy(mod->moduleName, name, sizeof(mod->moduleName) - 1);
mod->moduleName[sizeof(mod->moduleName) - 1] = '\0';
dbgPrint("neload: module name = '%s'\n", mod->moduleName);
firstEntry = false;
} else {
dbgPrint("neload: resident name '%s' = ordinal %u\n", name, ordinal);
}
}
return true;
}
static bool parseEntryTable(NeModuleT *mod, FILE *fp)
{
uint32_t tableOff = mod->neHeaderFileOffset + mod->neHeader.entryTableOffset;
uint32_t tableEnd = tableOff + mod->neHeader.entryTableSize;
fseek(fp, tableOff, SEEK_SET);
uint16_t currentOrdinal = 1;
while (ftell(fp) < (long)tableEnd) {
uint8_t bundleCount;
uint8_t indicator;
if (fread(&bundleCount, 1, 1, fp) != 1 || bundleCount == 0) {
break; // End of entry table
}
if (fread(&indicator, 1, 1, fp) != 1) {
break;
}
if (indicator == 0x00) {
// Empty bundle - skip these ordinals
currentOrdinal += bundleCount;
continue;
}
for (uint8_t i = 0; i < bundleCount; i++) {
if (currentOrdinal >= NE_MAX_EXPORTS) {
logErr("neload: export ordinal %u exceeds max\n", currentOrdinal);
currentOrdinal++;
continue;
}
if (indicator == 0xFF) {
// Moveable segment entry
NeMoveableEntryT entry;
if (fread(&entry, sizeof(entry), 1, fp) != 1) {
return false;
}
mod->exports[currentOrdinal].segIndex = entry.segIndex;
mod->exports[currentOrdinal].offset = entry.offset;
mod->exports[currentOrdinal].flags = entry.flags;
mod->exportCount++;
dbgPrint("neload: entry ord %u -> seg %u : 0x%04X (moveable)\n",
currentOrdinal, entry.segIndex, entry.offset);
} else {
// Fixed segment entry (indicator = segment number)
NeFixedEntryT entry;
if (fread(&entry, sizeof(entry), 1, fp) != 1) {
return false;
}
mod->exports[currentOrdinal].segIndex = indicator;
mod->exports[currentOrdinal].offset = entry.offset;
mod->exports[currentOrdinal].flags = entry.flags;
mod->exportCount++;
dbgPrint("neload: entry ord %u -> seg %u : 0x%04X (fixed)\n",
currentOrdinal, indicator, entry.offset);
}
currentOrdinal++;
}
}
dbgPrint("neload: %u export entries parsed\n", mod->exportCount);
return true;
}
static bool loadSegments(NeModuleT *mod, FILE *fp, ImportResolverT resolver)
{
// Read segment table
uint32_t segTableOff = mod->neHeaderFileOffset + mod->neHeader.segmentTableOffset;
fseek(fp, segTableOff, SEEK_SET);
NeSegEntryT segTable[NE_MAX_SEGMENTS];
if (fread(segTable, sizeof(NeSegEntryT), mod->segmentCount, fp) != mod->segmentCount) {
logErr("neload: failed to read segment table\n");
return false;
}
// Load each segment
for (uint16_t i = 0; i < mod->segmentCount; i++) {
NeSegEntryT *se = &segTable[i];
bool isCode = !(se->flags & NE_SEGF_DATA);
// Determine segment size
uint32_t fileLen = se->fileLength;
if (fileLen == 0 && se->fileSectorOffset != 0) {
fileLen = 0x10000; // 64K
}
uint32_t allocSize = se->minAllocSize;
if (allocSize == 0) {
allocSize = 0x10000; // 64K
}
if (allocSize < fileLen) {
allocSize = fileLen;
}
// Allocate memory and create descriptor
if (!allocateSegment(mod, i, allocSize, isCode)) {
logErr("neload: failed to allocate segment %u\n", i + 1);
return false;
}
mod->segments[i].flags = se->flags;
mod->segments[i].fileSize = fileLen;
// Load data from file
if (se->fileSectorOffset != 0 && fileLen > 0) {
uint32_t fileOffset = (uint32_t)se->fileSectorOffset << mod->sectorAlignShift;
if (!loadSegmentData(mod, i, fp, fileOffset, fileLen)) {
logErr("neload: failed to load segment %u data\n", i + 1);
return false;
}
}
dbgPrint("neload: loaded seg %u: %s size=%" PRIu32 " filelen=%" PRIu32 " sel=0x%04X base=0x%08" PRIX32 "\n",
i + 1, isCode ? "CODE" : "DATA",
allocSize, fileLen,
mod->segments[i].selector,
mod->segments[i].linearAddr);
}
// Resolve export selectors now that segments are loaded
for (uint16_t i = 1; i < NE_MAX_EXPORTS; i++) {
if (mod->exports[i].segIndex > 0 &&
mod->exports[i].segIndex <= mod->segmentCount) {
mod->exports[i].selector = mod->segments[mod->exports[i].segIndex - 1].selector;
}
}
// Process relocations for each segment
for (uint16_t i = 0; i < mod->segmentCount; i++) {
if (segTable[i].flags & NE_SEGF_HASRELOC) {
if (!processRelocations(mod, i, fp, resolver)) {
logErr("neload: relocation failed for segment %u\n", i + 1);
return false;
}
}
}
return true;
}
static bool allocateSegment(NeModuleT *mod, int segIdx, uint32_t size, bool isCode)
{
LoadedSegT *seg = &mod->segments[segIdx];
// Allocate memory using DJGPP's malloc (extended memory, flat model)
// The memory is accessible via the flat DS selector, but we also need
// a 16-bit selector pointing to it.
uint8_t *mem = (uint8_t *)calloc(1, size);
if (!mem) {
logErr("neload: failed to allocate %" PRIu32 " bytes for segment %u\n", size, segIdx + 1);
return false;
}
// In DJGPP, pointer values are offsets from the DS base.
// The true linear address = pointer + __djgpp_base_address.
// We store the pointer value (for C access) but use the linear
// address when setting up the LDT descriptor base.
uint32_t ptrVal = (uint32_t)mem;
uint32_t linearAddr = ptrVal + __djgpp_base_address;
// Create a 16-bit LDT descriptor for this segment
uint16_t sel = makeDescriptor16(linearAddr, size - 1, isCode);
if (sel == 0) {
free(mem);
return false;
}
seg->linearAddr = ptrVal; // Store DJGPP pointer (for C access via cast)
seg->selector = sel;
seg->size = size;
seg->isCode = isCode;
return true;
}
static uint16_t makeDescriptor16(uint32_t base, uint32_t limit, bool isCode)
{
int sel = __dpmi_allocate_ldt_descriptors(1);
if (sel < 0) {
logErr("neload: failed to allocate LDT descriptor\n");
return 0;
}
if (__dpmi_set_segment_base_address(sel, base) < 0) {
logErr("neload: failed to set segment base\n");
__dpmi_free_ldt_descriptor(sel);
return 0;
}
if (__dpmi_set_segment_limit(sel, limit) < 0) {
logErr("neload: failed to set segment limit\n");
__dpmi_free_ldt_descriptor(sel);
return 0;
}
// Set access rights for 16-bit segment
// Code: present, DPL 3, code, readable, non-conforming = 0xFA
// Data: present, DPL 3, data, writable = 0xF2
// High byte: G=0, D=0 (16-bit), 0, AVL=0 = 0x00
uint16_t rights = isCode ? 0x00FA : 0x00F2;
if (__dpmi_set_descriptor_access_rights(sel, rights) < 0) {
logErr("neload: failed to set access rights (0x%04X)\n", rights);
__dpmi_free_ldt_descriptor(sel);
return 0;
}
return (uint16_t)sel;
}
static bool loadSegmentData(NeModuleT *mod, int segIdx, FILE *fp, uint32_t fileOffset, uint32_t fileSize)
{
LoadedSegT *seg = &mod->segments[segIdx];
fseek(fp, fileOffset, SEEK_SET);
// Read directly into the allocated memory (flat model, so pointer works)
uint8_t *dest = (uint8_t *)seg->linearAddr;
if (fread(dest, 1, fileSize, fp) != fileSize) {
logErr("neload: short read loading segment %u data\n", segIdx + 1);
return false;
}
return true;
}
static bool processRelocations(NeModuleT *mod, int segIdx, FILE *fp, ImportResolverT resolver)
{
LoadedSegT *seg = &mod->segments[segIdx];
// Relocation data follows the segment data in the file.
// The segment table entry gives us the file offset; relocation data
// starts at fileOffset + fileLength.
uint32_t segTableOff = mod->neHeaderFileOffset + mod->neHeader.segmentTableOffset;
fseek(fp, segTableOff + segIdx * sizeof(NeSegEntryT), SEEK_SET);
NeSegEntryT se;
if (fread(&se, sizeof(se), 1, fp) != 1) {
return false;
}
uint32_t fileOffset = (uint32_t)se.fileSectorOffset << mod->sectorAlignShift;
uint32_t fileLen = se.fileLength;
if (fileLen == 0 && se.fileSectorOffset != 0) {
fileLen = 0x10000;
}
// Relocation records follow the segment data
uint32_t relocOff = fileOffset + fileLen;
fseek(fp, relocOff, SEEK_SET);
// First word is the count of relocation records
uint16_t relocCount;
if (fread(&relocCount, 2, 1, fp) != 1) {
return false;
}
dbgPrint("neload: segment %u has %u relocations\n", segIdx + 1, relocCount);
uint8_t *segData = (uint8_t *)seg->linearAddr;
for (uint16_t i = 0; i < relocCount; i++) {
NeRelocT rec;
if (fread(&rec, sizeof(rec), 1, fp) != 1) {
logErr("neload: failed to read relocation %u/%u\n", i + 1, relocCount);
return false;
}
uint8_t targetType = rec.flags & NE_RELF_TARGET_MASK;
bool additive = (rec.flags & NE_RELF_ADDITIVE) != 0;
// Resolve the target address
uint16_t targetSel = 0;
uint16_t targetOff = 0;
bool resolved = false;
switch (targetType) {
case NE_RELF_INTERNALREF: {
// Internal reference: target1 = segment index (1-based)
// For moveable segments, target2 is an entry table ordinal.
// For fixed segments, target2 is the offset.
uint16_t tSegIdx = rec.target1;
if (rec.target1 == 0xFF) {
// Moveable reference via entry table
uint16_t ordinal = rec.target2;
if (ordinal > 0 && ordinal < NE_MAX_EXPORTS &&
mod->exports[ordinal].segIndex > 0) {
tSegIdx = mod->exports[ordinal].segIndex;
targetOff = mod->exports[ordinal].offset;
targetSel = mod->segments[tSegIdx - 1].selector;
resolved = true;
}
} else if (tSegIdx > 0 && tSegIdx <= mod->segmentCount) {
targetSel = mod->segments[tSegIdx - 1].selector;
targetOff = rec.target2;
resolved = true;
}
break;
}
case NE_RELF_IMPORTORD: {
// Import by ordinal: target1 = module ref index (1-based)
// target2 = ordinal number
uint16_t modIdx = rec.target1;
uint16_t ordinal = rec.target2;
if (modIdx > 0 && modIdx <= mod->modRefCount && resolver) {
FarPtr16T addr = resolver(mod->modRefNames[modIdx - 1], ordinal, NULL);
if (addr.segment != 0 || addr.offset != 0) {
targetSel = addr.segment;
targetOff = addr.offset;
resolved = true;
}
dbgPrint("neload: RELOC seg %u off 0x%04X srcType=%u: %s.%u -> %04X:%04X\n",
segIdx + 1, rec.srcOffset, rec.srcType,
mod->modRefNames[modIdx - 1], ordinal,
targetSel, targetOff);
}
if (!resolved) {
dbgPrint("neload: UNRESOLVED import %s.%u in seg %u at 0x%04X\n",
(modIdx > 0 && modIdx <= mod->modRefCount) ?
mod->modRefNames[modIdx - 1] : "???",
ordinal, segIdx + 1, rec.srcOffset);
// Patch in a dummy value (INT 3 / breakpoint)
targetSel = mod->segments[0].selector; // Point to first code seg
targetOff = 0;
resolved = true;
}
break;
}
case NE_RELF_IMPORTNAME: {
// Import by name: target1 = module ref index (1-based)
// target2 = offset into imported names table
uint16_t modIdx = rec.target1;
uint16_t nameOff16 = rec.target2;
// Read the function name from the imported names table
char funcName[64] = "";
uint32_t importNameTableOff = mod->neHeaderFileOffset +
mod->neHeader.importNameTableOffset;
long savedPos = ftell(fp);
fseek(fp, importNameTableOff + nameOff16, SEEK_SET);
uint8_t nameLen;
if (fread(&nameLen, 1, 1, fp) == 1 && nameLen < 64) {
fread(funcName, 1, nameLen, fp);
funcName[nameLen] = '\0';
}
fseek(fp, savedPos, SEEK_SET);
if (modIdx > 0 && modIdx <= mod->modRefCount && resolver) {
FarPtr16T addr = resolver(mod->modRefNames[modIdx - 1], 0, funcName);
if (addr.segment != 0 || addr.offset != 0) {
targetSel = addr.segment;
targetOff = addr.offset;
resolved = true;
}
}
if (!resolved) {
dbgPrint("neload: UNRESOLVED import %s.%s in seg %u at 0x%04X\n",
(modIdx > 0 && modIdx <= mod->modRefCount) ?
mod->modRefNames[modIdx - 1] : "???",
funcName, segIdx + 1, rec.srcOffset);
targetSel = mod->segments[0].selector;
targetOff = 0;
resolved = true;
}
break;
}
case NE_RELF_OSFIXUP: {
// OS fixup (floating point emulation, etc.)
// target1 = fixup type (1=FIARQQ, 2=FJARQQ, etc.)
// We just patch in NOPs or a far return.
targetSel = mod->segments[0].selector;
targetOff = 0;
resolved = true;
dbgPrint("neload: OS fixup type %u at seg %u offset 0x%04X\n",
rec.target1, segIdx + 1, rec.srcOffset);
break;
}
}
if (!resolved) {
logErr("neload: failed to resolve reloc in seg %u at 0x%04X\n",
segIdx + 1, rec.srcOffset);
continue;
}
// Apply the fixup to the segment data.
// NE relocations can be chained: the word at srcOffset contains
// the offset of the next fixup location (forming a linked list),
// UNLESS the relocation is additive.
uint32_t fixupOff = rec.srcOffset;
if (additive) {
// Single fixup, add to existing value
switch (rec.srcType) {
case NE_RELOC_LOBYTE:
if (fixupOff < seg->size) {
segData[fixupOff] += (uint8_t)targetOff;
}
break;
case NE_RELOC_OFFSET:
if (fixupOff + 1 < seg->size) {
uint16_t *p = (uint16_t *)(segData + fixupOff);
*p += targetOff;
}
break;
case NE_RELOC_SEGMENT:
if (fixupOff + 1 < seg->size) {
uint16_t *p = (uint16_t *)(segData + fixupOff);
*p += targetSel;
}
break;
case NE_RELOC_FAR_ADDR:
if (fixupOff + 3 < seg->size) {
uint16_t *pOff = (uint16_t *)(segData + fixupOff);
uint16_t *pSeg = (uint16_t *)(segData + fixupOff + 2);
*pOff += targetOff;
*pSeg += targetSel;
}
break;
case NE_RELOC_OFFSET32:
if (fixupOff + 3 < seg->size) {
uint32_t *p = (uint32_t *)(segData + fixupOff);
*p += ((uint32_t)targetSel << 16) | targetOff;
}
break;
}
} else {
// Chained fixups: follow the linked list
int chainLimit = 4096; // Safety limit
int chainCount = 0;
while (fixupOff != 0xFFFF && chainLimit-- > 0) {
if (fixupOff + 1 >= seg->size) {
break;
}
uint16_t nextOff;
chainCount++;
switch (rec.srcType) {
case NE_RELOC_LOBYTE:
if (fixupOff < seg->size) {
nextOff = segData[fixupOff]; // Next in chain
segData[fixupOff] = (uint8_t)targetOff;
} else {
nextOff = 0xFFFF;
}
break;
case NE_RELOC_OFFSET: {
uint16_t *p = (uint16_t *)(segData + fixupOff);
nextOff = *p;
*p = targetOff;
break;
}
case NE_RELOC_SEGMENT: {
uint16_t *p = (uint16_t *)(segData + fixupOff);
nextOff = *p;
*p = targetSel;
break;
}
case NE_RELOC_FAR_ADDR: {
if (fixupOff + 3 >= seg->size) {
nextOff = 0xFFFF;
break;
}
uint16_t *pOff = (uint16_t *)(segData + fixupOff);
uint16_t *pSeg = (uint16_t *)(segData + fixupOff + 2);
nextOff = *pOff; // Chain is in offset field
*pOff = targetOff;
*pSeg = targetSel;
break;
}
case NE_RELOC_OFFSET32: {
if (fixupOff + 3 >= seg->size) {
nextOff = 0xFFFF;
break;
}
uint16_t *p16 = (uint16_t *)(segData + fixupOff);
nextOff = *p16;
uint32_t *p32 = (uint32_t *)(segData + fixupOff);
*p32 = ((uint32_t)targetSel << 16) | targetOff;
break;
}
default:
dbgPrint("neload: unknown reloc srcType 0x%02X\n", rec.srcType);
nextOff = 0xFFFF;
break;
}
fixupOff = nextOff;
}
if (chainCount > 1) {
dbgPrint("neload: chain: %d links patched\n", chainCount);
}
}
}
return true;
}
static void freeSegment(LoadedSegT *seg)
{
if (seg->selector != 0) {
__dpmi_free_ldt_descriptor(seg->selector);
seg->selector = 0;
}
if (seg->linearAddr != 0) {
free((void *)seg->linearAddr);
seg->linearAddr = 0;
}
seg->size = 0;
}
bool neExtendSegment(NeModuleT *mod, int segIdx, uint32_t extraSize, uint32_t *oldSizeOut)
{
if (segIdx < 0 || segIdx >= mod->segmentCount) {
return false;
}
LoadedSegT *seg = &mod->segments[segIdx];
uint32_t oldSize = seg->size;
uint32_t newSize = oldSize + extraSize;
// 16-bit segments are limited to 64K
if (newSize > 0x10000) {
logErr("neload: cannot extend segment %d beyond 64K (old=%" PRIu32 " extra=%" PRIu32 ")\n",
segIdx + 1, oldSize, extraSize);
return false;
}
uint8_t *newMem = (uint8_t *)realloc((void *)seg->linearAddr, newSize);
if (!newMem) {
logErr("neload: realloc failed extending segment %d\n", segIdx + 1);
return false;
}
// Zero the new space
memset(newMem + oldSize, 0, extraSize);
uint32_t newPtrVal = (uint32_t)newMem;
uint32_t newLinAddr = newPtrVal + __djgpp_base_address;
seg->linearAddr = newPtrVal;
seg->size = newSize;
// Update LDT descriptor base (may have moved) and limit
__dpmi_set_segment_base_address(seg->selector, newLinAddr);
__dpmi_set_segment_limit(seg->selector, newSize - 1);
if (oldSizeOut) {
*oldSizeOut = oldSize;
}
return true;
}
// Enable debug output for the NE loader
void neSetDebug(bool enable)
{
gDebug = enable;
}

115
win31drv/neload.h Normal file
View file

@ -0,0 +1,115 @@
#ifndef NELOAD_H
#define NELOAD_H
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "neformat.h"
#include "wintypes.h"
// ============================================================================
// Loaded segment descriptor
// ============================================================================
typedef struct {
uint32_t linearAddr; // Linear (physical) address of segment data
uint16_t selector; // DPMI selector for this segment
uint16_t flags; // Original NE segment flags
uint32_t size; // Actual size in memory (from minAllocSize or fileLength)
uint32_t fileSize; // Size of data in the file (0 if no file data)
bool isCode; // true = code segment, false = data segment
} LoadedSegT;
// ============================================================================
// Resolved export entry
// ============================================================================
typedef struct {
uint16_t segIndex; // 1-based segment index
uint16_t offset; // Offset within segment
uint16_t selector; // DPMI selector (resolved at load time)
uint8_t flags; // Entry flags
} ExportEntryT;
// ============================================================================
// Import resolution callback
//
// Called by the NE loader when it encounters an imported reference.
// The callback should return the far pointer (selector:offset) that the
// import should resolve to. Return FARPTR16_NULL if the import cannot
// be resolved (the loader will log a warning and patch in a stub).
// ============================================================================
typedef FarPtr16T (*ImportResolverT)(const char *moduleName, uint16_t ordinal, const char *funcName);
// ============================================================================
// Loaded NE module
// ============================================================================
#define NE_MAX_SEGMENTS 64
#define NE_MAX_EXPORTS 2048
#define NE_MAX_MODREFS 16
typedef struct {
// Header info
NeHeaderT neHeader;
uint32_t neHeaderFileOffset; // File offset of NE header
uint16_t sectorAlignShift; // Sector alignment
// Module name (from resident name table)
char moduleName[64];
// Segments
uint16_t segmentCount;
LoadedSegT segments[NE_MAX_SEGMENTS];
// Exports (indexed by ordinal)
uint16_t exportCount;
ExportEntryT exports[NE_MAX_EXPORTS];
// Module references (imported module names)
uint16_t modRefCount;
char modRefNames[NE_MAX_MODREFS][32];
// Auto data segment selector (DGROUP)
uint16_t autoDataSel;
// DOS memory block (for conventional memory allocations)
int dosMemSeg; // DOS memory segment (real mode)
int dosMemSel; // DOS memory selector (PM)
uint32_t dosMemSize; // Size in bytes
// Is the module valid/loaded?
bool loaded;
} NeModuleT;
// ============================================================================
// NE loader functions
// ============================================================================
// Load a NE executable from a file.
// importResolver is called for each imported reference.
// Returns true on success.
bool neLoadModule(NeModuleT *mod, const char *filePath, ImportResolverT resolver);
// Unload a previously loaded module, freeing all segments and selectors.
void neUnloadModule(NeModuleT *mod);
// Look up an export by ordinal number.
// Returns true and fills seg/off/sel if found.
bool neLookupExport(const NeModuleT *mod, uint16_t ordinal, uint16_t *seg, uint16_t *off, uint16_t *sel);
// Look up an export by name (searches the resident name table in the loaded file).
// Returns the ordinal if found, 0 if not found.
uint16_t neLookupExportByName(const NeModuleT *mod, const char *name, const char *filePath);
// Extend a loaded segment by extraSize bytes.
// The new space is zeroed. *oldSizeOut receives the original size
// (i.e., the offset of the new area within the segment).
// Returns true on success. Fails if the result would exceed 64K.
bool neExtendSegment(NeModuleT *mod, int segIdx, uint32_t extraSize, uint32_t *oldSizeOut);
// Debug: dump module information to stderr.
void neDumpModule(const NeModuleT *mod);
#endif // NELOAD_H

1110
win31drv/thunk.c Normal file

File diff suppressed because it is too large Load diff

135
win31drv/thunk.h Normal file
View file

@ -0,0 +1,135 @@
#ifndef THUNK_H
#define THUNK_H
#include <stdint.h>
#include <stdbool.h>
#include "wintypes.h"
// ============================================================================
// 32-to-16 bit thunking layer
//
// Provides a mechanism for 32-bit DJGPP code to call into 16-bit Windows
// driver code running in 16-bit protected mode segments.
//
// Architecture:
// - A small 16-bit relay thunk is placed in a 16-bit code segment
// - Parameters are pre-built on a dedicated 16-bit stack
// - The relay handles 32/16-bit return address translation
// - Driver functions use Pascal calling convention (callee cleans stack)
//
// The relay thunk code:
// 1. Pops the 32-bit return address (8 bytes: 32-bit CS + 32-bit EIP)
// 2. Pushes parameters from the shared data area onto the 16-bit stack
// 3. Does a 16-bit far call to the target driver function
// 4. Saves the return value (DX:AX)
// 5. Pushes the 32-bit return address back
// 6. Does a 32-bit far return (o32 retf)
// ============================================================================
// ============================================================================
// Thunk shared data area (in 16-bit addressable memory)
//
// The 32-bit side writes target address and parameters here before
// calling the relay. The relay reads from here.
// ============================================================================
#define THUNK_MAX_PARAMS 32 // Max 16-bit words of parameters per call
typedef struct __attribute__((packed)) {
uint16_t targetOff; // 0x00: target function offset
uint16_t targetSeg; // 0x02: target function segment (selector)
uint16_t paramCount; // 0x04: number of 16-bit parameter words
uint16_t params[THUNK_MAX_PARAMS]; // 0x06: parameter words
} ThunkDataT;
// ============================================================================
// Thunk context (initialized once, used for all calls)
// ============================================================================
typedef struct {
// 16-bit relay code segment
uint16_t relayCodeSel; // Selector for relay code segment
uint32_t relayCodeBase; // Linear base address of relay code
uint16_t relayCodeSize; // Size of relay code
// 16-bit data segment (for ThunkDataT)
uint16_t dataSegSel; // Selector for shared data segment
uint32_t dataSegBase; // Linear base address of data segment
uint16_t dataSegSize; // Size of data segment
// 16-bit stack segment
uint16_t stackSel; // Selector for 16-bit stack
uint32_t stackBase; // Linear base address of stack
uint16_t stackSize; // Size of stack
// DOS memory for all 16-bit segments (single allocation)
int dosMemSeg; // Real-mode segment
int dosMemSel; // PM selector from DOS alloc
uint32_t dosMemSize; // Total bytes allocated
// Driver's auto data segment (DGROUP) selector.
// Set this before calling thunkCall16 so the relay loads DS correctly.
uint16_t dgroupSel;
bool initialized;
} ThunkContextT;
// ============================================================================
// Thunk layer functions
// ============================================================================
// Initialize the thunking infrastructure.
// Allocates DOS memory, creates 16-bit segments, installs relay code.
bool thunkInit(ThunkContextT *ctx);
// Enable or disable verbose callback tracing.
void thunkSetDebug(bool debug);
// Set a watchpoint on 3 bytes at sel:off. Logs any changes during callbacks.
void thunkSetWatch(uint16_t sel, uint32_t off);
// Shut down the thunking infrastructure and free resources.
void thunkShutdown(ThunkContextT *ctx);
// Call a 16-bit function via the thunk.
// targetSel:targetOff - far address of the 16-bit function
// params - array of 16-bit parameter words in Pascal order:
// params[0] = leftmost parameter (pushed first, deepest)
// params[N-1] = rightmost parameter (pushed last, top)
// paramCount - number of 16-bit words in params
//
// Returns DX:AX combined as a uint32_t (AX in low 16, DX in high 16).
// For functions returning WORD, just use the low 16 bits.
uint32_t thunkCall16(ThunkContextT *ctx, uint16_t targetSel, uint16_t targetOff, const uint16_t *params, uint16_t paramCount);
// Convenience: call with individual parameters (up to 12 words).
uint32_t thunkCall16v(ThunkContextT *ctx, uint16_t targetSel, uint16_t targetOff, uint16_t paramCount, ...);
// ============================================================================
// 16-bit stub generation
//
// For Windows API stubs that the driver calls back into, we need 16-bit
// entry points that thunk UP to 32-bit code.
// ============================================================================
// Callback function type for 16-to-32 callbacks.
// Receives the parameters as an array of 16-bit words.
// Returns DX:AX as uint32_t.
typedef uint32_t (*ThunkCallbackT)(uint16_t *params, uint16_t paramCount);
// Maximum number of registered callbacks
#define THUNK_MAX_CALLBACKS 128
// Register a callback and get a 16-bit far pointer (sel:off) that,
// when called from 16-bit code, will invoke the callback in 32-bit.
// paramWords = number of 16-bit parameter words the function expects
// (used by the stub to clean the stack with retf N).
// Returns true on success.
bool thunkRegisterCallback(ThunkContextT *ctx, ThunkCallbackT callback, uint16_t paramWords, FarPtr16T *result);
// Sanitize the callback frame after freeing a selector.
// If the saved ES or DS in the callback interrupt frame matches freedSel,
// zero it out so the IRET doesn't try to load a freed descriptor.
void thunkSanitizeCbFrame(uint16_t freedSel);
#endif // THUNK_H

260
win31drv/winddi.h Normal file
View file

@ -0,0 +1,260 @@
#ifndef WINDDI_H
#define WINDDI_H
#include "wintypes.h"
// ============================================================================
// GDIINFO - Device capabilities structure filled by Enable()
// This is the 16-bit Windows 3.1 DDK GDIINFO structure.
// ============================================================================
typedef struct __attribute__((packed)) {
int16_t dpVersion; // 0x00: driver version (0x030A for 3.10)
int16_t dpTechnology; // 0x02: device technology
int16_t dpHorzSize; // 0x04: horizontal size in mm
int16_t dpVertSize; // 0x06: vertical size in mm
int16_t dpHorzRes; // 0x08: horizontal resolution (pixels)
int16_t dpVertRes; // 0x0A: vertical resolution (pixels)
int16_t dpBitsPixel; // 0x0C: bits per pixel
int16_t dpPlanes; // 0x0E: number of bit planes
int16_t dpNumBrushes; // 0x10: number of device brushes
int16_t dpNumPens; // 0x12: number of device pens
int16_t dpNumFonts; // 0x14: number of device fonts
int16_t dpNumColors; // 0x16: number of colors in color table
int16_t dpDEVICEsize; // 0x18: size of PDEVICE structure
uint16_t dpCurves; // 0x1A: curve capabilities
uint16_t dpLines; // 0x1C: line capabilities
uint16_t dpPolygonals; // 0x1E: polygon capabilities
uint16_t dpText; // 0x20: text capabilities
uint16_t dpClip; // 0x22: clipping capabilities
uint16_t dpRaster; // 0x24: raster capabilities
int16_t dpAspectX; // 0x26: x aspect ratio
int16_t dpAspectY; // 0x28: y aspect ratio
int16_t dpAspectXY; // 0x2A: diagonal aspect ratio
int16_t dpStyleLen; // 0x2C: length of styled line segment
Point16T dpMLoWin; // 0x2E: metric lo-res window
Point16T dpMLoVpt; // 0x32: metric lo-res viewport
Point16T dpMHiWin; // 0x36: metric hi-res window
Point16T dpMHiVpt; // 0x3A: metric hi-res viewport
Point16T dpELoWin; // 0x3E: english lo-res window
Point16T dpELoVpt; // 0x42: english lo-res viewport
Point16T dpEHiWin; // 0x46: english hi-res window
Point16T dpEHiVpt; // 0x4A: english hi-res viewport
Point16T dpTwpWin; // 0x4E: twips window
Point16T dpTwpVpt; // 0x52: twips viewport
int16_t dpLogPixelsX; // 0x56: logical pixels per inch X
int16_t dpLogPixelsY; // 0x58: logical pixels per inch Y
int16_t dpDCManage; // 0x5A: DC management flags
uint16_t reserved1[5]; // 0x5C: reserved
uint16_t dpPalColors; // 0x66: number of palette colors
uint16_t dpPalReserved; // 0x68: number of reserved palette entries
uint16_t dpPalResolution; // 0x6A: palette DAC resolution (bits per gun)
} GdiInfo16T;
// dpTechnology values
#define DT_PLOTTER 0
#define DT_RASDISPLAY 1
#define DT_RASPRINTER 2
#define DT_RASCAMERA 3
#define DT_CHARSTREAM 4
#define DT_METAFILE 5
#define DT_DISPFILE 6
// dpRaster capability bits
#define RC_BITBLT 0x0001
#define RC_BANDING 0x0002
#define RC_SCALING 0x0004
#define RC_BITMAP64 0x0008
#define RC_GDI20_OUTPUT 0x0010
#define RC_DI_BITMAP 0x0080
#define RC_PALETTE 0x0100
#define RC_DIBTODEV 0x0200
#define RC_BIGFONT 0x0400
#define RC_STRETCHBLT 0x0800
#define RC_FLOODFILL 0x1000
#define RC_STRETCHDIB 0x2000
// ============================================================================
// PDEVICE - Physical device descriptor
// The first word indicates the type. The rest is driver-specific.
// We allocate a generous buffer for the driver to fill in.
// ============================================================================
#define PDEVICE_MAX_SIZE 4096
typedef struct __attribute__((packed)) {
int16_t pdType; // 0 = memory bitmap, nonzero = physical device
uint8_t pdData[PDEVICE_MAX_SIZE - 2]; // driver-specific data
} PDevice16T;
// ============================================================================
// DRAWMODE - Drawing mode structure (passed to BitBlt, Output, etc.)
// ============================================================================
typedef struct __attribute__((packed)) {
int16_t rop2; // 0x00: raster operation (R2_*)
int16_t bkMode; // 0x02: background mode (TRANSPARENT=1, OPAQUE=2)
uint32_t bkColor; // 0x04: background color (physical)
uint32_t textColor; // 0x08: text color (physical)
int16_t tBreakExtra; // 0x0C: total break extra
int16_t breakExtra; // 0x0E: break extra per char
int16_t breakErr; // 0x10: accumulated break error
int16_t breakRem; // 0x12: break remainder
int16_t breakCount; // 0x14: break count
int16_t charExtra; // 0x16: extra pixels per char
uint32_t lbkColor; // 0x18: logical background color
uint32_t ltextColor; // 0x1C: logical text color
uint16_t icrBk; // 0x20: index to background color
uint16_t icrText; // 0x22: index to text color
} DrawMode16T;
// Background mode constants
#define BM_TRANSPARENT 1
#define BM_OPAQUE 2
// ============================================================================
// Logical brush (for RealizeObject)
// ============================================================================
typedef struct __attribute__((packed)) {
uint16_t lbStyle; // Brush style
uint32_t lbColor; // Brush color (COLORREF)
int16_t lbHatch; // Hatch pattern
uint32_t lbBkColor; // Background color (Win 3.1)
} LogBrush16T;
// Brush styles
#define BS_SOLID 0
#define BS_HOLLOW 1
#define BS_NULL 1
#define BS_HATCHED 2
#define BS_PATTERN 3
#define BS_DIBPATTERN 5
// Hatch styles
#define HS_HORIZONTAL 0
#define HS_VERTICAL 1
#define HS_FDIAGONAL 2
#define HS_BDIAGONAL 3
#define HS_CROSS 4
#define HS_DIAGCROSS 5
// ============================================================================
// Logical pen (for RealizeObject)
// ============================================================================
typedef struct __attribute__((packed)) {
uint16_t lopnStyle; // Pen style
Point16T lopnWidth; // Pen width
uint32_t lopnColor; // Pen color (COLORREF)
} LogPen16T;
// Pen styles
#define PS_SOLID 0
#define PS_DASH 1
#define PS_DOT 2
#define PS_DASHDOT 3
#define PS_DASHDOTDOT 4
#define PS_NULL 5
#define PS_INSIDEFRAME 6
// ============================================================================
// CURSORINFO - Cursor shape description
// ============================================================================
typedef struct __attribute__((packed)) {
int16_t csHotX; // Hotspot X
int16_t csHotY; // Hotspot Y
int16_t csWidth; // Cursor width
int16_t csHeight; // Cursor height
int16_t csWidthB; // Width in bytes
int16_t csColor; // Planes * bitsPixel
} CursorInfo16T;
// ============================================================================
// Enable() style parameter values
// ============================================================================
#define ENABLE_INQUIRE 0 // First call: fill GDIINFO
#define ENABLE_ENABLE 1 // Second call: initialize PDEVICE
// ============================================================================
// Output() style values
// ============================================================================
#define OS_ARC 3
#define OS_SCANLINES 4
#define OS_RECTANGLE 6
#define OS_ELLIPSE 7
#define OS_MARKER 8
#define OS_POLYLINE 18
#define OS_ALTPOLYGON 22
#define OS_WINDPOLYGON 20
#define OS_PIE 23
#define OS_POLYMARKER 24
#define OS_CHORD 39
#define OS_CIRCLE 55
#define OS_ROUNDRECT 72
// ============================================================================
// Control() function codes
// ============================================================================
#define CTRL_GETSCALINGFACTOR 14
#define CTRL_RESETDEVICE 128
#define CTRL_MOUSETRAILS 39
// ============================================================================
// RealizeObject() styles
// ============================================================================
#define OBJ_PEN 1
#define OBJ_BRUSH 2
#define OBJ_FONT 3
// ============================================================================
// Physical brush/pen structures (driver-specific, maximum size)
// ============================================================================
#define PHYS_OBJ_MAX_SIZE 128
typedef struct __attribute__((packed)) {
uint8_t data[PHYS_OBJ_MAX_SIZE];
} PhysObj16T;
// ============================================================================
// DIBENGINE structures (for drivers that use the DIB engine)
// ============================================================================
// DIB_BitmapInfo passed to DIB engine functions
typedef struct __attribute__((packed)) {
int16_t bmType;
int16_t bmWidth;
int16_t bmHeight;
int16_t bmWidthBytes;
uint8_t bmPlanes;
uint8_t bmBitsPixel;
uint32_t bmBits; // Far pointer to bits (as DWORD)
uint32_t bmWidthPlanes;
uint32_t bmBitsLong; // Selector:0 far pointer
uint16_t bmSegmentIndex;
uint16_t bmScanSegment;
uint16_t bmFillBytes;
uint16_t reserved1;
uint16_t reserved2;
} DibBitmapInfo16T;
// DIB engine PDEVICE extension (placed at start of PDEVICE by DIB-based drivers)
typedef struct __attribute__((packed)) {
int16_t deType; // Device type
uint16_t deWidth; // Width in pixels
uint16_t deHeight; // Height in pixels
uint16_t deWidthBytes; // Bytes per scan line
uint8_t dePlanes; // Number of planes
uint8_t deBitsPixel; // Bits per pixel
uint32_t delpPDevice; // Pointer to next PDEVICE
uint32_t dlpColorTable; // Pointer to color table
// ... additional fields follow
} DibPDevice16T;
#endif // WINDDI_H

3301
win31drv/windrv.c Normal file

File diff suppressed because it is too large Load diff

189
win31drv/windrv.h Normal file
View file

@ -0,0 +1,189 @@
#ifndef WINDRV_H
#define WINDRV_H
// ============================================================================
// windrv.h - Public API for using Windows 3.x display drivers from DOS
//
// This library loads Windows 3.x accelerated display drivers (16-bit NE
// format DLLs) and provides a clean 32-bit C API for DOS programs compiled
// with DJGPP to use their hardware-accelerated drawing functions.
//
// The library handles:
// - NE executable loading with segment relocation
// - 32-bit to 16-bit protected mode thunking via DPMI
// - Windows API stub functions that drivers import
// - DDI (Device Driver Interface) function wrappers
//
// Usage:
// 1. Call wdrvInit() to initialize the library
// 2. Call wdrvLoadDriver() with path to a .DRV file
// 3. Call wdrvEnable() to set a video mode
// 4. Use drawing functions (wdrvBitBlt, wdrvLine, etc.)
// 5. Call wdrvDisable() to restore text mode
// 6. Call wdrvUnloadDriver() and wdrvShutdown() to clean up
// ============================================================================
#include <stdint.h>
#include <stdbool.h>
#include "wintypes.h"
// ============================================================================
// Error codes
// ============================================================================
#define WDRV_OK 0
#define WDRV_ERR_INIT -1 // Initialization failed
#define WDRV_ERR_NO_DPMI -2 // DPMI not available or insufficient
#define WDRV_ERR_FILE_NOT_FOUND -3 // Driver file not found
#define WDRV_ERR_BAD_FORMAT -4 // Not a valid NE executable
#define WDRV_ERR_LOAD_FAILED -5 // Failed to load driver segments
#define WDRV_ERR_NO_MEMORY -6 // Out of memory (conventional or extended)
#define WDRV_ERR_RELOC_FAILED -7 // Relocation processing failed
#define WDRV_ERR_NO_ENTRY -8 // Required DDI entry point not found
#define WDRV_ERR_ENABLE_FAILED -9 // Driver Enable() call failed
#define WDRV_ERR_THUNK_FAILED -10 // Thunk setup failed
#define WDRV_ERR_NOT_LOADED -11 // No driver loaded
#define WDRV_ERR_NOT_ENABLED -12 // Driver not enabled
#define WDRV_ERR_UNSUPPORTED -13 // Operation not supported by driver
// ============================================================================
// Opaque driver handle
// ============================================================================
typedef struct WdrvDriverS *WdrvHandleT;
// ============================================================================
// Driver information (returned by wdrvGetInfo)
// ============================================================================
typedef struct {
char driverName[64]; // Module name from NE header
uint16_t driverVersion; // Driver version number
int32_t maxWidth; // Maximum supported width
int32_t maxHeight; // Maximum supported height
int32_t maxBpp; // Maximum bits per pixel
int32_t numColors; // Number of colors
uint32_t rasterCaps; // Raster capability bits (RC_*)
bool hasBitBlt; // Driver exports BitBlt
bool hasOutput; // Driver exports Output (lines, shapes)
bool hasPixel; // Driver exports Pixel
bool hasStretchBlt; // Driver exports StretchBlt
bool hasExtTextOut; // Driver exports ExtTextOut
bool hasSetPalette; // Driver exports SetPalette
bool hasSetCursor; // Driver exports SetCursor
} WdrvInfoT;
// ============================================================================
// BitBlt parameters
// ============================================================================
typedef struct {
int16_t dstX;
int16_t dstY;
int16_t srcX;
int16_t srcY;
int16_t width;
int16_t height;
uint32_t rop3; // Raster operation (SRCCOPY, PATCOPY, etc.)
} WdrvBitBltParamsT;
// ============================================================================
// Library initialization / shutdown
// ============================================================================
// Initialize the library. Must be called before any other functions.
// Sets up DPMI descriptors, thunk infrastructure, and API stubs.
int32_t wdrvInit(void);
// Shut down the library and free all resources.
void wdrvShutdown(void);
// ============================================================================
// Driver loading
// ============================================================================
// Load a Windows 3.x display driver (.DRV file).
// Returns a driver handle on success, NULL on failure.
// Call wdrvGetLastError() for details on failure.
WdrvHandleT wdrvLoadDriver(const char *driverPath);
// Unload a previously loaded driver.
void wdrvUnloadDriver(WdrvHandleT handle);
// Get information about a loaded driver.
// The driver must be loaded but need not be enabled.
int32_t wdrvGetInfo(WdrvHandleT handle, WdrvInfoT *info);
// ============================================================================
// Mode setting
// ============================================================================
// Enable the driver (set video mode and initialize hardware).
// width/height/bpp are the requested mode; the driver may adjust.
// Pass 0 for defaults (driver's preferred resolution).
int32_t wdrvEnable(WdrvHandleT handle, int32_t width, int32_t height, int32_t bpp);
// Disable the driver (restore text mode, release hardware).
int32_t wdrvDisable(WdrvHandleT handle);
// ============================================================================
// Drawing operations
// ============================================================================
// Block transfer (hardware-accelerated if supported).
int32_t wdrvBitBlt(WdrvHandleT handle, WdrvBitBltParamsT *params);
// Solid rectangle fill using PatBlt with a solid brush.
int32_t wdrvFillRect(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color);
// Set a single pixel.
int32_t wdrvSetPixel(WdrvHandleT handle, int16_t x, int16_t y, uint32_t color);
// Get a single pixel's color.
uint32_t wdrvGetPixel(WdrvHandleT handle, int16_t x, int16_t y);
// Draw a polyline using the Output DDI function.
int32_t wdrvPolyline(WdrvHandleT handle, Point16T *points, int16_t count, uint32_t color);
// Draw a rectangle outline.
int32_t wdrvRectangle(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color);
// ============================================================================
// Palette operations (for 8bpp modes)
// ============================================================================
// Set palette entries. colors is an array of RGBQUAD (R,G,B,flags).
int32_t wdrvSetPalette(WdrvHandleT handle, int32_t startIndex, int32_t count, const uint8_t *colors);
// ============================================================================
// Direct framebuffer access
// ============================================================================
// Get a near pointer to the linear framebuffer (if available).
// Returns NULL if the driver doesn't provide linear access.
void *wdrvGetFramebuffer(WdrvHandleT handle);
// Get the framebuffer pitch (bytes per scanline).
int32_t wdrvGetPitch(WdrvHandleT handle);
// ============================================================================
// Error reporting
// ============================================================================
// Get the last error code.
int32_t wdrvGetLastError(void);
// Get a human-readable description of the last error.
const char *wdrvGetLastErrorString(void);
// ============================================================================
// Debugging
// ============================================================================
// Enable/disable verbose debug output to stderr.
void wdrvSetDebug(bool enable);
// Dump all segment base addresses for debugging.
void wdrvDumpSegmentBases(WdrvHandleT handle);
#endif // WINDRV_H

1400
win31drv/winstub.c Normal file

File diff suppressed because it is too large Load diff

257
win31drv/winstub.h Normal file
View file

@ -0,0 +1,257 @@
#ifndef WINSTUB_H
#define WINSTUB_H
#include <stdint.h>
#include <stdbool.h>
#include "wintypes.h"
#include "thunk.h"
#include "neload.h"
// ============================================================================
// Windows API stub layer
//
// Provides minimal implementations of KERNEL, GDI, and USER functions
// that Windows 3.x display drivers import. These stubs are registered as
// 16-bit callbacks via the thunking layer so the driver can call them.
//
// Supported modules:
// KERNEL - Memory management (GlobalAlloc/Lock/Free), module queries,
// selector management, system info
// GDI - Minimal DC management, palette, object stubs
// USER - GetSystemMetrics, MessageBox (stub)
// ============================================================================
// ============================================================================
// Stub context
// ============================================================================
#define STUB_MAX_ALLOCS 64 // Max GlobalAlloc blocks
#define STUB_MAX_SELECTORS 32 // Extra selector allocations
// Pre-allocated DOS memory pool for GlobalDOSAlloc.
// Avoids calling __dpmi_allocate_dos_memory at runtime (which uses INT
// 21h AH=48h and walks the MCB chain) by bump-allocating from a single
// block allocated at init time.
#define STUB_DOS_POOL_SIZE 0x4000 // 16KB pool
#define STUB_MAX_DOS_ALLOCS 8
typedef struct {
// Memory allocation tracking
struct {
uint16_t handle; // HGLOBAL16
uint32_t linearAddr; // Linear address
uint16_t selector; // PM selector for the block
uint32_t size; // Block size
uint16_t lockCount; // Lock count
bool inUse;
} allocs[STUB_MAX_ALLOCS];
uint16_t nextHandle; // Next handle value to assign
// Extra selectors (for AllocSelector, AllocCStoDSAlias, etc.)
struct {
uint16_t selector;
bool inUse;
} selectors[STUB_MAX_SELECTORS];
// DOS memory pool for GlobalDOSAlloc (pre-allocated at init)
struct {
uint16_t paraOff; // Paragraph offset from pool base
uint16_t paragraphs; // Size in paragraphs
uint16_t selector; // PM selector for this sub-block
bool inUse;
} dosAllocs[STUB_MAX_DOS_ALLOCS];
uint16_t dosPoolSeg; // Real-mode segment of pool
uint16_t dosPoolSel; // DPMI selector for pool (for freeing)
uint16_t dosPoolParas; // Total pool size in paragraphs
uint16_t dosPoolNextPara; // Next free paragraph offset (bump allocator)
// Well-known memory region selectors
uint16_t biosDataSel; // 0040:0000 BIOS data area
uint16_t vramSel; // A000:0000 VGA graphics RAM
uint16_t monoTextSel; // B000:0000 Mono text video
uint16_t colorTextSel; // B800:0000 Color text video
uint16_t videoBiosSel; // C000:0000 Video BIOS ROM
uint16_t upperMemD000Sel; // D000:0000 Upper memory
uint16_t upperMemE000Sel; // E000:0000 Upper memory
uint16_t sysBiosSel; // F000:0000 System BIOS ROM
// Thunk context reference
ThunkContextT *thunkCtx;
// NE module reference (for GetProcAddress lookups)
NeModuleT *neModule;
// Lookup table: module name + ordinal -> FarPtr16T
// Built during stub registration
struct {
char module[16];
uint16_t ordinal;
FarPtr16T addr;
} stubTable[256];
uint16_t stubCount;
bool initialized;
} StubContextT;
// ============================================================================
// Stub layer functions
// ============================================================================
// Initialize the stub layer. Must be called after thunkInit().
bool stubInit(StubContextT *ctx, ThunkContextT *thunkCtx);
// Enable or disable verbose stub logging.
void stubSetDebug(bool debug);
// Shut down the stub layer.
void stubShutdown(StubContextT *ctx);
// Set the NE module reference for GetProcAddress lookups.
void stubSetModule(StubContextT *ctx, NeModuleT *mod);
// Resolve an imported function. Called by the NE loader's import resolver.
// Returns the 16-bit far pointer to the stub function, or FARPTR16_NULL
// if the import is unknown.
FarPtr16T stubResolveImport(StubContextT *ctx, const char *moduleName, uint16_t ordinal, const char *funcName);
// ============================================================================
// KERNEL stubs - ordinal numbers
// These are the most commonly imported KERNEL functions by display drivers.
// ============================================================================
// Error handling
#define KERNEL_ORD_FATALEXIT 1
#define KERNEL_ORD_FATALAPPEXIT 137
// Memory management - Global
#define KERNEL_ORD_GLOBALALLOC 15
#define KERNEL_ORD_GLOBALREALLOC 16
#define KERNEL_ORD_GLOBALFREE 17
#define KERNEL_ORD_GLOBALLOCK 18
#define KERNEL_ORD_GLOBALUNLOCK 19
#define KERNEL_ORD_GLOBALSIZE 20
#define KERNEL_ORD_GLOBALFLAGS 22
#define KERNEL_ORD_GLOBALDOSALLOC 84
#define KERNEL_ORD_GLOBALDOSFREE 85
#define KERNEL_ORD_GLOBALDOSALLOC2 184 // Duplicate ordinal for GlobalDOSAlloc
#define KERNEL_ORD_GLOBALDOSFREE2 185 // Duplicate ordinal for GlobalDOSFree
// Memory management - Local
#define KERNEL_ORD_LOCALINIT 4
#define KERNEL_ORD_LOCALALLOC 5
#define KERNEL_ORD_LOCALREALLOC 6
#define KERNEL_ORD_LOCALFREE 7
#define KERNEL_ORD_LOCALLOCK 8
#define KERNEL_ORD_LOCALUNLOCK 9
#define KERNEL_ORD_LOCALSIZE 10
// Memory info
#define KERNEL_ORD_GETFREESPACE 102
#define KERNEL_ORD_LOCKSEGMENT 23
#define KERNEL_ORD_UNLOCKSEGMENT 24
#define KERNEL_ORD_SETSWAPAREA 81
#define KERNEL_ORD_GETCURRENTPDB 82
// Module management
#define KERNEL_ORD_GETMODULEHANDLE 47
#define KERNEL_ORD_GETMODULEUSAGE 35
#define KERNEL_ORD_GETPROFILEINT 48
#define KERNEL_ORD_GETPROFILEINT2 57 // Alternate ordinal used by some drivers
#define KERNEL_ORD_WRITEPROFILESTRING 59
#define KERNEL_ORD_GETMODULEFILENAME 49
#define KERNEL_ORD_GETPROCADDRESS 50
#define KERNEL_ORD_LOADLIBRARY 95
#define KERNEL_ORD_FREELIBRARY 96
#define KERNEL_ORD_INITTASK 91
#define KERNEL_ORD_GETEXEPTR 133
// Resource management
#define KERNEL_ORD_FINDRESOURCE 60
#define KERNEL_ORD_LOADRESOURCE 61
#define KERNEL_ORD_FREERESOURCE 63
#define KERNEL_ORD_LOCKRESOURCE 62
#define KERNEL_ORD_SIZEOFRESOURCE 65
// Selector management
#define KERNEL_ORD_ALLOCSELECTOR 175
#define KERNEL_ORD_FREESELECTOR 176
#define KERNEL_ORD_ALLOCCSTODSALIAS 170
#define KERNEL_ORD_ALLOCDSTOCSALIAS 171
#define KERNEL_ORD_SETSELECTORBASE 187
#define KERNEL_ORD_GETSELECTORBASE 186
#define KERNEL_ORD_SETSELECTORLIMIT 189
#define KERNEL_ORD_PRESTOCHANGOSELECTOR 177
#define KERNEL_ORD_SELECTORACCESSRIGHTS 196
#define KERNEL_ORD_ALLOCSELECTORARRAY 206
// System info
#define KERNEL_ORD_GETVERSION 3
#define KERNEL_ORD_GETWINFLAGS 132
#define KERNEL_ORD_GETSYSTEMDIRECTORY 135
#define KERNEL_ORD_GETDOSENVIRONMENT 131
#define KERNEL_ORD_GETPRIVATEPROFILEINT 127
#define KERNEL_ORD_GETPRIVATEPROFILESTRING 128
#define KERNEL_ORD_WRITEPRIVATEPROFILESTRING 129
// Selector arithmetic (variables, not functions)
#define KERNEL_ORD___AHSHIFT 113
#define KERNEL_ORD___AHINCR 114
#define KERNEL_ORD___WINFLAGS 178
// Segment selectors to well-known memory regions (variables, not functions)
#define KERNEL_ORD___0000H 183
#define KERNEL_ORD___0040H 193
#define KERNEL_ORD___A000H 174
#define KERNEL_ORD___B000H 181
#define KERNEL_ORD___B800H 182
#define KERNEL_ORD___C000H 195
#define KERNEL_ORD___D000H 179
#define KERNEL_ORD___E000H 190
#define KERNEL_ORD___F000H 194
#define KERNEL_ORD___ROMBIOS 173
// Selector query
#define KERNEL_ORD_GETSELECTORLIMIT 188
#define KERNEL_ORD_GETSELECTORBASE2 186
// Debug
#define KERNEL_ORD_OUTPUTDEBUGSTRING 115
// ============================================================================
// GDI stubs - ordinal numbers
// ============================================================================
#define GDI_ORD_CREATEDC 53
#define GDI_ORD_DELETEDC 68
#define GDI_ORD_SELECTOBJECT 45
#define GDI_ORD_DELETEOBJECT 69
#define GDI_ORD_GETDEVICECAPS 80
#define GDI_ORD_SETBKCOLOR 1
#define GDI_ORD_SETTEXTCOLOR 6
#define GDI_ORD_GETPALETTE_GDI 5
// DIB Engine exports (from DIBENG.DLL or built into GDI)
#define DIBENG_ORD_DIBBITBLT 1
#define DIBENG_ORD_DIBOUTPUT 2
#define DIBENG_ORD_DIBPIXEL 3
#define DIBENG_ORD_DIBSTRBLT 4
#define DIBENG_ORD_DIBCOLORINFO 5
#define DIBENG_ORD_DIBREALIZE 6
#define DIBENG_ORD_DIBCREATEBITMAP 7
#define DIBENG_ORD_DIBSCANLR 8
#define DIBENG_ORD_DIBEXTOUT 9
// ============================================================================
// USER stubs - ordinal numbers
// ============================================================================
#define USER_ORD_GETSYSTEMMETRICS 179
#define USER_ORD_MESSAGEBOX 1
// ============================================================================
// KEYBOARD stubs - ordinal numbers
// ============================================================================
#define KEYBOARD_ORD_SCREENSWITCHENABLE 100
#endif // WINSTUB_H

152
win31drv/wintypes.h Normal file
View file

@ -0,0 +1,152 @@
#ifndef WINTYPES_H
#define WINTYPES_H
#include <stdint.h>
// ============================================================================
// Windows 16-bit basic types
// ============================================================================
typedef uint16_t WORD;
typedef int16_t SWORD;
typedef uint32_t DWORD;
typedef int32_t LONG;
typedef uint8_t BYTE;
typedef int16_t BOOL16;
// 16-bit handle types
typedef uint16_t HANDLE16;
typedef uint16_t HWND16;
typedef uint16_t HDC16;
typedef uint16_t HMODULE16;
typedef uint16_t HINSTANCE16;
typedef uint16_t HGLOBAL16;
typedef uint16_t HBITMAP16;
typedef uint16_t HBRUSH16;
typedef uint16_t HPEN16;
typedef uint16_t HPALETTE16;
typedef uint16_t HRGN16;
typedef uint16_t HCURSOR16;
typedef uint16_t ATOM16;
// ============================================================================
// Far pointer types (16:16 segment:offset, stored little-endian)
// ============================================================================
typedef struct __attribute__((packed)) {
uint16_t offset;
uint16_t segment;
} FarPtr16T;
#define FARPTR16_NULL ((FarPtr16T){0, 0})
static inline FarPtr16T makeFarPtr16(uint16_t seg, uint16_t off)
{
FarPtr16T fp;
fp.segment = seg;
fp.offset = off;
return fp;
}
static inline uint32_t farPtr16ToLinear(FarPtr16T fp)
{
// For real-mode addresses only; PM addresses need descriptor lookup
return ((uint32_t)fp.segment << 4) + fp.offset;
}
// ============================================================================
// Color types
// ============================================================================
typedef uint32_t ColorRefT;
#define RGB_RED(c) ((uint8_t)(c))
#define RGB_GREEN(c) ((uint8_t)((c) >> 8))
#define RGB_BLUE(c) ((uint8_t)((c) >> 16))
#define MAKE_RGB(r, g, b) ((ColorRefT)(((BYTE)(r)) | ((WORD)((BYTE)(g)) << 8) | ((DWORD)((BYTE)(b)) << 16)))
// Palette index color reference
#define PALETTEINDEX(i) ((ColorRefT)(0x01000000L | (DWORD)(WORD)(i)))
// ============================================================================
// Geometry types (16-bit, packed for compatibility with Win16 structures)
// ============================================================================
typedef struct __attribute__((packed)) {
int16_t x;
int16_t y;
} Point16T;
typedef struct __attribute__((packed)) {
int16_t left;
int16_t top;
int16_t right;
int16_t bottom;
} Rect16T;
// ============================================================================
// Windows constants
// ============================================================================
// GetWinFlags return values
#define WF_PMODE 0x0001
#define WF_CPU286 0x0002
#define WF_CPU386 0x0004
#define WF_CPU486 0x0008
#define WF_STANDARD 0x0010
#define WF_ENHANCED 0x0020
#define WF_80x87 0x0400
// GlobalAlloc flags
#define GMEM_FIXED 0x0000
#define GMEM_MOVEABLE 0x0002
#define GMEM_ZEROINIT 0x0040
#define GHND (GMEM_MOVEABLE | GMEM_ZEROINIT)
#define GPTR (GMEM_FIXED | GMEM_ZEROINIT)
// Boolean
#define WIN_FALSE 0
#define WIN_TRUE 1
// Null handle
#define NULL16 ((uint16_t)0)
// ============================================================================
// Raster operation codes (ROP2 and ROP3)
// ============================================================================
#define R2_BLACK 1
#define R2_NOTMERGEPEN 2
#define R2_MASKNOTPEN 3
#define R2_NOTCOPYPEN 4
#define R2_MASKPENNOT 5
#define R2_NOT 6
#define R2_XORPEN 7
#define R2_NOTMASKPEN 8
#define R2_MASKPEN 9
#define R2_NOTXORPEN 10
#define R2_NOP 11
#define R2_MERGENOTPEN 12
#define R2_COPYPEN 13
#define R2_MERGEPENNOT 14
#define R2_MERGEPEN 15
#define R2_WHITE 16
// Common ROP3 codes
#define SRCCOPY 0x00CC0020L
#define SRCPAINT 0x00EE0086L
#define SRCAND 0x008800C6L
#define SRCINVERT 0x00660046L
#define SRCERASE 0x00440328L
#define NOTSRCCOPY 0x00330008L
#define NOTSRCERASE 0x001100A6L
#define MERGECOPY 0x00C000CAL
#define MERGEPAINT 0x00BB0226L
#define PATCOPY 0x00F00021L
#define PATPAINT 0x00FB0A09L
#define PATINVERT 0x005A0049L
#define DSTINVERT 0x00550009L
#define BLACKNESS 0x00000042L
#define WHITENESS 0x00FF0062L
#endif // WINTYPES_H