From 355b07c9ac2879838fe17c081ab3a39d519dbc0b Mon Sep 17 00:00:00 2001 From: Kroese Date: Mon, 17 Aug 2026 12:58:01 +0200 Subject: [PATCH 1/3] Update mousew16.c --- src/mousew16.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/mousew16.c b/src/mousew16.c index f4cd377..a9685bc 100644 --- a/src/mousew16.c +++ b/src/mousew16.c @@ -47,6 +47,8 @@ static uint16_t mousex, mousey; static bool mouseposvalid; /** Existing interrupt2f handler. */ static LPFN prev_int2f_handler; +/** Last result of the temporary exported-entry VMware GETVERSION probe. */ +static volatile int32_t diagnostic_vmware_version; /* This is how events are delivered to Windows. */ @@ -58,6 +60,12 @@ static void send_event(unsigned short Status, short deltaX, short deltaY, short #pragma code_seg ( "CALLBACKS" ) +/** Temporary diagnostic: force one raw VMware GETVERSION backdoor access. */ +static void __far vmware_diagnostic_probe(void) +{ + diagnostic_vmware_version = vmware_get_version(); +} + static bool __far vmware_detect(void) { uint32_t status; @@ -381,6 +389,8 @@ BOOL FAR PASCAL LibMain(HINSTANCE hInstance, WORD wDataSegment, /** Called by Windows to retrieve information about the mouse hardware. */ WORD FAR PASCAL Inquire(LPMOUSEINFO lpMouseInfo) { + vmware_diagnostic_probe(); + lpMouseInfo->msExist = 1; lpMouseInfo->msRelative = mouseflags & MOUSEFLAGS_HAS_VMWARE ? 0 : 1; lpMouseInfo->msNumButtons = MOUSE_NUM_BUTTONS; @@ -392,6 +402,8 @@ WORD FAR PASCAL Inquire(LPMOUSEINFO lpMouseInfo) * @param lpEventProc Callback function to call when a mouse event happens. */ VOID FAR PASCAL Enable(LPFN_MOUSEEVENT lpEventProc) { + vmware_diagnostic_probe(); + /* Store the Windows-given callback. */ cli(); /* Write to far pointer may not be atomic, and we could be interrupted mid-write. */ eventproc = lpEventProc; From 2af3c15782631ad6fce8fe4593965260f5520f3e Mon Sep 17 00:00:00 2001 From: Kroese Date: Mon, 17 Aug 2026 14:32:20 +0200 Subject: [PATCH 2/3] Update mousew16.c --- src/mousew16.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/mousew16.c b/src/mousew16.c index a9685bc..f4cd377 100644 --- a/src/mousew16.c +++ b/src/mousew16.c @@ -47,8 +47,6 @@ static uint16_t mousex, mousey; static bool mouseposvalid; /** Existing interrupt2f handler. */ static LPFN prev_int2f_handler; -/** Last result of the temporary exported-entry VMware GETVERSION probe. */ -static volatile int32_t diagnostic_vmware_version; /* This is how events are delivered to Windows. */ @@ -60,12 +58,6 @@ static void send_event(unsigned short Status, short deltaX, short deltaY, short #pragma code_seg ( "CALLBACKS" ) -/** Temporary diagnostic: force one raw VMware GETVERSION backdoor access. */ -static void __far vmware_diagnostic_probe(void) -{ - diagnostic_vmware_version = vmware_get_version(); -} - static bool __far vmware_detect(void) { uint32_t status; @@ -389,8 +381,6 @@ BOOL FAR PASCAL LibMain(HINSTANCE hInstance, WORD wDataSegment, /** Called by Windows to retrieve information about the mouse hardware. */ WORD FAR PASCAL Inquire(LPMOUSEINFO lpMouseInfo) { - vmware_diagnostic_probe(); - lpMouseInfo->msExist = 1; lpMouseInfo->msRelative = mouseflags & MOUSEFLAGS_HAS_VMWARE ? 0 : 1; lpMouseInfo->msNumButtons = MOUSE_NUM_BUTTONS; @@ -402,8 +392,6 @@ WORD FAR PASCAL Inquire(LPMOUSEINFO lpMouseInfo) * @param lpEventProc Callback function to call when a mouse event happens. */ VOID FAR PASCAL Enable(LPFN_MOUSEEVENT lpEventProc) { - vmware_diagnostic_probe(); - /* Store the Windows-given callback. */ cli(); /* Write to far pointer may not be atomic, and we could be interrupted mid-write. */ eventproc = lpEventProc; From a50f5a3d09bb77a6079473e2e32884cfd2468307 Mon Sep 17 00:00:00 2001 From: Kroese Date: Mon, 17 Aug 2026 14:34:00 +0200 Subject: [PATCH 3/3] fix: Work around QEMU VMPort return-register issues on Win9x --- src/vmware.h | 74 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/src/vmware.h b/src/vmware.h index c7afc38..88ab111 100644 --- a/src/vmware.h +++ b/src/vmware.h @@ -81,6 +81,15 @@ struct vmware_abspointer_data { #define VMWARE_ABSPOINTER_DATA_PACKET_SIZE 4 +/* + * VMware backdoor commands normally return extra values through EBX/ECX/EDX + * as side effects of an IN instruction. On the QEMU/KVM + Win9x path we have + * observed GETVERSION reach QEMU correctly while its returned EBX magic does + * not survive back to this Win16 caller. Remember that condition so vmmouse + * packet reads can use only the architectural IN result in EAX. + */ +static uint8_t vmware_eax_only_returns; + #pragma code_seg ( "CALLBACKS" ) static inline void vmware_call(struct vmware_call_regs __far * regs); @@ -109,9 +118,24 @@ static int32_t vmware_get_version(void) regs.command = VMWARE_CMD_GETVERSION; regs.ebx = ~VMWARE_MAGIC; vmware_call(®s); - if (regs.ebx != VMWARE_MAGIC) { - return -1; + + if (regs.ebx == VMWARE_MAGIC) { + /* Normal VMware-style return path: all output registers survived. */ + vmware_eax_only_returns = 0; + } else { + /* + * QEMU/KVM can still return GETVERSION through EAX even when the + * VMware-specific EBX side effect is lost on the way back to Win16. + * Historical GETVERSION values are 0..6. Keep this fallback narrow; + * vmware_detect() also probes the actual vmmouse command/status + * interface before absolute input is advertised. + */ + if (regs.eax > 6) { + return -1; + } + vmware_eax_only_returns = 1; } + return regs.eax; } @@ -151,15 +175,43 @@ static uint32_t vmware_abspointer_status(void) * VMWARE_ABSPOINTER_STATUS_MASK_DATA bits: amount of data available to read. */ static void vmware_abspointer_data(unsigned size, struct vmware_abspointer_data __far * data) { - union u { - struct vmware_call_regs regs; - struct vmware_abspointer_data data; - } __far *p = (union u __far *) data; - p->regs.magic = VMWARE_MAGIC; - p->regs.port = VMWARE_PORT; - p->regs.command = VMWARE_CMD_ABSPOINTER_DATA; - p->regs.size = size; - vmware_call(&p->regs); + if (!vmware_eax_only_returns) { + /* VMware's native path returns up to four words in EAX/EBX/ECX/EDX. */ + union u { + struct vmware_call_regs regs; + struct vmware_abspointer_data data; + } __far *p = (union u __far *) data; + p->regs.magic = VMWARE_MAGIC; + p->regs.port = VMWARE_PORT; + p->regs.command = VMWARE_CMD_ABSPOINTER_DATA; + p->regs.size = size; + vmware_call(&p->regs); + return; + } + + /* + * QEMU removes exactly the requested number of dwords from the vmmouse + * queue. Request one dword per call so status, X, Y and Z each return + * through EAX; this avoids depending on returned EBX/ECX/EDX entirely. + */ + { + uint32_t __far *out = (uint32_t __far *) data; + unsigned i; + + if (size > VMWARE_ABSPOINTER_DATA_PACKET_SIZE) { + size = VMWARE_ABSPOINTER_DATA_PACKET_SIZE; + } + + for (i = 0; i < size; ++i) { + struct vmware_call_regs regs; + regs.magic = VMWARE_MAGIC; + regs.port = VMWARE_PORT; + regs.command = VMWARE_CMD_ABSPOINTER_DATA; + regs.size = 1; + vmware_call(®s); + out[i] = regs.eax; + } + } } /** Reads (and discards) all available data in the VMware absolute pointing interface. */