// ============================================================================ // 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 #include #include #include #include #include #include #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 }