diff options
-rw-r--r-- | README.md | 33 | ||||
-rw-r--r-- | dosmain.c | 183 | ||||
-rw-r--r-- | dostsr.c | 6 | ||||
-rw-r--r-- | int21dos.h | 100 | ||||
-rw-r--r-- | utils.h | 3 |
5 files changed, 293 insertions, 32 deletions
@@ -10,14 +10,19 @@ some additional features: Multiple DOS boxes can use this driver simultaneously, and clicks in the DOS window will be passed through to the correct running DOS application. -* PS/2 Wheel and 3 button mouse support, using the API from CuteMouse. - * Integration with VirtualBox: in many DOS programs, the mouse can be used without requiring capture, and will seamlessly integrate with the mouse cursor in the host. The mouse cursor will be rendered by the host rather than the guest OS, appearing much more responsive. Programs/games that utilize relative mouse motion information will be "jumpy" when this is enabled, so this integration can be disabled (either from the VirtualBox menu or by using `vbmouse integ off` after loading the driver). + +* Integration with VMware/qemu vmmouse: like the above. + Use `vbmouse integ off` to disable it for software requiring relative mouse motions. + Host mouse cursor is not implemented and will be always rendered by the guest. + +* Wheel and 3 button mouse support, using the API from CuteMouse. + This is currently limited to the VirtualBox/VMware integration, albeit limited PS/2 wheel support is planned. * A companion driver for Windows 3.x that uses this driver (via int33h) instead of accessing the mouse directly, so that Windows 3.x gains some of the features of this driver (like mouse integration in VirtualBox). @@ -25,9 +30,29 @@ some additional features: Note that it does not support serial mice or anything other than PS/2. -# Install +# Usage + +To install the driver, just run `vbmouse`. + +Run `vbmouse <action>` for specific configuration. Here are the supported actions: + +* `install` installs the driver (i.e. the same as if you run `vbmouse`). `vbmouse install low` can be used to force installation in conventional memory; by default, it tries to use a DOS UMB block. + +* `uninstall` uninstalls the driver. Note that if you have added some other TSRs after vbmouse, they may be removed. + +* `wheel on|off` to enable/disable the wheel support. + +* `integ on|off` to enable/disable the VirtualBox/VMware cursor integration. + Useful for programs that expect relative mouse coordinates. + +* `hostcur on|off` to enable/disable the host-rendered mouse cursor. + +* `reset` resets the mouse to default settings and re-initializes the hardware. + This does not include any of the above settings, but rather the traditional int33 mouse settings (like sensitivity) + that may be altered by other programs. It is equivalent to int33/ax=0. + + -Just run vbmouse. # Building @@ -25,6 +25,7 @@ #include "dlog.h" #include "int33.h" +#include "int21dos.h" #include "ps2.h" #include "vbox.h" #include "vmware.h" @@ -241,34 +242,142 @@ static int configure_driver(LPTSRDATA data) return 0; } -static void deallocate_environment(uint16_t psp) +/** Converts bytes to MS-DOS "paragraphs" (16 bytes), rounding up. */ +static inline unsigned get_paragraphs(unsigned bytes) +{ + return (bytes + 15) / 16; +} + +/** Gets the size of the resident part of this program, including the PSP. */ +static inline unsigned get_resident_program_size() +{ + return get_resident_size() + DOS_PSP_SIZE; +} + +/** Deallocates the environment block from the passed PSP segment. */ +static void deallocate_environment(__segment psp) { // TODO : Too lazy to make PSP struct; // 0x2C is offsetof the environment block field on the PSP uint16_t __far *envblockP = (uint16_t __far *) MK_FP(psp, 0x2C); - _dos_freemem(*envblockP); + dos_free(*envblockP); *envblockP = 0; } -static int install_driver(LPTSRDATA data) +/** Copies a program to another location. + * @param new_seg PSP segment for the new location + * @param old_seg PSP segment for the old location + * @param size size of the program to copy including PSP size. */ +static void copy_program(__segment new_seg, __segment old_seg, unsigned size) +{ + // The MCB is always 1 segment before. + uint8_t __far *new_mcb = MK_FP(new_seg - 1, 0); + uint8_t __far *old_mcb = MK_FP(old_seg - 1, 0); + uint16_t __far *new_mcb_owner = (uint16_t __far *) &new_mcb[1]; + char __far *new_mcb_owner_name = &new_mcb[8]; + char __far *old_mcb_owner_name = &old_mcb[8]; + + // Copy entire resident segment including PSP + _fmemcpy(MK_FP(new_seg, 0), MK_FP(old_seg, 0), size); + + // Make the new MCB point to itself as owner + *new_mcb_owner = new_seg; + + // Copy the program name, too. + _fmemcpy(new_mcb_owner_name, old_mcb_owner_name, 8); +} + +/** Allocates a UMB of the given size. + * If no UMBs are available, this may still return a block in conventional memory. */ +static __segment allocate_umb(unsigned size) +{ + bool old_umb_link = dos_query_umb_link_state(); + unsigned int old_strategy = dos_query_allocation_strategy(); + __segment new_segment; + + dos_set_umb_link_state(true); + dos_set_allocation_strategy(DOS_FIT_BEST | DOS_FIT_HIGHONLY); + + new_segment = dos_alloc(get_paragraphs(size)); + + dos_set_umb_link_state(old_umb_link); + dos_set_allocation_strategy(old_strategy); + + return new_segment; +} + +static int reallocate_to_umb(LPTSRDATA __far * data) { - unsigned int resident_size = get_resident_size(); + const unsigned int resident_size = get_resident_program_size(); + LPTSRDATA old_data = *data; + __segment old_psp_segment = FP_SEG(old_data) - (DOS_PSP_SIZE/16); + __segment new_psp_segment; - // Not that this will do anything other than fragment memory, but why not... deallocate_environment(_psp); + // If we are already in UMA, don't bother + if (old_psp_segment >= 0xA000) { + return -1; + } + + new_psp_segment = allocate_umb(resident_size); + + if (new_psp_segment && new_psp_segment >= 0xA000) { + __segment new_segment = new_psp_segment + (DOS_PSP_SIZE/16); + printf("Moving to upper memory\n"); + + // Create a new program instance including PSP at the new_segment + copy_program(new_psp_segment, old_psp_segment, resident_size); + + // Tell DOS to "switch" to the new program + dos_set_psp(new_psp_segment); + + // Now update the data pointer to the new segment + *data = MK_FP(new_segment, FP_OFF(old_data)); + + return 0; + } else { + printf("No upper memory available\n"); + if (new_psp_segment) { + // In case we got another low-memory segment... + dos_free(new_psp_segment); + } + + return -1; + } +} + +static __declspec(aborts) int install_driver(LPTSRDATA data, bool high) +{ + const unsigned int resident_size = DOS_PSP_SIZE + get_resident_size(); + + // No more interruptions from now on and until we TSR. + // Inserting ourselves in the interrupt chain should be atomic. + _disable(); + data->prev_int33_handler = _dos_getvect(0x33); - _dos_setvect(0x33, int33_isr); + _dos_setvect(0x33, data:>int33_isr); #if USE_WIN386 data->prev_int2f_handler = _dos_getvect(0x2f); - _dos_setvect(0x2f, int2f_isr); + _dos_setvect(0x2f, data:>int2f_isr); #endif printf("Driver installed\n"); - _dos_keep(EXIT_SUCCESS, (256 + resident_size + 15) / 16); - return 0; + // If we reallocated ourselves to UMB, + // it's time to free our initial conventional memory allocation + if (high) { + // We are about to free() our own code segment. + // Nothing should try to allocate memory between this and the TSR call + // below, since it could overwrite our code... + dos_free(_psp); + } + + _dos_keep(EXIT_SUCCESS, get_paragraphs(resident_size)); + + // Shouldn't reach this part + return EXIT_FAILURE; } static bool check_if_driver_uninstallable(LPTSRDATA data) @@ -298,10 +407,8 @@ static bool check_if_driver_uninstallable(LPTSRDATA data) static int unconfigure_driver(LPTSRDATA data) { -#if USE_VIRTUALBOX - if (data->vbavail) { - set_integration(data, false); - } +#if USE_INTEGRATION + set_integration(data, false); #endif ps2m_enable(false); @@ -320,7 +427,7 @@ static int uninstall_driver(LPTSRDATA data) // Find and deallocate the PSP (including the entire program), // it is always 256 bytes (16 paragraphs) before the TSR segment - _dos_freemem(FP_SEG(data) - 16); + dos_free(FP_SEG(data) - (DOS_PSP_SIZE/16)); printf("Driver uninstalled\n"); @@ -343,21 +450,29 @@ static void print_help(void) { printf("\n" "Usage: \n" - "\tVBMOUSE <ACTION>\n\n" + " VBMOUSE <ACTION> <ARGS..>\n\n" "Supported actions:\n" - "\tinstall install the driver (default)\n" - "\tuninstall uninstall the driver from memory\n" + " install install the driver (default)\n" + " low install in conventional memory (otherwise UMB)\n" + " uninstall uninstall the driver from memory\n" #if USE_WHEEL - "\twheel <ON|OFF> enable/disable wheel API support\n" + " wheel <ON|OFF> enable/disable wheel API support\n" #endif #if USE_INTEGRATION - "\tinteg <ON|OFF> enable/disable virtualbox integration\n" - "\thostcur <ON|OFF> enable/disable mouse cursor rendering in host\n" + " integ <ON|OFF> enable/disable virtualbox integration\n" + " hostcur <ON|OFF> enable/disable mouse cursor rendering in host\n" #endif - "\treset reset mouse driver settings\n" + " reset reset mouse driver settings\n" ); } +static int invalid_arg(const char *s) +{ + fprintf(stderr, "Invalid argument '%s'", s); + print_help(); + return EXIT_FAILURE; +} + static bool is_true(const char *s) { return stricmp(s, "yes") == 0 @@ -388,15 +503,34 @@ int main(int argc, const char *argv[]) printf("\nVBMouse %x.%x (like MSMOUSE %x.%x)\n", VERSION_MAJOR, VERSION_MINOR, REPORTED_VERSION_MAJOR, REPORTED_VERSION_MINOR); if (argi >= argc || stricmp(argv[argi], "install") == 0) { + bool high = true; + + argi++; + for (; argi < argc; argi++) { + if (stricmp(argv[argi], "low") == 0) { + high = false; + } else if (stricmp(argv[argi], "high") == 0) { + high = true; + } else { + return invalid_arg(argv[argi]); + } + } + if (data) { printf("VBMouse already installed\n"); return EXIT_SUCCESS; } data = get_tsr_data(false); + if (high) { + err = reallocate_to_umb(&data); + if (err) high = false; // Not fatal + } else { + deallocate_environment(_psp); + } err = configure_driver(data); if (err) return EXIT_FAILURE; - return install_driver(data); + return install_driver(data, high); } else if (stricmp(argv[argi], "uninstall") == 0) { if (!data) return driver_not_found(); if (!check_if_driver_uninstallable(data)) { @@ -449,8 +583,7 @@ int main(int argc, const char *argv[]) #endif } else if (stricmp(argv[argi], "reset") == 0) { return driver_reset(); + } else { + return invalid_arg(argv[argi]); } - - print_help(); - return EXIT_FAILURE; } @@ -648,7 +648,7 @@ static void handle_mouse_event(uint16_t buttons, bool absolute, int x, int y, in } /** PS/2 BIOS calls this routine to notify mouse events. */ -static void __far ps2_mouse_handler(uint16_t word1, uint16_t word2, uint16_t word3, uint16_t word4) +static void ps2_mouse_handler(uint16_t word1, uint16_t word2, uint16_t word3, uint16_t word4) { #pragma aux ps2_mouse_handler "*" parm caller [ax] [bx] [cx] [dx] modify [ax bx cx dx si di es fs gs] @@ -876,7 +876,7 @@ static void reset_mouse_hardware() ps2m_set_sample_rate(4); // 4 = 80 reports per second ps2m_set_scaling_factor(1); // 1 = 1:1 scaling - ps2m_set_callback(ps2_mouse_callback); + ps2m_set_callback(get_cs():>ps2_mouse_callback); #if USE_INTEGRATION // By default, enable absolute mouse @@ -1290,7 +1290,7 @@ static void int2f_handler(union INTPACK r) break; case VMD_CALLOUT_GET_DOS_MOUSE_API: // Windows is asking our mouse driver for the hook function address - r.x.ds = FP_SEG(windows_mouse_callback); + r.x.ds = get_cs(); r.x.si = FP_OFF(windows_mouse_callback); r.x.ax = 0; // Yes, we are here! break; diff --git a/int21dos.h b/int21dos.h new file mode 100644 index 0000000..3990793 --- /dev/null +++ b/int21dos.h @@ -0,0 +1,100 @@ +/* + * VBMouse - Interface to some DOS int 21h services + * Copyright (C) 2022 Javier S. Pedro + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef INT21DOS_H +#define INT21DOS_H + +#include <stdbool.h> +#include <dos.h> + +#define DOS_PSP_SIZE 256 + +enum dos_allocation_strategy { + DOS_FIT_FIRST = 0, + DOS_FIT_BEST = 1, + DOS_FIT_LAST = 2, + + DOS_FIT_HIGH = 0x80, + DOS_FIT_HIGHONLY = 0x40, +}; + +static unsigned dos_query_allocation_strategy(void); +#pragma aux dos_query_allocation_strategy = \ + "mov ax, 0x5800" \ + "int 0x21" \ + __value [ax] + +static int dos_set_allocation_strategy(unsigned strategy); +#pragma aux dos_set_allocation_strategy = \ + "clc" \ + "mov ax, 0x5801" \ + "int 0x21" \ + "jc end" \ + "mov ax, 0" \ + "end:" \ + __parm [bx] \ + __value [ax] + +static bool dos_query_umb_link_state(void); +#pragma aux dos_query_umb_link_state = \ + "mov ax, 0x5802" \ + "int 0x21" \ + __value [al] + +static int dos_set_umb_link_state(bool state); +#pragma aux dos_set_umb_link_state = \ + "clc" \ + "mov ax, 0x5803" \ + "int 0x21" \ + "jc end" \ + "mov ax, 0" \ + "end:" \ + __parm [bx] \ + __value [ax] + +/** Allocates a new DOS segment. + * @returns either the allocated segment or 0 if any error. */ +static __segment dos_alloc(unsigned paragraphs); +#pragma aux dos_alloc = \ + "clc" \ + "mov ah, 0x48" \ + "int 0x21" \ + "jnc end" \ + "mov ax, 0xB" \ + "end:" \ + __parm [bx] \ + __value [ax] + +/** Frees DOS segment. */ +static void dos_free(_segment segment); +#pragma aux dos_free = \ + "mov ah, 0x49" \ + "int 0x21" \ + __parm [es] \ + __modify [ax] + +/** Sets a given PSP as the current active process. */ +static void dos_set_psp(_segment psp); +#pragma aux dos_set_psp = \ + "mov ah, 0x50" \ + "int 0x21" \ + __parm [bx] \ + __modify [ax] + +#endif // INT21DOS_H @@ -11,6 +11,9 @@ static inline void breakpoint(void); #pragma aux breakpoint = 0xcd 0x03; +static inline __segment get_cs(void); +#pragma aux get_cs = "mov ax, cs" value [ax] modify exact []; + /** Map x linearly from range [0, srcmax] to [0, dstmax]. * Equivalent of (x * dstmax) / srcmax but with 32-bit unsigned precision. */ static unsigned scaleu(unsigned x, unsigned srcmax, unsigned dstmax); |