587 lines
20 KiB
C
587 lines
20 KiB
C
// kpjoy.c -- KPJOY.DRV, a joystick driver for Windows 3.1: the PC game
|
|
// port's two sticks for MMSYSTEM's joystick functions (joyGetPos,
|
|
// joyGetDevCaps and the rest).
|
|
//
|
|
// Windows 3.1 has the joystick API but no joystick driver of its own;
|
|
// MMSYSTEM loads the one SYSTEM.INI names ([drivers] joystick=kpjoy.drv)
|
|
// as an installable driver and talks to it through DriverProc.
|
|
//
|
|
// The game port (I/O port 201h): writing it fires four one-shots, one per
|
|
// axis (bits 0-3: stick A's X and Y, stick B's X and Y), each of which
|
|
// holds its bit at 1 for a time set by the axis's position, about 24 us at
|
|
// one end to 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.
|
|
//
|
|
// The driver times each axis by counting reads of the port until its bit
|
|
// clears. The counts depend on the CPU's speed, so they are only ever used
|
|
// against each other: an axis's rest is taken from its first readings, as
|
|
// the stick is centred then, and a position is reported in 0..65535 with
|
|
// the rest at 32768 (the stick's travel to either side is learned as it
|
|
// moves). Anything that takes the CPU away during a count (an interrupt,
|
|
// an emulator slowing down) only ever shortens it, so the rest settles on
|
|
// the longest of the first readings that are not a push; and readings
|
|
// within DEAD_BAND_PERCENT of the rest report exactly 32768, so a centred
|
|
// stick reads centred through that jitter. A program, or a calibration
|
|
// tool, can set explicit calibration instead (JDD_SETCALIBRATION), which
|
|
// is used as it is. How long a missing axis is waited for is measured
|
|
// against the BIOS tick at enable, so it does not depend on the CPU either.
|
|
//
|
|
// One read of the port times all four axes, and serves both sticks: a
|
|
// program asking for stick A and then stick B gets the same reading, so the
|
|
// port is busy-waited on once per pair. A stick that is not there is looked
|
|
// for again now and then, so plugging one in is noticed.
|
|
//
|
|
// The code and data are fixed: joySetCapture's timer calls in at interrupt
|
|
// time.
|
|
|
|
#include <windows.h>
|
|
#include <mmsystem.h>
|
|
#include <conio.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
// ----- Constants -----
|
|
|
|
// Logging to C:\KPJOY.LOG (build with -DKPJOY_LOG=1): every message but
|
|
// JDD_GETPOS, and each change in what a stick reports, with its counts.
|
|
#ifndef KPJOY_LOG
|
|
#define KPJOY_LOG 0
|
|
#endif
|
|
|
|
// The joystick driver messages (the Windows 3.1 DDK's mmddk.h).
|
|
#define JDD_GETNUMDEVS (DRV_RESERVED + 0x0001)
|
|
#define JDD_GETDEVCAPS (DRV_RESERVED + 0x0002)
|
|
#define JDD_GETPOS (DRV_RESERVED + 0x0101)
|
|
#define JDD_SETCALIBRATION (DRV_RESERVED + 0x0102)
|
|
|
|
// The game port and its bits.
|
|
#define GAME_PORT 0x201
|
|
#define AXIS_COUNT 4
|
|
#define BUTTON_SHIFT 4
|
|
#define BUTTON_MASK_ALL 0x0F
|
|
#define BUTTONS_PER_STICK 2
|
|
#define BUTTON_STICK_MASK 0x03
|
|
|
|
// Two sticks of two axes each: stick A is axes 0 and 1, stick B 2 and 3.
|
|
#define STICK_COUNT 2
|
|
#define AXES_PER_STICK 2
|
|
#define STICK_AXIS_MASK 0x03
|
|
#define AXIS_X 0
|
|
#define AXIS_Y 1
|
|
|
|
// Positions as JOYCAPS reports their range.
|
|
#define POS_MIN 0u
|
|
#define POS_MAX 65535u
|
|
#define POS_REST 32768u
|
|
|
|
// A full push to one side is taken to be at least twice the rest's count
|
|
// until the stick shows more.
|
|
#define TRAVEL_FACTOR 2u
|
|
|
|
// The rest settles over an axis's first readings: one longer than the rest
|
|
// so far is the rest unless it is half as long again (a push).
|
|
#define REST_SETTLE_READINGS 64u
|
|
#define REST_PUSH_NUMERATOR 3u
|
|
#define REST_PUSH_DENOMINATOR 2u
|
|
|
|
// Readings this close to the rest (percent of the travel to either end)
|
|
// report the rest itself.
|
|
#define DEAD_BAND_PERCENT 4u
|
|
// (In 32 bits: an int is 16 here, and POS_REST * 4 would overflow it.)
|
|
#define DEAD_BAND ((uint16_t)((uint32_t)POS_REST * DEAD_BAND_PERCENT / 100u))
|
|
|
|
// A missing axis is waited for this long; the longest a real one holds its
|
|
// bit is about 1.35 ms.
|
|
#define TIMEOUT_MS 4u
|
|
|
|
// The BIOS tick: its count's place in the BIOS data area, and its period.
|
|
#define BIOS_DATA_SEGMENT 0x0040
|
|
#define BIOS_TICK_OFFSET 0x006C
|
|
#define BIOS_TICK_MS 55u
|
|
// Reads of the port counted while waiting for a tick, at most: a tick that
|
|
// never comes must not hang the enable.
|
|
#define CALIBRATE_READS_MAX 50000000UL
|
|
// The fewest reads a timeout is made of, whatever the measurement says.
|
|
#define TIMEOUT_READS_MIN 1000UL
|
|
|
|
// DPMI: a selector for a real-mode segment.
|
|
#define DPMI_INT 0x31
|
|
#define DPMI_SEG_TO_DESC 0x0002
|
|
|
|
// A missing stick is looked for again after this many readings.
|
|
#define PROBE_READINGS 64u
|
|
|
|
// What JOYCAPS says of the driver: no registered manufacturer or product,
|
|
// and the polling periods joySetCapture may ask for (ms).
|
|
#define KPJOY_MID 0
|
|
#define KPJOY_PID 0
|
|
#define KPJOY_NAME "KPJOY Game Port Joystick"
|
|
#define PERIOD_MIN_MS 10
|
|
#define PERIOD_MAX_MS 1000
|
|
|
|
#if KPJOY_LOG
|
|
#define LOG_FILE "C:\\KPJOY.LOG"
|
|
#define LOG_LINE_MAX 160
|
|
#define LOG_SEEK_END 2
|
|
// A line: wsprintf into gLogLine (DGROUP: with SS != DS a buffer on the
|
|
// stack cannot be handed to Windows by a near pointer), then appended.
|
|
#define LOG(...) logWrite(wsprintf(gLogLine, __VA_ARGS__))
|
|
#else
|
|
#define LOG(...)
|
|
#endif
|
|
|
|
// ----- Types -----
|
|
|
|
// Calibration as JDD_SETCALIBRATION gives it (mmddk.h's JOYCALIBRATE):
|
|
// position = (count - base) * delta, for X, Y and Z.
|
|
typedef struct {
|
|
uint16_t wXbase;
|
|
uint16_t wXdelta;
|
|
uint16_t wYbase;
|
|
uint16_t wYdelta;
|
|
uint16_t wZbase;
|
|
uint16_t wZdelta;
|
|
} JoyCalibrateT;
|
|
|
|
// What is known of one axis: its rest, the readings left for it to
|
|
// settle, and the longest reading yet.
|
|
typedef struct {
|
|
bool restKnown;
|
|
uint8_t settle;
|
|
uint32_t rest;
|
|
uint32_t high;
|
|
} AxisT;
|
|
|
|
// One stick: whether it is there, whether it has explicit calibration and
|
|
// what, and whether it has yet to report the last reading.
|
|
typedef struct {
|
|
bool present;
|
|
bool calibrated;
|
|
bool fresh;
|
|
JoyCalibrateT calibration;
|
|
} StickT;
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static uint16_t axisPosition(uint8_t axis, uint32_t count);
|
|
static uint16_t biosSelector(void);
|
|
static uint16_t biosTick(void);
|
|
static uint16_t calibratedPosition(uint32_t count, uint16_t base, uint16_t delta);
|
|
static void driverEnable(void);
|
|
LRESULT FAR PASCAL _export DriverProc(DWORD driverId, HDRVR driver, UINT message, LPARAM param1, LPARAM param2);
|
|
static LRESULT getDevCaps(JOYCAPS FAR *caps, UINT size);
|
|
static LRESULT getPos(uint8_t stick, JOYINFO FAR *info);
|
|
int FAR PASCAL LibMain(HINSTANCE instance, WORD dataSeg, WORD heapSize, LPSTR cmdLine);
|
|
#if KPJOY_LOG
|
|
static void logWrite(int length);
|
|
#endif
|
|
static uint16_t lowPosition(uint32_t count, uint32_t rest);
|
|
static void readPort(void);
|
|
static LRESULT setCalibration(uint8_t stick, const JoyCalibrateT FAR *set, JoyCalibrateT FAR *previous);
|
|
static uint8_t stickOf(DWORD driverId);
|
|
int FAR PASCAL _export WEP(int param);
|
|
|
|
// ----- Module state -----
|
|
|
|
static StickT gSticks[STICK_COUNT];
|
|
static AxisT gAxes[AXIS_COUNT];
|
|
// The last reading: each axis's count (gTimeoutReads if it never cleared)
|
|
// and the button bits (1 = pressed, stick A's in bits 0-1, B's in 2-3).
|
|
static uint32_t gCounts[AXIS_COUNT];
|
|
static uint8_t gButtons;
|
|
static uint32_t gTimeoutReads = TIMEOUT_READS_MIN;
|
|
static uint16_t gReadings;
|
|
static uint16_t gBiosSelector;
|
|
// JDD_GETDEVCAPS's answer, in DGROUP (see LOG).
|
|
static JOYCAPS gCaps;
|
|
#if KPJOY_LOG
|
|
// What each stick last reported.
|
|
static UINT gLogX[STICK_COUNT];
|
|
static UINT gLogY[STICK_COUNT];
|
|
static UINT gLogButtons[STICK_COUNT];
|
|
static char gLogLine[LOG_LINE_MAX];
|
|
static OFSTRUCT gLogFile;
|
|
#endif
|
|
|
|
// 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 the Open Watcom code it carries (its DLL start-up and C library
|
|
// helpers) is available, and where. This is the driver's copy, never shown;
|
|
// it is not static, so the compiler keeps it although nothing reads it.
|
|
// README.md gives the same text for the documentation.
|
|
const char owplNotice[] = "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";
|
|
|
|
// ----- Functions (alphabetical) -----
|
|
|
|
// An axis's count as a position: explicit calibration if its stick has
|
|
// one, else around the rest.
|
|
static uint16_t axisPosition(uint8_t axis, uint32_t count) {
|
|
AxisT *a;
|
|
StickT *s;
|
|
bool isY;
|
|
uint16_t travel;
|
|
uint16_t position;
|
|
|
|
a = &gAxes[axis];
|
|
s = &gSticks[axis / AXES_PER_STICK];
|
|
isY = (axis % AXES_PER_STICK) == AXIS_Y;
|
|
if (s->calibrated) {
|
|
if (isY) {
|
|
return calibratedPosition(count, s->calibration.wYbase, s->calibration.wYdelta);
|
|
}
|
|
return calibratedPosition(count, s->calibration.wXbase, s->calibration.wXdelta);
|
|
}
|
|
if (!a->restKnown) {
|
|
a->rest = count;
|
|
a->high = count * TRAVEL_FACTOR;
|
|
a->settle = REST_SETTLE_READINGS;
|
|
a->restKnown = true;
|
|
} else if (a->settle > 0) {
|
|
a->settle--;
|
|
if (count > a->rest && count * REST_PUSH_DENOMINATOR < a->rest * REST_PUSH_NUMERATOR) {
|
|
a->rest = count;
|
|
if (a->high < count * TRAVEL_FACTOR) {
|
|
a->high = count * TRAVEL_FACTOR;
|
|
}
|
|
}
|
|
}
|
|
if (count > a->high) {
|
|
a->high = count;
|
|
}
|
|
if (count <= a->rest) {
|
|
position = lowPosition(count, a->rest);
|
|
} else if (a->high <= a->rest) {
|
|
position = (uint16_t)POS_MAX;
|
|
} else {
|
|
travel = lowPosition(count - a->rest, a->high - a->rest);
|
|
if (travel > POS_MAX - POS_REST) {
|
|
travel = (uint16_t)(POS_MAX - POS_REST);
|
|
}
|
|
position = (uint16_t)(POS_REST + travel);
|
|
}
|
|
if (position >= POS_REST - DEAD_BAND && position <= POS_REST + DEAD_BAND) {
|
|
return (uint16_t)POS_REST;
|
|
}
|
|
return position;
|
|
}
|
|
|
|
|
|
// A selector for the BIOS data area, from DPMI, made once.
|
|
static uint16_t biosSelector(void) {
|
|
uint16_t sel;
|
|
|
|
if (gBiosSelector == 0) {
|
|
// 0 stays when DPMI refuses (carry set).
|
|
sel = 0;
|
|
_asm {
|
|
mov ax, DPMI_SEG_TO_DESC
|
|
mov bx, BIOS_DATA_SEGMENT
|
|
int DPMI_INT
|
|
jc refused
|
|
mov sel, ax
|
|
refused:
|
|
}
|
|
gBiosSelector = sel;
|
|
}
|
|
return gBiosSelector;
|
|
}
|
|
|
|
|
|
// The BIOS tick count's low word.
|
|
static uint16_t biosTick(void) {
|
|
uint16_t FAR *tick;
|
|
|
|
tick = (uint16_t FAR *)MAKELP(biosSelector(), BIOS_TICK_OFFSET);
|
|
return *tick;
|
|
}
|
|
|
|
|
|
// position = (count - base) * delta, kept to 0..65535.
|
|
static uint16_t calibratedPosition(uint32_t count, uint16_t base, uint16_t delta) {
|
|
uint32_t position;
|
|
|
|
if (count <= base) {
|
|
return (uint16_t)POS_MIN;
|
|
}
|
|
count -= base;
|
|
if (count > POS_MAX) {
|
|
return (uint16_t)POS_MAX;
|
|
}
|
|
position = count * delta;
|
|
if (position > POS_MAX) {
|
|
return (uint16_t)POS_MAX;
|
|
}
|
|
return (uint16_t)position;
|
|
}
|
|
|
|
|
|
// DRV_ENABLE: how many reads of the port make TIMEOUT_MS, counted over one
|
|
// whole BIOS tick (the first loop waits for a tick to begin), then a first
|
|
// look for both sticks. Rests and calibration start again.
|
|
static void driverEnable(void) {
|
|
uint16_t start;
|
|
uint32_t reads;
|
|
uint8_t i;
|
|
|
|
reads = 0;
|
|
if (biosSelector() != 0) {
|
|
start = biosTick();
|
|
while (biosTick() == start && reads < CALIBRATE_READS_MAX) {
|
|
reads++;
|
|
}
|
|
start = biosTick();
|
|
reads = 0;
|
|
while (biosTick() == start && reads < CALIBRATE_READS_MAX) {
|
|
(void)inp(GAME_PORT);
|
|
reads++;
|
|
}
|
|
}
|
|
gTimeoutReads = reads / BIOS_TICK_MS * TIMEOUT_MS;
|
|
if (gTimeoutReads < TIMEOUT_READS_MIN) {
|
|
gTimeoutReads = TIMEOUT_READS_MIN;
|
|
}
|
|
for (i = 0; i < AXIS_COUNT; i++) {
|
|
gAxes[i].restKnown = false;
|
|
}
|
|
for (i = 0; i < STICK_COUNT; i++) {
|
|
gSticks[i].present = true;
|
|
gSticks[i].calibrated = false;
|
|
gSticks[i].fresh = false;
|
|
}
|
|
gReadings = 0;
|
|
readPort();
|
|
LOG("enable: %lu reads per tick, timeout %lu reads; sticks %d %d", reads, gTimeoutReads, (int)gSticks[0].present, (int)gSticks[1].present);
|
|
}
|
|
|
|
|
|
LRESULT FAR PASCAL _export DriverProc(DWORD driverId, HDRVR driver, UINT message, LPARAM param1, LPARAM param2) {
|
|
if (message != JDD_GETPOS) {
|
|
LOG("message %04x id %lx param1 %lx param2 %lx", message, driverId, param1, param2);
|
|
}
|
|
switch (message) {
|
|
case DRV_LOAD:
|
|
case DRV_FREE:
|
|
case DRV_CLOSE:
|
|
case DRV_DISABLE:
|
|
return 1L;
|
|
case DRV_ENABLE:
|
|
driverEnable();
|
|
return 1L;
|
|
case DRV_OPEN:
|
|
// MMSYSTEM opens an instance for each stick, lParam2 0 and 1; the
|
|
// answer is the ID the stick's messages come with (stickOf).
|
|
if (param2 >= 0 && param2 < STICK_COUNT) {
|
|
return (LRESULT)(param2 + 1);
|
|
}
|
|
return 1L;
|
|
case DRV_QUERYCONFIGURE:
|
|
return 0L;
|
|
case DRV_INSTALL:
|
|
case DRV_REMOVE:
|
|
return (LRESULT)DRVCNF_OK;
|
|
case JDD_GETNUMDEVS:
|
|
return (LRESULT)STICK_COUNT;
|
|
case JDD_GETDEVCAPS:
|
|
return getDevCaps((JOYCAPS FAR *)param1, (UINT)param2);
|
|
case JDD_GETPOS:
|
|
return getPos(stickOf(driverId), (JOYINFO FAR *)param1);
|
|
case JDD_SETCALIBRATION:
|
|
return setCalibration(stickOf(driverId), (const JoyCalibrateT FAR *)param1, (JoyCalibrateT FAR *)param2);
|
|
default:
|
|
return DefDriverProc(driverId, driver, message, param1, param2);
|
|
}
|
|
}
|
|
|
|
|
|
// JDD_GETDEVCAPS: as much of JOYCAPS as the caller's size holds.
|
|
static LRESULT getDevCaps(JOYCAPS FAR *caps, UINT size) {
|
|
const char *from;
|
|
char *to;
|
|
UINT i;
|
|
|
|
if (caps == NULL) {
|
|
return (LRESULT)JOYERR_PARMS;
|
|
}
|
|
gCaps.wMid = KPJOY_MID;
|
|
gCaps.wPid = KPJOY_PID;
|
|
gCaps.wXmin = POS_MIN;
|
|
gCaps.wXmax = POS_MAX;
|
|
gCaps.wYmin = POS_MIN;
|
|
gCaps.wYmax = POS_MAX;
|
|
gCaps.wZmin = POS_MIN;
|
|
gCaps.wZmax = POS_MIN;
|
|
gCaps.wNumButtons = BUTTONS_PER_STICK;
|
|
gCaps.wPeriodMin = PERIOD_MIN_MS;
|
|
gCaps.wPeriodMax = PERIOD_MAX_MS;
|
|
from = KPJOY_NAME;
|
|
to = gCaps.szPname;
|
|
while (*from != '\0' && to < gCaps.szPname + MAXPNAMELEN - 1) {
|
|
*to++ = *from++;
|
|
}
|
|
*to = '\0';
|
|
for (i = 0; i < size && i < sizeof(gCaps); i++) {
|
|
((char FAR *)caps)[i] = ((char *)&gCaps)[i];
|
|
}
|
|
return (LRESULT)JOYERR_NOERROR;
|
|
}
|
|
|
|
|
|
// JDD_GETPOS: a new reading of the port unless this stick has not had the
|
|
// last one yet.
|
|
static LRESULT getPos(uint8_t stick, JOYINFO FAR *info) {
|
|
StickT *s;
|
|
uint8_t first;
|
|
|
|
if (info == NULL) {
|
|
return (LRESULT)JOYERR_PARMS;
|
|
}
|
|
s = &gSticks[stick];
|
|
if (!s->fresh) {
|
|
readPort();
|
|
}
|
|
s->fresh = false;
|
|
if (!s->present) {
|
|
return (LRESULT)JOYERR_UNPLUGGED;
|
|
}
|
|
first = (uint8_t)(stick * AXES_PER_STICK);
|
|
info->wXpos = axisPosition((uint8_t)(first + AXIS_X), gCounts[first + AXIS_X]);
|
|
info->wYpos = axisPosition((uint8_t)(first + AXIS_Y), gCounts[first + AXIS_Y]);
|
|
info->wZpos = 0;
|
|
info->wButtons = (gButtons >> (stick * BUTTONS_PER_STICK)) & BUTTON_STICK_MASK;
|
|
#if KPJOY_LOG
|
|
if (info->wXpos != gLogX[stick] || info->wYpos != gLogY[stick] || info->wButtons != gLogButtons[stick]) {
|
|
LOG("reading %u: stick %d at %u %u buttons %u, counts %lu %lu, rests %lu %lu", gReadings, (int)stick, info->wXpos, info->wYpos, info->wButtons, gCounts[first + AXIS_X], gCounts[first + AXIS_Y], gAxes[first + AXIS_X].rest, gAxes[first + AXIS_Y].rest);
|
|
gLogX[stick] = info->wXpos;
|
|
gLogY[stick] = info->wYpos;
|
|
gLogButtons[stick] = info->wButtons;
|
|
}
|
|
#endif
|
|
return (LRESULT)JOYERR_NOERROR;
|
|
}
|
|
|
|
|
|
int FAR PASCAL LibMain(HINSTANCE instance, WORD dataSeg, WORD heapSize, LPSTR cmdLine) {
|
|
(void)instance;
|
|
(void)dataSeg;
|
|
(void)heapSize;
|
|
(void)cmdLine;
|
|
return 1;
|
|
}
|
|
|
|
|
|
#if KPJOY_LOG
|
|
// Append gLogLine's length characters, and a line end, to LOG_FILE.
|
|
static void logWrite(int length) {
|
|
HFILE file;
|
|
|
|
gLogLine[length++] = '\r';
|
|
gLogLine[length++] = '\n';
|
|
file = OpenFile(LOG_FILE, &gLogFile, OF_WRITE);
|
|
if (file == HFILE_ERROR) {
|
|
file = OpenFile(LOG_FILE, &gLogFile, OF_CREATE | OF_WRITE);
|
|
}
|
|
if (file == HFILE_ERROR) {
|
|
return;
|
|
}
|
|
_llseek(file, 0L, LOG_SEEK_END);
|
|
_lwrite(file, gLogLine, (UINT)length);
|
|
_lclose(file);
|
|
}
|
|
#endif
|
|
|
|
|
|
// count / rest as 0..32768: both are cut down first so the product fits.
|
|
static uint16_t lowPosition(uint32_t count, uint32_t rest) {
|
|
while (rest > 0xFFFFUL) {
|
|
rest >>= 1;
|
|
count >>= 1;
|
|
}
|
|
if (rest == 0 || count >= rest) {
|
|
return (uint16_t)POS_REST;
|
|
}
|
|
return (uint16_t)(count * POS_REST / rest);
|
|
}
|
|
|
|
|
|
// One reading: fire the one-shots and count reads of the port until each
|
|
// awaited axis clears. The axes of the sticks that are there are awaited;
|
|
// now and then all four are, so a stick plugged in since is found. A stick
|
|
// that is there and does not clear is gone.
|
|
static void readPort(void) {
|
|
uint32_t reads;
|
|
uint8_t pending;
|
|
uint8_t cleared;
|
|
uint8_t value;
|
|
uint8_t axis;
|
|
uint8_t i;
|
|
bool probe;
|
|
|
|
probe = gReadings % PROBE_READINGS == 0;
|
|
gReadings++;
|
|
pending = 0;
|
|
for (i = 0; i < STICK_COUNT; i++) {
|
|
if (probe || gSticks[i].present) {
|
|
pending |= (uint8_t)(STICK_AXIS_MASK << (i * AXES_PER_STICK));
|
|
}
|
|
}
|
|
for (axis = 0; axis < AXIS_COUNT; axis++) {
|
|
gCounts[axis] = gTimeoutReads;
|
|
}
|
|
outp(GAME_PORT, 0);
|
|
reads = 0;
|
|
while (pending != 0 && reads < gTimeoutReads) {
|
|
value = (uint8_t)inp(GAME_PORT);
|
|
cleared = (uint8_t)(pending & ~value);
|
|
if (cleared != 0) {
|
|
for (axis = 0; axis < AXIS_COUNT; axis++) {
|
|
if ((cleared & (1u << axis)) != 0) {
|
|
gCounts[axis] = reads;
|
|
}
|
|
}
|
|
pending &= (uint8_t)~cleared;
|
|
}
|
|
reads++;
|
|
}
|
|
value = (uint8_t)inp(GAME_PORT);
|
|
gButtons = (uint8_t)((~value >> BUTTON_SHIFT) & BUTTON_MASK_ALL);
|
|
for (i = 0; i < STICK_COUNT; i++) {
|
|
if (probe || gSticks[i].present) {
|
|
gSticks[i].present = gCounts[i * AXES_PER_STICK + AXIS_X] < gTimeoutReads && gCounts[i * AXES_PER_STICK + AXIS_Y] < gTimeoutReads;
|
|
}
|
|
gSticks[i].fresh = true;
|
|
}
|
|
}
|
|
|
|
|
|
// JDD_SETCALIBRATION: the stick's calibration from now on; the one it had
|
|
// goes to previous.
|
|
static LRESULT setCalibration(uint8_t stick, const JoyCalibrateT FAR *set, JoyCalibrateT FAR *previous) {
|
|
StickT *s;
|
|
|
|
s = &gSticks[stick];
|
|
if (previous != NULL) {
|
|
*previous = s->calibration;
|
|
}
|
|
if (set == NULL) {
|
|
return (LRESULT)JOYERR_PARMS;
|
|
}
|
|
s->calibration = *set;
|
|
s->calibrated = true;
|
|
return (LRESULT)JOYERR_NOERROR;
|
|
}
|
|
|
|
|
|
// The stick a driver instance is for: DRV_OPEN returned its index plus 1.
|
|
static uint8_t stickOf(DWORD driverId) {
|
|
if (driverId >= 1 && driverId <= STICK_COUNT) {
|
|
return (uint8_t)(driverId - 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int FAR PASCAL _export WEP(int param) {
|
|
(void)param;
|
|
return 1;
|
|
}
|