| .gitignore | ||
| kpjoy.c | ||
| kpjoy.lnk | ||
| LICENSE.md | ||
| Makefile | ||
| README.md | ||
KPJOY.DRV -- Game Port Joystick Driver for Windows 3.1
KPJOY.DRV is a joystick driver for Windows 3.1 and Windows for Workgroups
3.11. Windows 3.1 has the joystick API (joyGetPos, joyGetDevCaps,
joySetCapture and the rest, in MMSYSTEM) but ships no driver for it, so
without one no program ever sees a stick. KPJOY reads the PC game port's
two sticks (two axes and two buttons each, or one stick and a Y cable's
second) for every program that uses that API.
It was written for JoeyLib's Win16 port, whose Windows runs it, and is verified there under DOSBox Staging; it has not yet run on real hardware.
Contents
- Files
- Building
- Installing
- How it works
- What programs see
- Calibration
- Limits
- Debug logging
- Verification
- License
Files
| File | Contents |
|---|---|
kpjoy.c |
The driver: DriverProc, the game port reader, positions and calibration |
kpjoy.lnk |
wlink directives: a Windows DLL, module KPJOY, code and data fixed |
Makefile |
The build, for GNU make on Linux with Open Watcom v2 |
LICENSE.md |
The MIT License |
Building
Requires Open Watcom v2 with its 16-bit Windows libraries (lib286/win).
The Makefile uses the copy JoeyLib's installer sets up beside this project
(../joeylib/toolchains/opt/openWatcom) unless WATCOM names another.
make bin/kpjoy.drv
make LOG=1 the same, logging to C:\KPJOY.LOG
make clean removes obj/ and bin/
make OBJ=DIR BIN=DIR builds somewhere else
Compiler flags (see Makefile): Windows target DLL (-bt=windows -bd),
small model with SS != DS (-ms -zu: a driver runs on its caller's stack),
the Windows prolog for exported functions (-zw), no stack probes (-s),
286 code (-2), 2-byte structure packing as the Windows headers expect
(-zp2), every warning, as an error.
Two rules matter for anyone changing the code:
- No near pointers to locals. With SS != DS a near pointer is DS-relative, so a buffer handed to Windows (or reached through a pointer) lives in DGROUP, not on the stack.
- An
intis 16 bits. Arithmetic on positions (0..65535) and on the counts is done inuint32_t;32768u * 4is 0 in anunsigned int.
JoeyLib's installer builds it with this Makefile (its kpjoy component),
into its own build tree, so this folder is never written to.
Installing
- Copy
kpjoy.drvto\WINDOWS\SYSTEM. - In
SYSTEM.INI[drivers], setjoystick=kpjoy.drv. - Restart Windows.
MMSYSTEM loads and enables the driver itself (DRV_LOAD, DRV_ENABLE).
A game shipped for Windows 3.x can carry the driver and these two steps in
its own setup (see License).
How it works
The game port. Writing I/O port 201h fires four one-shots, one per axis (bits 0-3: stick A's X and Y, stick B's X and Y). Each holds its bit at 1 for a time set by the axis's position: about 24 us at one end, about 0.7 ms in the middle, about 1.35 ms at the other. Bits 4-7 are the buttons (stick A's 1 and 2, stick B's 1 and 2), 0 while pressed. An axis with nothing plugged in never clears.
Timing. The driver counts reads of the port until each axis's bit clears. The count depends on the CPU's speed, so counts are only ever used against each other (below). How long a missing axis is waited for, 4 ms, is turned into a number of reads at enable by counting reads over one BIOS tick (the BIOS data area's tick count, through a DPMI selector), so that does not depend on the CPU either.
One reading for both sticks. A reading times all four axes at once. MMSYSTEM asks for each stick separately, so the reading is kept until both sticks have had it: a program polling sticks A and B each frame costs one busy wait per frame, not two. A stick that is not there is not waited for, except every 64th reading, which looks for all four axes so a stick plugged in since is found; a stick that is there and stops clearing is reported unplugged.
MMSYSTEM's protocol. MMSYSTEM opens the driver once per joystick:
DRV_OPEN with lParam2 0 for the first stick and 1 for the second, and
the driver's answer (the stick's index plus 1) comes back as the driver ID
of that stick's JDD_* messages. The messages are those of the Windows 3.1
DDK's mmddk.h:
| Message | Value | Answer |
|---|---|---|
JDD_GETNUMDEVS |
0x0801 | 2 |
JDD_GETDEVCAPS |
0x0802 | a JOYCAPS (lParam1), as much as lParam2's size holds |
JDD_GETPOS |
0x0901 | a JOYINFO (lParam1), or JOYERR_UNPLUGGED |
JDD_SETCALIBRATION |
0x0902 | sets the stick's calibration (below) |
Everything else goes to DefDriverProc. The code and data segments are
fixed: joySetCapture's timer calls in at interrupt time.
What programs see
- JOYCAPS: X and Y from 0 to 65535, no Z, two buttons, polling periods 10 ms to 1000 ms; the name "KPJOY Game Port Joystick"; manufacturer and product 0 (unregistered).
- JOYINFO: X and Y in 0..65535, the rest at 32768; buttons
JOY_BUTTON1andJOY_BUTTON2. - Unplugged:
JDD_GETPOSanswersJOYERR_UNPLUGGED; MMSYSTEM passes it on fromjoyGetPos.
Calibration
Automatic (the default). An axis's rest is taken from its first
readings, as the stick is centred then: leave it centred as the program
starts. Anything that takes the CPU away during a count -- an interrupt,
an emulator slowing down -- only ever shortens it, so over the first 64
readings the rest rises to the longest reading that is not a push (one half
as long again as the rest is a push). The travel to either side is learned
as the stick moves: a full push is taken to be at least twice the rest's
count until the stick shows more. Readings within 4% of the rest report
exactly 32768, so a centred stick reads centred through jitter.
DRV_ENABLE starts it all again.
Explicit. JDD_SETCALIBRATION (lParam1: the new values, lParam2, if
not NULL: where the old ones go, both in mmddk.h's JOYCALIBRATE layout)
sets a stick's calibration: position = (count - base) * delta for X and Y,
kept to 0..65535, where count is the driver's raw count. From then on that
stick's readings use it as they are, with no dead band.
Limits
- Two sticks of two axes and two buttons. A four-button stick on one port reads as stick A plus stick B's buttons; a throttle or rudder on the second stick's axes reads as stick B.
- No Z axis and no configuration dialog (
DRV_QUERYCONFIGUREanswers 0). - Interrupts stay on while the port is timed (a count of up to about 1.35 ms with a stick in place), so a serial port at 115200 baud keeps its FIFO served; the price is the jitter the dead band absorbs.
- Counts follow the CPU's speed. A real PC's is fixed, but an emulator that varies its own (DOSBox at max cycles, which follows the host's load) makes the positions drift with it, the rest included; give it a fixed speed.
Debug logging
make LOG=1 builds a driver that appends to C:\KPJOY.LOG: every message
but JDD_GETPOS (with its driver ID and parameters), the enable's
measurements, and a line for each change in what a stick reports, with the
counts and rests behind it. The log is written with KERNEL's file calls,
not at interrupt time; do not ship it.
Verification
JoeyLib's scripts/verify-win16-joy.sh runs JOY (JoeyLib's
examples/joy) on Windows for Workgroups 3.11 in DOSBox Staging, with
DOSBox's 4-axis game port driven from host keys, and reads both sticks'
whole state back off the screen: rest, each direction of each stick, two
diagonals each, each button alone and both together, a release after each,
a step holding lines on both sticks at once, and no stick at all when the
game port is disabled, with DOSBox's CPU at a fixed speed.
Not yet done: a real 386 or 486 with an analog stick, a real Y cable,
joySetCapture, and a calibration tool's JDD_SETCALIBRATION.
License
KPJOY is distributed under the MIT License (LICENSE.md): it may be used,
changed and shipped, alone or with a program, as source or as kpjoy.drv,
as long as the copyright and permission notice go with it. Each source
file opens with a short header that says so
(SPDX-License-Identifier: MIT).
The driver also carries Open Watcom code (its DLL start-up and C library
helpers), and Open Watcom's license (the Sybase Open Watcom Public
License, section 2.2(d)) asks for a notice, in the driver and in its
documentation, that the source of that code is available and where.
kpjoy.drv carries the notice (never shown); a program that ships the
driver puts this line in its documentation:
This software contains code from Open Watcom. Its source code is available
under the terms of the Sybase Open Watcom Public License version 1.0 from
https://github.com/open-watcom/open-watcom-v2