WinDriver/win31drv/winstub.c
2026-02-21 18:01:54 -06:00

1400 lines
46 KiB
C

// ============================================================================
// winstub.c - Windows API function stubs
//
// Provides minimal implementations of KERNEL, GDI, USER, and DIBENG
// functions that Windows 3.x display drivers import. Each stub is
// registered as a 16-bit callback via the thunking layer.
//
// These stubs implement just enough behavior for a display driver to
// initialize and perform basic drawing operations. Many stubs simply
// return success without doing real work.
// ============================================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dpmi.h>
#include <go32.h>
#include <sys/nearptr.h>
#include <sys/movedata.h>
#include "thunk.h"
#include "winstub.h"
#include "wintypes.h"
#include "log.h"
static bool gStubDebug = false;
// Forward declarations - KERNEL stubs
static uint32_t stubFatalExit(uint16_t *p, uint16_t n);
static uint32_t stubGlobalAlloc(uint16_t *p, uint16_t n);
static uint32_t stubGlobalFree(uint16_t *p, uint16_t n);
static uint32_t stubGlobalLock(uint16_t *p, uint16_t n);
static uint32_t stubGlobalUnlock(uint16_t *p, uint16_t n);
static uint32_t stubGlobalRealloc(uint16_t *p, uint16_t n);
static uint32_t stubGlobalSize(uint16_t *p, uint16_t n);
static uint32_t stubGlobalDOSAlloc(uint16_t *p, uint16_t n);
static uint32_t stubGlobalDOSFree(uint16_t *p, uint16_t n);
static uint32_t stubLocalInit(uint16_t *p, uint16_t n);
static uint32_t stubLocalAlloc(uint16_t *p, uint16_t n);
static uint32_t stubLocalReAlloc(uint16_t *p, uint16_t n);
static uint32_t stubLocalFree(uint16_t *p, uint16_t n);
static uint32_t stubLocalLock(uint16_t *p, uint16_t n);
static uint32_t stubLocalUnlock(uint16_t *p, uint16_t n);
static uint32_t stubLocalSize(uint16_t *p, uint16_t n);
static uint32_t stubGetFreeSpace(uint16_t *p, uint16_t n);
static uint32_t stubGetCurrentPDB(uint16_t *p, uint16_t n);
static uint32_t stubGetModuleHandle(uint16_t *p, uint16_t n);
static uint32_t stubGetModuleUsage(uint16_t *p, uint16_t n);
static uint32_t stubGetProcAddress(uint16_t *p, uint16_t n);
static uint32_t stubGetWinFlags(uint16_t *p, uint16_t n);
static uint32_t stubGetVersion(uint16_t *p, uint16_t n);
static uint32_t stubGetProfileInt(uint16_t *p, uint16_t n);
static uint32_t stubGetPrivateProfileInt(uint16_t *p, uint16_t n);
static uint32_t stubGetPrivateProfileString(uint16_t *p, uint16_t n);
static uint32_t stubGetDOSEnvironment(uint16_t *p, uint16_t n);
static uint32_t stubGetSystemDirectory(uint16_t *p, uint16_t n);
static uint32_t stubAllocSelector(uint16_t *p, uint16_t n);
static uint32_t stubFreeSelector(uint16_t *p, uint16_t n);
static uint32_t stubAllocCStoDSAlias(uint16_t *p, uint16_t n);
static uint32_t stubAllocDSToCSAlias(uint16_t *p, uint16_t n);
static uint32_t stubLoadLibrary(uint16_t *p, uint16_t n);
static uint32_t stubPrestoChangoSelector(uint16_t *p, uint16_t n);
static uint32_t stubSetSelectorBase(uint16_t *p, uint16_t n);
static uint32_t stubSetSelectorLimit(uint16_t *p, uint16_t n);
static uint32_t stubGetSelectorLimit(uint16_t *p, uint16_t n);
static uint32_t stubGetSelectorBase(uint16_t *p, uint16_t n);
static uint32_t stubSelectorAccessRights(uint16_t *p, uint16_t n);
static uint32_t stubGetModuleFileName(uint16_t *p, uint16_t n);
static uint32_t stubOutputDebugString(uint16_t *p, uint16_t n);
static uint32_t stubWriteProfileString(uint16_t *p, uint16_t n);
static uint32_t stubGetExePtr(uint16_t *p, uint16_t n);
// Forward declarations - GDI stubs
static uint32_t stubGetDeviceCaps(uint16_t *p, uint16_t n);
static uint32_t stubDummy(uint16_t *p, uint16_t n);
// Forward declarations - USER stubs
static uint32_t stubGetSystemMetrics(uint16_t *p, uint16_t n);
static uint32_t stubMessageBox(uint16_t *p, uint16_t n);
// Helper to register a single stub and add it to the lookup table
static bool registerStub(StubContextT *ctx, const char *module, uint16_t ordinal,
ThunkCallbackT func, uint16_t paramWords);
void stubSetDebug(bool debug)
{
gStubDebug = debug;
}
bool stubInit(StubContextT *ctx, ThunkContextT *thunkCtx)
{
memset(ctx, 0, sizeof(StubContextT));
ctx->thunkCtx = thunkCtx;
ctx->nextHandle = 0x1000; // Start handle allocation here
// Create well-known memory region selectors.
// Win 3.x drivers import these as KERNEL "variables" (__A000H, __0040H, etc.)
// and use them for direct hardware access.
// Helper macro: allocate a 16-bit data selector for a physical address range
#define MAKE_MEM_SEL(field, physAddr, limit) do { \
int _s = __dpmi_allocate_ldt_descriptors(1); \
if (_s > 0) { \
__dpmi_set_segment_base_address(_s, (physAddr)); \
__dpmi_set_segment_limit(_s, (limit)); \
__dpmi_set_descriptor_access_rights(_s, 0x00F2); \
ctx->field = (uint16_t)_s; \
} \
} while (0)
MAKE_MEM_SEL(biosDataSel, 0x00400, 0x00FF); // 0040:0000 (256 bytes)
MAKE_MEM_SEL(vramSel, 0xA0000, 0xFFFF); // A000:0000 (64K)
MAKE_MEM_SEL(monoTextSel, 0xB0000, 0x7FFF); // B000:0000 (32K)
MAKE_MEM_SEL(colorTextSel, 0xB8000, 0x7FFF); // B800:0000 (32K)
MAKE_MEM_SEL(videoBiosSel, 0xC0000, 0x7FFF); // C000:0000 (32K)
MAKE_MEM_SEL(upperMemD000Sel, 0xD0000, 0xFFFF); // D000:0000 (64K)
MAKE_MEM_SEL(upperMemE000Sel, 0xE0000, 0xFFFF); // E000:0000 (64K)
MAKE_MEM_SEL(sysBiosSel, 0xF0000, 0xFFFF); // F000:0000 (64K)
#undef MAKE_MEM_SEL
// ================================================================
// Register KERNEL stubs
// ================================================================
// Error handling
registerStub(ctx, "KERNEL", KERNEL_ORD_FATALEXIT, stubFatalExit, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_FATALAPPEXIT, stubFatalExit, 3);
// Global memory
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALALLOC, stubGlobalAlloc, 3);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALREALLOC, stubGlobalRealloc, 4);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALFREE, stubGlobalFree, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALLOCK, stubGlobalLock, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALUNLOCK, stubGlobalUnlock, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALSIZE, stubGlobalSize, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALFLAGS, stubDummy, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALDOSALLOC, stubGlobalDOSAlloc, 2);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALDOSFREE, stubGlobalDOSFree, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALDOSALLOC2, stubGlobalDOSAlloc, 2);
registerStub(ctx, "KERNEL", KERNEL_ORD_GLOBALDOSFREE2, stubGlobalDOSFree, 1);
// Local memory
registerStub(ctx, "KERNEL", KERNEL_ORD_LOCALINIT, stubLocalInit, 3);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOCALALLOC, stubLocalAlloc, 2);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOCALREALLOC, stubLocalReAlloc, 3);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOCALFREE, stubLocalFree, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOCALLOCK, stubLocalLock, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOCALUNLOCK, stubLocalUnlock, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOCALSIZE, stubLocalSize, 1);
// Memory info
registerStub(ctx, "KERNEL", KERNEL_ORD_GETFREESPACE, stubGetFreeSpace, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOCKSEGMENT, stubDummy, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_UNLOCKSEGMENT, stubDummy, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_SETSWAPAREA, stubDummy, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETCURRENTPDB, stubGetCurrentPDB, 0);
// Module management
registerStub(ctx, "KERNEL", KERNEL_ORD_GETMODULEHANDLE, stubGetModuleHandle, 2);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETMODULEUSAGE, stubGetModuleUsage, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETPROFILEINT, stubGetProfileInt, 5);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETPROFILEINT2, stubGetProfileInt, 5);
registerStub(ctx, "KERNEL", KERNEL_ORD_WRITEPROFILESTRING, stubWriteProfileString, 6);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETMODULEFILENAME, stubGetModuleFileName, 4);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETPROCADDRESS, stubGetProcAddress, 3);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOADLIBRARY, stubLoadLibrary, 2);
registerStub(ctx, "KERNEL", KERNEL_ORD_FREELIBRARY, stubDummy, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_INITTASK, stubDummy, 0);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETEXEPTR, stubGetExePtr, 1);
// Resource management (return 0 / not found)
registerStub(ctx, "KERNEL", KERNEL_ORD_FINDRESOURCE, stubDummy, 3);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOADRESOURCE, stubDummy, 2);
registerStub(ctx, "KERNEL", KERNEL_ORD_FREERESOURCE, stubDummy, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_LOCKRESOURCE, stubDummy, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_SIZEOFRESOURCE, stubDummy, 3);
// System info
registerStub(ctx, "KERNEL", KERNEL_ORD_GETWINFLAGS, stubGetWinFlags, 0);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETVERSION, stubGetVersion, 0);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETPRIVATEPROFILEINT, stubGetPrivateProfileInt, 7);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETPRIVATEPROFILESTRING, stubGetPrivateProfileString, 11);
registerStub(ctx, "KERNEL", KERNEL_ORD_WRITEPRIVATEPROFILESTRING, stubDummy, 8);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETDOSENVIRONMENT, stubGetDOSEnvironment, 0);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETSYSTEMDIRECTORY, stubGetSystemDirectory, 3);
// Selector management
registerStub(ctx, "KERNEL", KERNEL_ORD_ALLOCSELECTOR, stubAllocSelector, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_FREESELECTOR, stubFreeSelector, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_ALLOCCSTODSALIAS, stubAllocCStoDSAlias, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_ALLOCDSTOCSALIAS, stubAllocDSToCSAlias, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_PRESTOCHANGOSELECTOR, stubPrestoChangoSelector, 2);
registerStub(ctx, "KERNEL", KERNEL_ORD_SETSELECTORBASE, stubSetSelectorBase, 3);
registerStub(ctx, "KERNEL", KERNEL_ORD_SETSELECTORLIMIT, stubSetSelectorLimit, 3);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETSELECTORLIMIT, stubGetSelectorLimit, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_GETSELECTORBASE2, stubGetSelectorBase, 1);
registerStub(ctx, "KERNEL", KERNEL_ORD_SELECTORACCESSRIGHTS, stubSelectorAccessRights, 3);
// Debug
registerStub(ctx, "KERNEL", KERNEL_ORD_OUTPUTDEBUGSTRING, stubOutputDebugString, 2);
// Variable imports (__WINFLAGS, __0040H, __A000H, __AHSHIFT, __AHINCR, etc.)
// are handled as special cases in stubResolveImport, not as callbacks.
// ================================================================
// Register GDI stubs
// ================================================================
// GetDeviceCaps(hdc:WORD, index:WORD) -> int
registerStub(ctx, "GDI", GDI_ORD_GETDEVICECAPS, stubGetDeviceCaps, 2);
// CreateDC, DeleteDC, SelectObject, DeleteObject - simple stubs
registerStub(ctx, "GDI", GDI_ORD_CREATEDC, stubDummy, 8);
registerStub(ctx, "GDI", GDI_ORD_DELETEDC, stubDummy, 1);
registerStub(ctx, "GDI", GDI_ORD_SELECTOBJECT, stubDummy, 2);
registerStub(ctx, "GDI", GDI_ORD_DELETEOBJECT, stubDummy, 1);
registerStub(ctx, "GDI", GDI_ORD_SETBKCOLOR, stubDummy, 3);
registerStub(ctx, "GDI", GDI_ORD_SETTEXTCOLOR, stubDummy, 3);
// ================================================================
// Register DIBENG stubs (return 0 so driver uses its own code).
// Parameter counts MUST match the real DDI signatures so the
// generated retf N correctly pops the caller's parameters.
// ================================================================
// DIBBitBlt: lpDst(2) DstX DstY lpSrc(2) SrcX SrcY xExt yExt Rop3(2) lpBrush(2) lpDM(2) = 16
registerStub(ctx, "DIBENG", DIBENG_ORD_DIBBITBLT, stubDummy, 16);
// DIBOutput: lpDst(2) style count lpPts(2) lpPen(2) lpBrush(2) lpDM(2) lpClip(2) = 14
registerStub(ctx, "DIBENG", DIBENG_ORD_DIBOUTPUT, stubDummy, 14);
// DIBPixel: lpDev(2) x y color(2) lpDM(2) = 8
registerStub(ctx, "DIBENG", DIBENG_ORD_DIBPIXEL, stubDummy, 8);
// DIBStrBlt: lpDst(2) DstX DstY DstXE DstYE lpSrc(2) SrcX SrcY SrcXE SrcYE Rop3(2) lpBrush(2) lpDM(2) lpClip(2) = 20
registerStub(ctx, "DIBENG", DIBENG_ORD_DIBSTRBLT, stubDummy, 20);
// DIBColorInfo: lpDev(2) color(2) lpPColor(2) = 6
registerStub(ctx, "DIBENG", DIBENG_ORD_DIBCOLORINFO, stubDummy, 6);
// DIBRealize: lpDev(2) style lpIn(2) lpOut(2) lpTXF(2) = 9
registerStub(ctx, "DIBENG", DIBENG_ORD_DIBREALIZE, stubDummy, 9);
// DIBCreateBitmap (BitmapBits): lpDev(2) flags(2) count(2) lpBits(2) = 8
registerStub(ctx, "DIBENG", DIBENG_ORD_DIBCREATEBITMAP, stubDummy, 8);
// DIBScanLR: lpDev(2) x y color(2) style = 7
registerStub(ctx, "DIBENG", DIBENG_ORD_DIBSCANLR, stubDummy, 7);
// DIBExtOut: lpDev(2) x y lpClip(2) lpStr(2) count lpFont(2) lpDM(2) lpTXF(2) lpWidths(2) lpOpaque(2) opts = 20
registerStub(ctx, "DIBENG", DIBENG_ORD_DIBEXTOUT, stubDummy, 20);
// ================================================================
// Register USER stubs
// ================================================================
// GetSystemMetrics(nIndex:WORD) -> int
registerStub(ctx, "USER", USER_ORD_GETSYSTEMMETRICS, stubGetSystemMetrics, 1);
// MessageBox(hwnd:WORD, lpText:DWORD, lpCaption:DWORD, type:WORD) -> int
registerStub(ctx, "USER", USER_ORD_MESSAGEBOX, stubMessageBox, 6);
// ================================================================
// Register KEYBOARD stubs
// ================================================================
// ScreenSwitchEnable(wEnable:WORD) -> void
registerStub(ctx, "KEYBOARD", KEYBOARD_ORD_SCREENSWITCHENABLE, stubDummy, 1);
// ================================================================
// Pre-allocate DOS memory pool for GlobalDOSAlloc.
// Sub-allocate from it in stubGlobalDOSAlloc to avoid
// calling __dpmi_allocate_dos_memory at runtime.
// ================================================================
{
uint16_t poolParas = STUB_DOS_POOL_SIZE / 16;
int poolSel;
int poolSeg = __dpmi_allocate_dos_memory(poolParas, &poolSel);
if (poolSeg < 0) {
logErr("stub: failed to allocate DOS memory pool\n");
return false;
}
ctx->dosPoolSeg = (uint16_t)poolSeg;
ctx->dosPoolSel = (uint16_t)poolSel;
ctx->dosPoolParas = poolParas;
ctx->dosPoolNextPara = 0;
}
ctx->initialized = true;
return true;
}
void stubShutdown(StubContextT *ctx)
{
// Free allocated memory blocks
for (int i = 0; i < STUB_MAX_ALLOCS; i++) {
if (ctx->allocs[i].inUse) {
if (ctx->allocs[i].selector) {
__dpmi_free_ldt_descriptor(ctx->allocs[i].selector);
}
if (ctx->allocs[i].linearAddr) {
free((void *)ctx->allocs[i].linearAddr);
}
ctx->allocs[i].inUse = false;
}
}
// Free DOS memory pool sub-allocation selectors
for (int i = 0; i < STUB_MAX_DOS_ALLOCS; i++) {
if (ctx->dosAllocs[i].inUse) {
if (ctx->dosAllocs[i].selector) {
__dpmi_free_ldt_descriptor(ctx->dosAllocs[i].selector);
}
ctx->dosAllocs[i].inUse = false;
}
}
// Free the DOS memory pool itself
if (ctx->dosPoolSel) {
__dpmi_free_dos_memory(ctx->dosPoolSel);
ctx->dosPoolSel = 0;
ctx->dosPoolSeg = 0;
}
// Free extra selectors
for (int i = 0; i < STUB_MAX_SELECTORS; i++) {
if (ctx->selectors[i].inUse) {
__dpmi_free_ldt_descriptor(ctx->selectors[i].selector);
ctx->selectors[i].inUse = false;
}
}
// Free well-known memory selectors
uint16_t *memSels[] = {
&ctx->biosDataSel, &ctx->vramSel, &ctx->monoTextSel,
&ctx->colorTextSel, &ctx->videoBiosSel, &ctx->upperMemD000Sel,
&ctx->upperMemE000Sel, &ctx->sysBiosSel, NULL
};
for (int i = 0; memSels[i]; i++) {
if (*memSels[i]) {
__dpmi_free_ldt_descriptor(*memSels[i]);
*memSels[i] = 0;
}
}
ctx->initialized = false;
}
void stubSetModule(StubContextT *ctx, NeModuleT *mod)
{
ctx->neModule = mod;
}
FarPtr16T stubResolveImport(StubContextT *ctx, const char *moduleName,
uint16_t ordinal, const char *funcName)
{
// ================================================================
// KERNEL "variable" imports
//
// These are not functions - they're resolved as segment selector
// values that get patched into the driver's code/data via
// relocation fixups. The selector goes in the segment field of
// the returned far pointer; the offset is 0.
// ================================================================
if (strcasecmp(moduleName, "KERNEL") == 0) {
switch (ordinal) {
case KERNEL_ORD___0040H:
// Value import: selector goes in offset field for OFFSET relocations.
return makeFarPtr16(0, ctx->biosDataSel);
case KERNEL_ORD___A000H:
return makeFarPtr16(0, ctx->vramSel);
case KERNEL_ORD___B000H:
return makeFarPtr16(0, ctx->monoTextSel);
case KERNEL_ORD___B800H:
return makeFarPtr16(0, ctx->colorTextSel);
case KERNEL_ORD___C000H:
return makeFarPtr16(0, ctx->videoBiosSel);
case KERNEL_ORD___D000H:
return makeFarPtr16(0, ctx->upperMemD000Sel);
case KERNEL_ORD___E000H:
return makeFarPtr16(0, ctx->upperMemE000Sel);
case KERNEL_ORD___F000H:
case KERNEL_ORD___ROMBIOS:
return makeFarPtr16(0, ctx->sysBiosSel);
case KERNEL_ORD___WINFLAGS: {
// Value import: the NE relocation is OFFSET type, so the
// value must go in the offset field to be patched correctly.
uint16_t flags = WF_PMODE | WF_CPU386 | WF_ENHANCED;
return makeFarPtr16(0, flags);
}
case KERNEL_ORD___AHSHIFT:
// Selector arithmetic shift count. In protected mode this
// is always 3 (selectors are 8 bytes apart in the LDT).
// Value import: goes in offset field for OFFSET relocations.
return makeFarPtr16(0, 3);
case KERNEL_ORD___AHINCR:
// Selector increment value. In protected mode, consecutive
// LDT selectors differ by 8.
// Value import: goes in offset field for OFFSET relocations.
return makeFarPtr16(0, 8);
case KERNEL_ORD___0000H:
return makeFarPtr16(0, 0);
}
}
// Look up in the stub table
for (uint16_t i = 0; i < ctx->stubCount; i++) {
if (strcasecmp(ctx->stubTable[i].module, moduleName) == 0 &&
ctx->stubTable[i].ordinal == ordinal) {
return ctx->stubTable[i].addr;
}
}
// For DISPLAY (self-reference) imports, return NULL and let the NE loader
// handle it via internal reference
if (strcasecmp(moduleName, "DISPLAY") == 0) {
return FARPTR16_NULL;
}
// Unknown import - log and return NULL
if (funcName && funcName[0]) {
logErr("winstub: unresolved import %s.%s (ord %u)\n",
moduleName, funcName, ordinal);
} else {
logErr("winstub: unresolved import %s.%u\n", moduleName, ordinal);
}
return FARPTR16_NULL;
}
// ============================================================================
// Internal helper
// ============================================================================
static StubContextT *gStubCtx = NULL; // Global reference for callback functions
static bool registerStub(StubContextT *ctx, const char *module, uint16_t ordinal,
ThunkCallbackT func, uint16_t paramWords)
{
if (ctx->stubCount >= 256) {
return false;
}
FarPtr16T addr;
if (!thunkRegisterCallback(ctx->thunkCtx, func, paramWords, &addr)) {
logErr("winstub: failed to register callback for %s.%u\n", module, ordinal);
return false;
}
uint16_t slot = ctx->stubCount;
strncpy(ctx->stubTable[slot].module, module, 15);
ctx->stubTable[slot].module[15] = '\0';
ctx->stubTable[slot].ordinal = ordinal;
ctx->stubTable[slot].addr = addr;
ctx->stubCount++;
if (gStubDebug) {
logErr("winstub: slot %u = %s.%u paramWords=%u -> %04X:%04X\n",
slot, module, ordinal, paramWords, addr.segment, addr.offset);
}
gStubCtx = ctx; // Keep global reference updated
return true;
}
// ============================================================================
// KERNEL stub implementations
// ============================================================================
// Find a free allocation slot
static int findFreeAllocSlot(void)
{
for (int i = 0; i < STUB_MAX_ALLOCS; i++) {
if (!gStubCtx->allocs[i].inUse) {
return i;
}
}
return -1;
}
// Find allocation by handle
static int findAllocByHandle(uint16_t handle)
{
for (int i = 0; i < STUB_MAX_ALLOCS; i++) {
if (gStubCtx->allocs[i].inUse && gStubCtx->allocs[i].handle == handle) {
return i;
}
}
return -1;
}
// FatalExit(code) -> does not return
static uint32_t stubFatalExit(uint16_t *p, uint16_t n)
{
(void)n;
logErr("DRIVER: FatalExit(%d)\n", (int16_t)p[0]);
return 0;
}
// GlobalAlloc(flags, size_hi, size_lo) -> HGLOBAL
// Pascal params: [0]=flags, [1]=size_hi, [2]=size_lo
// On 16-bit stack (Pascal, rightmost on top): size_lo, size_hi, flags
// But our params array is in push order: params[0]=flags (pushed first, deepest)
static uint32_t stubGlobalAlloc(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t flags = p[0];
uint32_t size = ((uint32_t)p[1] << 16) | p[2];
(void)flags;
int slot = findFreeAllocSlot();
if (slot < 0 || size == 0 || size > 0x100000) {
return 0; // Failure
}
uint8_t *mem = (uint8_t *)calloc(1, size);
if (!mem) {
return 0;
}
// Create a 16-bit data selector for this block
int sel = __dpmi_allocate_ldt_descriptors(1);
if (sel < 0) {
free(mem);
return 0;
}
uint32_t linAddr = (uint32_t)mem;
__dpmi_set_segment_base_address(sel, linAddr + __djgpp_base_address);
__dpmi_set_segment_limit(sel, size - 1);
__dpmi_set_descriptor_access_rights(sel, 0x00F2); // 16-bit data RW
// In Windows 3.x, GlobalAlloc returns a handle that IS the selector.
// Drivers use the handle directly as a segment selector, so we must
// return the actual LDT selector value.
uint16_t handle = (uint16_t)sel;
gStubCtx->allocs[slot].handle = handle;
gStubCtx->allocs[slot].linearAddr = linAddr;
gStubCtx->allocs[slot].selector = (uint16_t)sel;
gStubCtx->allocs[slot].size = size;
gStubCtx->allocs[slot].lockCount = 0;
gStubCtx->allocs[slot].inUse = true;
return handle;
}
// GlobalFree(hMem) -> HGLOBAL (0 = success)
static uint32_t stubGlobalFree(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t handle = p[0];
int slot = findAllocByHandle(handle);
if (slot < 0) {
return handle; // Failure - return handle
}
if (gStubCtx->allocs[slot].selector) {
uint16_t sel = gStubCtx->allocs[slot].selector;
__dpmi_free_ldt_descriptor(sel);
thunkSanitizeCbFrame(sel);
}
if (gStubCtx->allocs[slot].linearAddr) {
free((void *)gStubCtx->allocs[slot].linearAddr);
}
gStubCtx->allocs[slot].inUse = false;
return 0; // Success
}
// GlobalLock(hMem) -> far pointer (DX:AX = seg:off)
static uint32_t stubGlobalLock(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t handle = p[0];
int slot = findAllocByHandle(handle);
if (slot < 0) {
return 0; // NULL pointer
}
gStubCtx->allocs[slot].lockCount++;
// Return selector:0000 as the far pointer
uint16_t sel = gStubCtx->allocs[slot].selector;
return ((uint32_t)sel << 16) | 0x0000; // DX=sel, AX=0
}
// GlobalUnlock(hMem) -> BOOL
static uint32_t stubGlobalUnlock(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t handle = p[0];
int slot = findAllocByHandle(handle);
if (slot >= 0 && gStubCtx->allocs[slot].lockCount > 0) {
gStubCtx->allocs[slot].lockCount--;
}
return 0; // Success (return value is remaining lock count == 0)
}
// GlobalRealloc(hMem, size_hi, size_lo, flags) -> HGLOBAL
static uint32_t stubGlobalRealloc(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t handle = p[0];
uint32_t newSize = ((uint32_t)p[1] << 16) | p[2];
int slot = findAllocByHandle(handle);
if (slot < 0 || newSize == 0) {
return 0;
}
uint8_t *newMem = (uint8_t *)realloc((void *)gStubCtx->allocs[slot].linearAddr, newSize);
if (!newMem) {
return 0;
}
gStubCtx->allocs[slot].linearAddr = (uint32_t)newMem;
gStubCtx->allocs[slot].size = newSize;
// Update the selector (true linear = pointer + DS base)
__dpmi_set_segment_base_address(gStubCtx->allocs[slot].selector, (uint32_t)newMem + __djgpp_base_address);
__dpmi_set_segment_limit(gStubCtx->allocs[slot].selector, newSize - 1);
return handle;
}
// GlobalSize(hMem) -> DWORD
static uint32_t stubGlobalSize(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t handle = p[0];
int slot = findAllocByHandle(handle);
if (slot < 0) {
return 0;
}
return gStubCtx->allocs[slot].size;
}
// LocalAlloc - treat as a near-pointer allocation within the data segment
// For simplicity, we redirect to GlobalAlloc
static uint32_t stubLocalAlloc(uint16_t *p, uint16_t n)
{
(void)n;
// LocalAlloc(flags, size) - size is 16-bit
uint16_t localParams[3];
localParams[0] = p[0]; // flags
localParams[1] = 0; // size_hi
localParams[2] = p[1]; // size_lo
return stubGlobalAlloc(localParams, 3);
}
static uint32_t stubLocalFree(uint16_t *p, uint16_t n)
{
return stubGlobalFree(p, n);
}
static uint32_t stubLocalLock(uint16_t *p, uint16_t n)
{
// LocalLock returns a near pointer (just the offset)
uint32_t farPtr = stubGlobalLock(p, n);
return farPtr & 0xFFFF; // Return just the offset (always 0)
}
static uint32_t stubLocalUnlock(uint16_t *p, uint16_t n)
{
return stubGlobalUnlock(p, n);
}
// LocalInit(segment, start, end) -> BOOL
static uint32_t stubLocalInit(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
return 1; // Success
}
// LocalReAlloc(hMem, newSize, flags) -> HANDLE
static uint32_t stubLocalReAlloc(uint16_t *p, uint16_t n)
{
(void)n;
// Return the same handle (pretend success)
return p[0];
}
// LocalSize(hMem) -> UINT
static uint32_t stubLocalSize(uint16_t *p, uint16_t n)
{
(void)n;
// Redirect to GlobalSize
return stubGlobalSize(p, 1);
}
// GlobalDOSAlloc(size_hi:WORD, size_lo:WORD) -> DWORD (AX=selector, DX=segment)
//
// Sub-allocates from the pre-allocated DOS memory pool.
static uint32_t stubGlobalDOSAlloc(uint16_t *p, uint16_t n)
{
(void)n;
uint32_t size = ((uint32_t)p[0] << 16) | p[1];
if (size == 0 || size > STUB_DOS_POOL_SIZE) {
return 0;
}
uint16_t paragraphs = (uint16_t)((size + 15) / 16);
// Find a free slot
int slot = -1;
for (int i = 0; i < STUB_MAX_DOS_ALLOCS; i++) {
if (!gStubCtx->dosAllocs[i].inUse) {
slot = i;
break;
}
}
if (slot < 0) {
return 0;
}
// Bump-allocate from pool
uint16_t paraOff = gStubCtx->dosPoolNextPara;
if (paraOff + paragraphs > gStubCtx->dosPoolParas) {
return 0;
}
gStubCtx->dosPoolNextPara = paraOff + paragraphs;
// Real-mode segment for this sub-block
uint16_t blockSeg = gStubCtx->dosPoolSeg + paraOff;
// Create a PM selector for the sub-block
int sel = __dpmi_allocate_ldt_descriptors(1);
if (sel < 0) {
return 0;
}
uint32_t blockBase = (uint32_t)blockSeg * 16;
__dpmi_set_segment_base_address(sel, blockBase);
__dpmi_set_segment_limit(sel, (uint32_t)paragraphs * 16 - 1);
__dpmi_set_descriptor_access_rights(sel, 0x00F2); // 16-bit data, writable, DPL=3
// Track the sub-allocation
gStubCtx->dosAllocs[slot].paraOff = paraOff;
gStubCtx->dosAllocs[slot].paragraphs = paragraphs;
gStubCtx->dosAllocs[slot].selector = (uint16_t)sel;
gStubCtx->dosAllocs[slot].inUse = true;
if (gStubDebug) {
logErr("stub: GlobalDOSAlloc(%lu) -> sel=%04X seg=%04X\n",
(unsigned long)size, (uint16_t)sel, blockSeg);
}
// Return DX:AX = segment:selector (LOWORD=selector, HIWORD=paragraph)
return ((uint32_t)blockSeg << 16) | (uint16_t)sel;
}
// GlobalDOSFree(selector:WORD) -> WORD (0=success)
static uint32_t stubGlobalDOSFree(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t sel = p[0];
// Find the sub-allocation by selector
for (int i = 0; i < STUB_MAX_DOS_ALLOCS; i++) {
if (gStubCtx->dosAllocs[i].inUse && gStubCtx->dosAllocs[i].selector == sel) {
__dpmi_free_ldt_descriptor(sel);
thunkSanitizeCbFrame(sel);
gStubCtx->dosAllocs[i].inUse = false;
return 0; // Success
}
}
return sel; // Failure — not found
}
// GetFreeSpace(flags) -> DWORD (bytes of free memory)
static uint32_t stubGetFreeSpace(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
return 16 * 1024 * 1024; // Report 16MB free
}
// GetCurrentPDB() -> WORD (PSP segment)
static uint32_t stubGetCurrentPDB(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
// Return a fake PSP segment address. The driver rarely uses this
// for anything meaningful - it's mainly for KERNEL internal bookkeeping.
return 0x0100;
}
// GetModuleUsage(hModule) -> int (reference count)
static uint32_t stubGetModuleUsage(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
return 1; // Always 1 reference
}
// GetProfileInt(lpApp:DWORD, lpKey:DWORD, nDefault:WORD) -> UINT
static uint32_t stubGetProfileInt(uint16_t *p, uint16_t n)
{
(void)n;
// Return the default value (param index 4 = nDefault)
return p[4];
}
// GetDOSEnvironment() -> far pointer to environment block
static uint32_t stubGetDOSEnvironment(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
// Return NULL - driver shouldn't need environment
return 0;
}
// GetSystemDirectory(lpBuffer:DWORD, nSize:WORD) -> UINT (chars copied)
static uint32_t stubGetSystemDirectory(uint16_t *p, uint16_t n)
{
(void)n;
// Write "C:\WINDOWS\SYSTEM" to the buffer
uint16_t seg = p[0];
uint16_t off = p[1];
if (seg == 0 && off == 0) {
return 0;
}
const char *sysDir = "C:\\WINDOWS\\SYSTEM";
uint16_t len = (uint16_t)strlen(sysDir);
movedata(_my_ds(), (unsigned)sysDir, seg, off, len + 1);
return len;
}
// SelectorAccessRights(sel:WORD, op:WORD, rights:WORD) -> WORD
// op=0: get, op=1: set
static uint32_t stubSelectorAccessRights(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t sel = p[0];
uint16_t op = p[1];
if (op == 0) {
// Get access rights
uint8_t desc[8];
if (__dpmi_get_descriptor(sel, desc) != 0) {
return 0;
}
return ((uint16_t)desc[6] << 8) | desc[5];
} else {
// Set access rights
uint16_t rights = p[2];
__dpmi_set_descriptor_access_rights(sel, rights);
return 0;
}
}
// GetModuleHandle(lpModuleName) -> HMODULE
// Returns a fake module handle
static uint32_t stubGetModuleHandle(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
// Return a non-zero handle. The driver doesn't really need a valid one.
return 0x0100;
}
// GetProcAddress(hModule:WORD, lpProcName:DWORD) -> FARPROC
// If lpProcName has segment=0, the offset is an ordinal number.
static uint32_t stubGetProcAddress(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t nameSeg = p[1];
uint16_t nameOff = p[2];
// If segment is 0, offset is an ordinal (MAKEINTRESOURCE)
if (nameSeg == 0 && gStubCtx && gStubCtx->neModule) {
uint16_t ordinal = nameOff;
uint16_t seg = 0;
uint16_t off = 0;
uint16_t sel = 0;
if (neLookupExport(gStubCtx->neModule, ordinal, &seg, &off, &sel)) {
logErr("winstub: GetProcAddress(ord %u) -> %04X:%04X\n", ordinal, sel, off);
return ((uint32_t)sel << 16) | off;
}
}
logErr("winstub: GetProcAddress(%04X:%04X) -> NULL\n", nameSeg, nameOff);
return 0;
}
// GetModuleFileName(hModule, lpFilename, nSize) -> int
static uint32_t stubGetModuleFileName(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
return 0; // Empty string, 0 chars copied
}
// GetWinFlags() -> DWORD
static uint32_t stubGetWinFlags(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
uint32_t flags = WF_PMODE | WF_CPU386 | WF_ENHANCED;
return flags;
}
// GetVersion() -> DWORD (low word = Windows version, high word = DOS version)
static uint32_t stubGetVersion(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
// Windows 3.10, DOS 6.22
// Low byte of low word = major version = 3
// High byte of low word = minor version = 10
// Low byte of high word = DOS major = 6
// High byte of high word = DOS minor = 22
return 0x160A0A03; // DOS 6.22, Windows 3.10
}
// GetPrivateProfileInt(lpApp, lpKey, nDefault, lpFile) -> UINT
// Pascal params: lpApp(2w), lpKey(2w), nDefault(1w), lpFile(2w) = 7 words
static uint32_t stubGetPrivateProfileInt(uint16_t *p, uint16_t n)
{
(void)n;
// Pascal push order: lpApp(2w), lpKey(2w), nDefault(1w), lpFile(2w)
uint16_t appSeg = p[0];
uint16_t appOff = p[1];
uint16_t keySeg = p[2];
uint16_t keyOff = p[3];
uint16_t def = p[4];
uint16_t fileSeg = p[5];
uint16_t fileOff = p[6];
char app[64] = {0};
char key[64] = {0};
char file[64] = {0};
if (appSeg || appOff) {
movedata(appSeg, appOff, _my_ds(), (unsigned)app, 63);
}
if (keySeg || keyOff) {
movedata(keySeg, keyOff, _my_ds(), (unsigned)key, 63);
}
if (fileSeg || fileOff) {
movedata(fileSeg, fileOff, _my_ds(), (unsigned)file, 63);
}
logErr("stub: GetPrivateProfileInt([%s] %s, def=%u, %s)\n",
app, key, def, file);
// Provide values for known sections
if (strcasecmp(app, "VBESVGA.DRV") == 0) {
if (strcasecmp(key, "Width") == 0) {
return 800;
} else if (strcasecmp(key, "Height") == 0) {
return 600;
} else if (strcasecmp(key, "Depth") == 0) {
return 8;
}
}
return def;
}
// GetPrivateProfileString - return default string
static uint32_t stubGetPrivateProfileString(uint16_t *p, uint16_t n)
{
(void)n;
// Pascal push order: lpApp(2w), lpKey(2w), lpDefault(2w),
// lpBuffer(2w), nSize(1w), lpFile(2w)
uint16_t appSeg = p[0];
uint16_t appOff = p[1];
uint16_t keySeg = p[2];
uint16_t keyOff = p[3];
uint16_t defSeg = p[4];
uint16_t defOff = p[5];
uint16_t bufSeg = p[6];
uint16_t bufOff = p[7];
uint16_t bufSize = p[8];
uint16_t fileSeg = p[9];
uint16_t fileOff = p[10];
char app[64] = {0};
char key[64] = {0};
char def[64] = {0};
char file[64] = {0};
if (appSeg || appOff) {
movedata(appSeg, appOff, _my_ds(), (unsigned)app, 63);
}
if (keySeg || keyOff) {
movedata(keySeg, keyOff, _my_ds(), (unsigned)key, 63);
}
if (defSeg || defOff) {
movedata(defSeg, defOff, _my_ds(), (unsigned)def, 63);
}
if (fileSeg || fileOff) {
movedata(fileSeg, fileOff, _my_ds(), (unsigned)file, 63);
}
logErr("stub: GetPrivateProfileString([%s] %s, def=\"%s\", buf=%u, %s)\n",
app, key, def, bufSize, file);
// Provide SYSTEM.INI [display] values matching the OEMSETUP.INF
// that ships with the S3 Trio64V driver v1.70.04 (D808L mode:
// 800x600 256-color, large font).
const char *result = def;
if (strcasecmp(app, "DISPLAY") == 0) {
if (strcasecmp(key, "SCREEN-SIZE") == 0) {
result = "800";
} else if (strcasecmp(key, "COLOR-FORMAT") == 0) {
result = "8";
} else if (strcasecmp(key, "DAC-TYPE") == 0) {
result = "nbt";
} else if (strcasecmp(key, "POLYGON-SUPPORT") == 0) {
result = "on";
} else if (strcasecmp(key, "ELLIPSE-SUPPORT") == 0) {
result = "on";
} else if (strcasecmp(key, "SCACHE") == 0) {
result = "on";
} else if (strcasecmp(key, "TEXTRMW") == 0) {
result = "0";
} else if (strcasecmp(key, "FASTMMIO") == 0) {
result = "on";
} else if (strcasecmp(key, "DPI") == 0) {
result = "120";
}
} else if (strcasecmp(app, "VBESVGA.DRV") == 0) {
if (strcasecmp(key, "Width") == 0) {
result = "800";
} else if (strcasecmp(key, "Height") == 0) {
result = "600";
} else if (strcasecmp(key, "Depth") == 0) {
result = "8";
}
} else if (strcasecmp(app, "Debug") == 0) {
if (strcasecmp(key, "OutputTo") == 0) {
result = "NUL";
}
}
// Copy result to buffer
if ((bufSeg || bufOff) && bufSize > 0) {
uint16_t len = (uint16_t)strlen(result);
if (len >= bufSize) {
len = bufSize - 1;
}
movedata(_my_ds(), (unsigned)result, bufSeg, bufOff, len + 1);
return len;
}
return 0;
}
// AllocSelector(srcSelector) -> new selector
static uint32_t stubAllocSelector(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t srcSel = p[0];
int newSel = __dpmi_allocate_ldt_descriptors(1);
if (newSel < 0) {
return 0;
}
// If srcSelector is non-zero, copy its attributes
if (srcSel != 0) {
// Get source descriptor and copy it
uint8_t desc[8];
if (__dpmi_get_descriptor(srcSel, desc) == 0) {
__dpmi_set_descriptor(newSel, desc);
}
}
// Track this selector
for (int i = 0; i < STUB_MAX_SELECTORS; i++) {
if (!gStubCtx->selectors[i].inUse) {
gStubCtx->selectors[i].selector = (uint16_t)newSel;
gStubCtx->selectors[i].inUse = true;
break;
}
}
return (uint16_t)newSel;
}
// FreeSelector(selector) -> selector (0 = success)
// NOTE: We do NOT actually free the LDT descriptor here. Windows 3.1 drivers
// use an alloc-then-free pattern to "discover" selector numbers, then continue
// using the freed selector. The descriptor stays valid with its stale contents.
// Actual cleanup happens in stubShutdown when all selectors are freed.
static uint32_t stubFreeSelector(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t sel = p[0];
// Mark as freed in our tracking but don't release the LDT slot
for (int i = 0; i < STUB_MAX_SELECTORS; i++) {
if (gStubCtx->selectors[i].inUse && gStubCtx->selectors[i].selector == sel) {
gStubCtx->selectors[i].inUse = false;
break;
}
}
return 0;
}
// AllocCStoDSAlias(codeSelector) -> data selector
// Creates a data segment alias for a code segment
static uint32_t stubAllocCStoDSAlias(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t codeSel = p[0];
int newSel = __dpmi_allocate_ldt_descriptors(1);
if (newSel < 0) {
return 0;
}
// Copy the code segment descriptor
uint8_t desc[8];
if (__dpmi_get_descriptor(codeSel, desc) != 0) {
__dpmi_free_ldt_descriptor(newSel);
return 0;
}
// Change to data segment (clear the code bit in the type field)
// Byte 5 (access rights): change type from code to data
// Code readable: 1010 -> Data writable: 0010
desc[5] = (desc[5] & 0xF0) | 0x02; // Data, writable
__dpmi_set_descriptor(newSel, desc);
for (int i = 0; i < STUB_MAX_SELECTORS; i++) {
if (!gStubCtx->selectors[i].inUse) {
gStubCtx->selectors[i].selector = (uint16_t)newSel;
gStubCtx->selectors[i].inUse = true;
break;
}
}
return (uint16_t)newSel;
}
// AllocDSToCSAlias(dataSel) -> code selector
// Creates a code segment alias for a data segment
static uint32_t stubAllocDSToCSAlias(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t dataSel = p[0];
int newSel = __dpmi_allocate_ldt_descriptors(1);
if (newSel < 0) {
return 0;
}
// Copy the data segment descriptor
uint8_t desc[8];
if (__dpmi_get_descriptor(dataSel, desc) != 0) {
__dpmi_free_ldt_descriptor(newSel);
return 0;
}
// Change to code segment (set the code bit in the type field)
// Data writable: 0010 -> Code readable: 1010
desc[5] = (desc[5] & 0xF0) | 0x0A; // Code, readable
__dpmi_set_descriptor(newSel, desc);
for (int i = 0; i < STUB_MAX_SELECTORS; i++) {
if (!gStubCtx->selectors[i].inUse) {
gStubCtx->selectors[i].selector = (uint16_t)newSel;
gStubCtx->selectors[i].inUse = true;
break;
}
}
return (uint16_t)newSel;
}
// LoadLibrary(lpLibFileName) -> hModule
// Returns error code < 32 since we don't load real DLLs
static uint32_t stubLoadLibrary(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t seg = p[0];
uint16_t off = p[1];
(void)seg;
(void)off;
logErr("stub: LoadLibrary() -> 2 (file not found)\n");
// Return 2 = file not found. Values < 32 indicate error.
// The driver checks hModule >= 32 for success.
return 2;
}
// PrestoChangoSelector(srcSel, dstSel) -> new selector
// Converts code selector to data and vice versa
static uint32_t stubPrestoChangoSelector(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t srcSel = p[0];
uint16_t dstSel = p[1];
uint8_t desc[8];
if (__dpmi_get_descriptor(srcSel, desc) != 0) {
return 0;
}
// Toggle code/data bit
desc[5] ^= 0x08; // Toggle the code/data bit
__dpmi_set_descriptor(dstSel, desc);
return dstSel;
}
// SetSelectorBase(selector, base_hi, base_lo) -> selector
static uint32_t stubSetSelectorBase(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t sel = p[0];
uint32_t base = ((uint32_t)p[1] << 16) | p[2];
__dpmi_set_segment_base_address(sel, base);
return sel;
}
// SetSelectorLimit(selector, limit_hi, limit_lo) -> 0=success
static uint32_t stubSetSelectorLimit(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t sel = p[0];
uint32_t limit = ((uint32_t)p[1] << 16) | p[2];
if (__dpmi_set_segment_limit(sel, limit) == 0) {
return 0; // Success
}
return 1; // Failure
}
// GetSelectorLimit(selector) -> DWORD
static uint32_t stubGetSelectorLimit(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t sel = p[0];
// DPMI function 0006h: Get Segment Base Address
// We need the limit, not the base. Use __dpmi_get_descriptor.
uint8_t desc[8];
if (__dpmi_get_descriptor(sel, desc) != 0) {
return 0;
}
// Limit is in bytes 0-1 (low 16 bits) and bits 0-3 of byte 6 (high 4 bits)
uint32_t limit = (uint32_t)desc[0] | ((uint32_t)desc[1] << 8) |
((uint32_t)(desc[6] & 0x0F) << 16);
// If granularity bit is set, limit is in 4K pages
if (desc[6] & 0x80) {
limit = (limit << 12) | 0xFFF;
}
return limit;
}
// GetSelectorBase(selector) -> DWORD (DX:AX)
static uint32_t stubGetSelectorBase(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t sel = p[0];
unsigned long base;
if (__dpmi_get_segment_base_address(sel, &base) != 0) {
return 0;
}
return (uint32_t)base;
}
// OutputDebugString(lpString) -> void
// Prints the debug string to stderr for diagnostic purposes.
static uint32_t stubOutputDebugString(uint16_t *p, uint16_t n)
{
(void)n;
// p[0] = segment, p[1] = offset of the string
uint16_t seg = p[0];
uint16_t off = p[1];
if (seg == 0 && off == 0) {
return 0;
}
// Read the string from the 16-bit segment
char buf[256];
movedata(seg, off, _my_ds(), (unsigned)buf, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
logErr("DRIVER DEBUG: %s\n", buf);
return 0;
}
// WriteProfileString(lpApp:DWORD, lpKey:DWORD, lpString:DWORD) -> BOOL
static uint32_t stubWriteProfileString(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
// No-op — pretend we wrote to WIN.INI
return 1; // TRUE
}
// GetExePtr(hInstance:WORD) -> HMODULE
static uint32_t stubGetExePtr(uint16_t *p, uint16_t n)
{
(void)n;
// In real Windows, converts an instance handle to a module handle.
// For our purposes, they're the same value.
return p[0];
}
// ============================================================================
// GDI stub implementations
// ============================================================================
// GetDeviceCaps(hdc, index) -> int
static uint32_t stubGetDeviceCaps(uint16_t *p, uint16_t n)
{
(void)n;
// Return generic values for common device caps indices
uint16_t index = p[1]; // Second param (Pascal: hdc pushed first, index pushed second)
switch (index) {
case 8: return 8; // BITSPIXEL
case 14: return 1; // PLANES
case 12: return 16; // NUMCOLORS
case 38: return 0; // RASTERCAPS - no special caps
case 88: return 8; // SIZEPALETTE
default: return 0;
}
}
// Dummy stub - returns 0/1 depending on context
static uint32_t stubDummy(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
return 0;
}
// ============================================================================
// USER stub implementations
// ============================================================================
// GetSystemMetrics(nIndex) -> int
static uint32_t stubGetSystemMetrics(uint16_t *p, uint16_t n)
{
(void)n;
uint16_t index = p[0];
switch (index) {
case 0: return 640; // SM_CXSCREEN
case 1: return 480; // SM_CYSCREEN
case 16: return 640; // SM_CXFULLSCREEN
case 17: return 480; // SM_CYFULLSCREEN
default: return 0;
}
}
// MessageBox - just ignore, return IDOK
static uint32_t stubMessageBox(uint16_t *p, uint16_t n)
{
(void)p; (void)n;
return 1; // IDOK
}