1400 lines
46 KiB
C
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
|
|
}
|