207 lines
8.6 KiB
Markdown
207 lines
8.6 KiB
Markdown
# 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](#files)
|
|
- [Building](#building)
|
|
- [Installing](#installing)
|
|
- [How it works](#how-it-works)
|
|
- [What programs see](#what-programs-see)
|
|
- [Calibration](#calibration)
|
|
- [Limits](#limits)
|
|
- [Debug logging](#debug-logging)
|
|
- [Verification](#verification)
|
|
- [License](#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 `int` is 16 bits.** Arithmetic on positions (0..65535) and on the
|
|
counts is done in `uint32_t`; `32768u * 4` is 0 in an `unsigned 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
|
|
|
|
1. Copy `kpjoy.drv` to `\WINDOWS\SYSTEM`.
|
|
2. In `SYSTEM.INI` `[drivers]`, set `joystick=kpjoy.drv`.
|
|
3. 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](#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_BUTTON1` and `JOY_BUTTON2`.
|
|
- **Unplugged:** `JDD_GETPOS` answers `JOYERR_UNPLUGGED`; MMSYSTEM passes it
|
|
on from `joyGetPos`.
|
|
|
|
|
|
## 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_QUERYCONFIGURE` answers 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.
|
|
|
|
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
|
|
```
|