Add comprehensive README covering architecture, API usage, build instructions, tested drivers, binary patching details, and DGROUP layout. Expand file header comments in all library sources and headers to document module responsibilities, data flow, and key constraints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1423 lines
47 KiB
C
1423 lines
47 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 thunkRegisterCallback(), giving
|
|
// it a 16:16 far pointer that the NE loader patches into the driver's
|
|
// relocation fixups.
|
|
//
|
|
// These stubs implement just enough behavior for a display driver to
|
|
// initialize and perform basic drawing operations:
|
|
//
|
|
// KERNEL (~50 stubs):
|
|
// - Memory: GlobalAlloc/Lock/Unlock/Free/Realloc/Size,
|
|
// GlobalDOSAlloc/Free (bump-allocated from a pre-allocated pool),
|
|
// LocalAlloc/Lock/Free
|
|
// - Selectors: AllocSelector, FreeSelector, AllocCStoDSAlias,
|
|
// AllocDSToCSAlias, PrestoChangoSelector, Set/GetSelectorBase,
|
|
// Set/GetSelectorLimit, SelectorAccessRights
|
|
// - System: GetVersion (3.10), GetWinFlags (Enhanced + 386),
|
|
// GetProfileInt, GetPrivateProfileInt/String (returns SYSTEM.INI
|
|
// values for display driver configuration)
|
|
// - Module: GetModuleHandle, GetProcAddress, LoadLibrary
|
|
// - Variable imports: __WINFLAGS, __AHSHIFT, __AHINCR, __A000H,
|
|
// __B000H, __B800H, __0040H, etc. (resolved as selector values)
|
|
//
|
|
// GDI (~8 stubs): GetDeviceCaps, CreateDC, DeleteDC, SelectObject
|
|
// USER (2 stubs): GetSystemMetrics, MessageBox
|
|
// DIBENG (9 stubs): DIBBitBlt, DIBOutput, DIBPixel, etc. (return 0
|
|
// so the driver falls back to its own implementation)
|
|
// KEYBOARD (1 stub): ScreenSwitchEnable (no-op)
|
|
//
|
|
// Stubs that are not implemented return 0 via stubDummy.
|
|
// ============================================================================
|
|
|
|
#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
|
|
}
|