refactor all common parts and games

This commit is contained in:
2025-04-17 19:31:37 +02:00
parent a6126bf290
commit ae3dd666f4
496 changed files with 236 additions and 344 deletions

16
games/swdcio/backend.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <stdint.h>
#include "swdcio/swdcio.h"
struct swdc_io_backend {
void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint16_t *gamebtn);
void (*get_analogs)(struct swdc_io_analog_state *state);
HRESULT (*ffb_init)(void);
void (*ffb_toggle)(bool active);
void (*ffb_constant_force)(uint8_t direction, uint8_t force);
void (*ffb_rumble)(uint8_t period, uint8_t force);
void (*ffb_damper)(uint8_t force);
};

147
games/swdcio/config.c Normal file
View File

@ -0,0 +1,147 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include "swdcio/config.h"
void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename)
{
wchar_t key[8];
int i;
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"dinput",
L"deviceName",
L"",
cfg->device_name,
_countof(cfg->device_name),
filename);
GetPrivateProfileStringW(
L"dinput",
L"pedalsName",
L"",
cfg->pedals_name,
_countof(cfg->pedals_name),
filename);
GetPrivateProfileStringW(
L"dinput",
L"brakeAxis",
L"RZ",
cfg->brake_axis,
_countof(cfg->brake_axis),
filename);
GetPrivateProfileStringW(
L"dinput",
L"accelAxis",
L"Y",
cfg->accel_axis,
_countof(cfg->accel_axis),
filename);
cfg->start = GetPrivateProfileIntW(L"dinput", L"start", 0, filename);
cfg->view_chg = GetPrivateProfileIntW(L"dinput", L"viewChg", 0, filename);
cfg->paddle_left = GetPrivateProfileIntW(L"dinput", L"paddleLeft", 0, filename);
cfg->paddle_right = GetPrivateProfileIntW(L"dinput", L"paddleRight", 0, filename);
cfg->wheel_green = GetPrivateProfileIntW(L"dinput", L"wheelGreen", 0, filename);
cfg->wheel_red = GetPrivateProfileIntW(L"dinput", L"wheelRed", 0, filename);
cfg->wheel_blue = GetPrivateProfileIntW(L"dinput", L"wheelBlue", 0, filename);
cfg->wheel_yellow = GetPrivateProfileIntW(L"dinput", L"wheelYellow", 0, filename);
cfg->reverse_brake_axis = GetPrivateProfileIntW(
L"dinput",
L"reverseBrakeAxis",
0,
filename);
cfg->reverse_accel_axis = GetPrivateProfileIntW(
L"dinput",
L"reverseAccelAxis",
0,
filename);
// FFB configuration
cfg->ffb_constant_force_strength = GetPrivateProfileIntW(
L"dinput",
L"constantForceStrength",
100,
filename);
cfg->ffb_rumble_strength = GetPrivateProfileIntW(
L"dinput",
L"rumbleStrength",
100,
filename);
cfg->ffb_damper_strength = GetPrivateProfileIntW(
L"dinput",
L"damperStrength",
100,
filename);
cfg->ffb_rumble_duration = GetPrivateProfileIntW(
L"dinput",
L"rumbleDuration",
1000,
filename);
}
void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->single_stick_steering = GetPrivateProfileIntW(
L"xinput",
L"singleStickSteering",
0,
filename);
cfg->linear_steering = GetPrivateProfileIntW(
L"xinput",
L"linearSteering",
0,
filename);
cfg->left_stick_deadzone = GetPrivateProfileIntW(
L"xinput",
L"leftStickDeadzone",
7849,
filename);
cfg->right_stick_deadzone = GetPrivateProfileIntW(
L"xinput",
L"rightStickDeadzone",
8689,
filename);
}
void swdc_io_config_load(struct swdc_io_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename);
cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename);
cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename);
cfg->restrict_ = GetPrivateProfileIntW(L"io4", L"restrict", 128, filename);
GetPrivateProfileStringW(
L"io4",
L"mode",
L"xinput",
cfg->mode,
_countof(cfg->mode),
filename);
swdc_di_config_load(&cfg->di, filename);
swdc_xi_config_load(&cfg->xi, filename);
}

50
games/swdcio/config.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct swdc_di_config {
wchar_t device_name[64];
wchar_t pedals_name[64];
wchar_t brake_axis[16];
wchar_t accel_axis[16];
uint8_t start;
uint8_t view_chg;
uint8_t paddle_left;
uint8_t paddle_right;
uint8_t wheel_green;
uint8_t wheel_red;
uint8_t wheel_blue;
uint8_t wheel_yellow;
bool reverse_brake_axis;
bool reverse_accel_axis;
// FFB configuration
uint8_t ffb_constant_force_strength;
uint8_t ffb_rumble_strength;
uint8_t ffb_damper_strength;
uint32_t ffb_rumble_duration;
};
struct swdc_xi_config {
bool single_stick_steering;
bool linear_steering;
uint16_t left_stick_deadzone;
uint16_t right_stick_deadzone;
};
struct swdc_io_config {
uint8_t vk_test;
uint8_t vk_service;
uint8_t vk_coin;
wchar_t mode[8];
int restrict_;
struct swdc_di_config di;
struct swdc_xi_config xi;
};
void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename);
void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename);
void swdc_io_config_load(struct swdc_io_config *cfg, const wchar_t *filename);

371
games/swdcio/di-dev.c Normal file
View File

@ -0,0 +1,371 @@
#include <windows.h>
#include <dinput.h>
#include <stdbool.h>
#include <assert.h>
#include "swdcio/di-dev.h"
#include "util/dprintf.h"
const struct swdc_di_config *swdc_di_cfg;
static HWND swdc_di_wnd;
static IDirectInputDevice8W *swdc_di_dev;
/* Individual DI Effects */
static IDirectInputEffect *swdc_di_fx;
static IDirectInputEffect *swdc_di_fx_rumble;
static IDirectInputEffect *swdc_di_fx_damper;
/* Max FFB Board value is 127 */
static const double swdc_di_ffb_scale = 127.0;
HRESULT swdc_di_dev_init(
const struct swdc_di_config *cfg,
IDirectInputDevice8W *dev,
HWND wnd)
{
HRESULT hr;
assert(dev != NULL);
assert(wnd != NULL);
swdc_di_cfg = cfg;
swdc_di_dev = dev;
swdc_di_wnd = wnd;
return S_OK;
}
HRESULT swdc_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,
union swdc_di_state *out)
{
HRESULT hr;
MSG msg;
assert(dev != NULL);
assert(wnd != NULL);
assert(out != NULL);
memset(out, 0, sizeof(*out));
/* Pump our dummy window's message queue just in case DirectInput or an
IHV DirectInput driver somehow relies on it */
while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
hr = IDirectInputDevice8_GetDeviceState(
dev,
sizeof(out->st),
&out->st);
if (FAILED(hr)) {
dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr);
}
return hr;
}
HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) {
HRESULT hr;
assert(dev != NULL);
assert(wnd != NULL);
hr = IDirectInputDevice8_SetCooperativeLevel(
dev,
wnd,
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
if (FAILED(hr)) {
dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr);
return hr;
}
hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick);
if (FAILED(hr)) {
dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr);
return hr;
}
hr = IDirectInputDevice8_Acquire(dev);
if (FAILED(hr)) {
dprintf("DirectInput: Acquire failed: %08x\n", (int)hr);
return hr;
}
return hr;
}
HRESULT swdc_di_ffb_init(void)
{
HRESULT hr;
hr = swdc_di_dev_start(swdc_di_dev, swdc_di_wnd);
if (FAILED(hr)) {
return hr;
}
return S_OK;
}
void swdc_di_ffb_toggle(bool active)
{
if (active) {
return;
}
/* Stop and release all effects */
/* I never programmed DirectInput Effects, so this might be bad practice. */
if (swdc_di_fx != NULL) {
IDirectInputEffect_Stop(swdc_di_fx);
IDirectInputEffect_Release(swdc_di_fx);
swdc_di_fx = NULL;
}
if (swdc_di_fx_rumble != NULL) {
IDirectInputEffect_Stop(swdc_di_fx_rumble);
IDirectInputEffect_Release(swdc_di_fx_rumble);
swdc_di_fx_rumble = NULL;
}
if (swdc_di_fx_damper != NULL) {
IDirectInputEffect_Stop(swdc_di_fx_damper);
IDirectInputEffect_Release(swdc_di_fx_damper);
swdc_di_fx_damper = NULL;
}
}
void swdc_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force)
{
/* DI expects a magnitude in the range of -10.000 to 10.000 */
uint16_t ffb_strength = swdc_di_cfg->ffb_constant_force_strength * 100;
if (ffb_strength == 0) {
return;
}
DWORD axis;
LONG direction;
DIEFFECT fx;
DICONSTANTFORCE cf;
HRESULT hr;
/* Direction 0: move to the right, 1: move to the left */
LONG magnitude = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength);
cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude;
axis = DIJOFS_X;
direction = 0;
memset(&fx, 0, sizeof(fx));
fx.dwSize = sizeof(fx);
fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
fx.dwDuration = INFINITE;
fx.dwGain = DI_FFNOMINALMAX;
fx.dwTriggerButton = DIEB_NOTRIGGER;
fx.dwTriggerRepeatInterval = INFINITE;
fx.cAxes = 1;
fx.rgdwAxes = &axis;
fx.rglDirection = &direction;
fx.cbTypeSpecificParams = sizeof(cf);
fx.lpvTypeSpecificParams = &cf;
/* Check if the effect already exists */
if (swdc_di_fx != NULL) {
hr = IDirectInputEffect_SetParameters(swdc_di_fx, &fx, DIEP_TYPESPECIFICPARAMS);
if (SUCCEEDED(hr)) {
return; // Successfully updated existing effect
}
else {
dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr);
IDirectInputEffect_Stop(swdc_di_fx);
IDirectInputEffect_Release(swdc_di_fx);
swdc_di_fx = NULL; // Reset the pointer
}
}
/* Create a new constant force effect */
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
swdc_di_dev,
&GUID_ConstantForce,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int)hr);
return;
}
/* Start the effect */
hr = IDirectInputEffect_Start(obj, INFINITE, 0);
if (FAILED(hr)) {
dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int)hr);
IDirectInputEffect_Release(obj);
return;
}
swdc_di_fx = obj;
}
void swdc_di_ffb_rumble(uint8_t force, uint8_t period)
{
/* DI expects a magnitude in the range of -10.000 to 10.000 */
uint16_t ffb_strength = swdc_di_cfg->ffb_rumble_strength * 100;
if (ffb_strength == 0) {
return;
}
uint32_t ffb_duration = swdc_di_cfg->ffb_rumble_duration;
DWORD axis;
LONG direction;
DIEFFECT fx;
DIPERIODIC pe;
HRESULT hr;
DWORD duration = (DWORD)((double)force * ffb_duration);
memset(&pe, 0, sizeof(pe));
pe.dwMagnitude = (DWORD)(((double)force / swdc_di_ffb_scale) * ffb_strength);
pe.lOffset = 0;
pe.dwPhase = 0;
pe.dwPeriod = duration;
axis = DIJOFS_X;
direction = 0;
memset(&fx, 0, sizeof(fx));
fx.dwSize = sizeof(fx);
fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
fx.dwDuration = INFINITE;
fx.dwGain = DI_FFNOMINALMAX;
fx.dwTriggerButton = DIEB_NOTRIGGER;
fx.dwTriggerRepeatInterval = INFINITE;
fx.cAxes = 1;
fx.rgdwAxes = &axis;
fx.rglDirection = &direction;
fx.cbTypeSpecificParams = sizeof(pe);
fx.lpvTypeSpecificParams = &pe;
/* Check if the effect already exists */
if (swdc_di_fx_rumble != NULL) {
hr = IDirectInputEffect_SetParameters(swdc_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS);
if (SUCCEEDED(hr)) {
return;
}
else {
dprintf("DirectInput: Failed to update rumble feedback, recreating effect: %08x\n", (int)hr);
IDirectInputEffect_Stop(swdc_di_fx_rumble);
IDirectInputEffect_Release(swdc_di_fx_rumble);
swdc_di_fx_rumble = NULL;
}
}
/* Create a new rumble effect */
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
swdc_di_dev,
&GUID_Sine,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: Rumble effect creation failed: %08x\n", (int)hr);
return;
}
/* Start the effect */
hr = IDirectInputEffect_Start(obj, INFINITE, 0);
if (FAILED(hr)) {
dprintf("DirectInput: Rumble effect start failed: %08x\n", (int)hr);
IDirectInputEffect_Release(obj);
return;
}
swdc_di_fx_rumble = obj;
}
void swdc_di_ffb_damper(uint8_t force)
{
/* DI expects a coefficient in the range of -10.000 to 10.000 */
uint16_t ffb_strength = swdc_di_cfg->ffb_damper_strength * 100;
if (ffb_strength == 0) {
return;
}
DWORD axis;
LONG direction;
DIEFFECT fx;
DICONDITION cond;
HRESULT hr;
memset(&cond, 0, sizeof(cond));
cond.lOffset = 0;
cond.lPositiveCoefficient = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength);
cond.lNegativeCoefficient = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength);
/* Not sure on this one */
cond.dwPositiveSaturation = DI_FFNOMINALMAX;
cond.dwNegativeSaturation = DI_FFNOMINALMAX;
cond.lDeadBand = 0;
axis = DIJOFS_X;
direction = 0;
memset(&fx, 0, sizeof(fx));
fx.dwSize = sizeof(fx);
fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
fx.dwDuration = INFINITE;
fx.dwGain = DI_FFNOMINALMAX;
fx.dwTriggerButton = DIEB_NOTRIGGER;
fx.dwTriggerRepeatInterval = INFINITE;
fx.cAxes = 1;
fx.rgdwAxes = &axis;
fx.rglDirection = &direction;
fx.cbTypeSpecificParams = sizeof(cond);
fx.lpvTypeSpecificParams = &cond;
/* Check if the damper effect already exists */
if (swdc_di_fx_damper != NULL) {
hr = IDirectInputEffect_SetParameters(swdc_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS);
if (SUCCEEDED(hr)) {
return;
}
else {
IDirectInputEffect_Stop(swdc_di_fx_damper);
IDirectInputEffect_Release(swdc_di_fx_damper);
swdc_di_fx_damper = NULL;
}
}
/* Create a new damper effect */
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
swdc_di_dev,
&GUID_Damper,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
return;
}
/* Start the effect */
hr = IDirectInputEffect_Start(obj, fx.dwDuration, 0);
if (FAILED(hr)) {
IDirectInputEffect_Release(obj);
return;
}
swdc_di_fx_damper = obj;
}

30
games/swdcio/di-dev.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <windows.h>
#include <dinput.h>
#include <stdint.h>
#include "swdcio/config.h"
union swdc_di_state {
DIJOYSTATE st;
uint8_t bytes[sizeof(DIJOYSTATE)];
};
HRESULT swdc_di_dev_init(
const struct swdc_di_config *cfg,
IDirectInputDevice8W *dev,
HWND wnd);
HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
HRESULT swdc_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,
union swdc_di_state *out);
HRESULT swdc_di_ffb_init(void);
void swdc_di_ffb_toggle(bool active);
void swdc_di_ffb_constant_force(uint8_t direction, uint8_t force);
void swdc_di_ffb_rumble(uint8_t force, uint8_t period);
void swdc_di_ffb_damper(uint8_t force);

528
games/swdcio/di.c Normal file
View File

@ -0,0 +1,528 @@
#include <windows.h>
#include <dinput.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <wchar.h>
#include "swdcio/backend.h"
#include "swdcio/config.h"
#include "swdcio/di.h"
#include "swdcio/di-dev.h"
#include "swdcio/swdcio.h"
#include "swdcio/wnd.h"
#include "util/dprintf.h"
#include "util/str.h"
struct swdc_di_axis {
wchar_t name[4];
size_t off;
};
static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg);
static const struct swdc_di_axis *swdc_di_get_axis(const wchar_t *name);
static BOOL CALLBACK swdc_di_enum_callback(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static BOOL CALLBACK swdc_di_enum_callback_pedals(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static void swdc_di_get_buttons(uint16_t *gamebtn_out);
static uint8_t swdc_di_decode_pov(DWORD pov);
static void swdc_di_get_analogs(struct swdc_io_analog_state *out);
static const struct swdc_di_axis swdc_di_axes[] = {
/* Just map DIJOYSTATE for now, we can map DIJOYSTATE2 later if needed */
{ .name = L"X", .off = DIJOFS_X },
{ .name = L"Y", .off = DIJOFS_Y },
{ .name = L"Z", .off = DIJOFS_Z },
{ .name = L"RX", .off = DIJOFS_RX },
{ .name = L"RY", .off = DIJOFS_RY },
{ .name = L"RZ", .off = DIJOFS_RZ },
{ .name = L"U", .off = DIJOFS_SLIDER(0) },
{ .name = L"V", .off = DIJOFS_SLIDER(1) },
};
static const struct swdc_io_backend swdc_di_backend = {
.get_gamebtns = swdc_di_get_buttons,
.get_analogs = swdc_di_get_analogs,
.ffb_init = swdc_di_ffb_init,
.ffb_toggle = swdc_di_ffb_toggle,
.ffb_constant_force = swdc_di_ffb_constant_force,
.ffb_rumble = swdc_di_ffb_rumble,
.ffb_damper = swdc_di_ffb_damper
};
static HWND swdc_di_wnd;
static IDirectInput8W *swdc_di_api;
static IDirectInputDevice8W *swdc_di_dev;
static IDirectInputDevice8W *swdc_di_pedals;
static IDirectInputEffect *swdc_di_fx;
static size_t swdc_di_off_brake;
static size_t swdc_di_off_accel;
static uint8_t swdc_di_paddle_left;
static uint8_t swdc_di_paddle_right;
static uint8_t swdc_di_view_chg;
static uint8_t swdc_di_start;
static uint8_t swdc_di_wheel_green;
static uint8_t swdc_di_wheel_red;
static uint8_t swdc_di_wheel_blue;
static uint8_t swdc_di_wheel_yellow;
static bool swdc_di_use_pedals;
static bool swdc_di_reverse_brake_axis;
static bool swdc_di_reverse_accel_axis;
HRESULT swdc_di_init(
const struct swdc_di_config *cfg,
HINSTANCE inst,
const struct swdc_io_backend **backend)
{
HRESULT hr;
HMODULE dinput8;
HRESULT (WINAPI *api_entry)(HINSTANCE,DWORD,REFIID,LPVOID *,LPUNKNOWN);
wchar_t dll_path[MAX_PATH];
UINT path_pos;
assert(cfg != NULL);
assert(backend != NULL);
*backend = NULL;
hr = swdc_di_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
hr = swdc_io_wnd_create(inst, &swdc_di_wnd);
if (FAILED(hr)) {
return hr;
}
/* SWDC has some built-in DirectInput support that is not
particularly useful. swdchook shorts this out by redirecting dinput8.dll
to a no-op implementation of DirectInput. However, swdcio does need to
talk to the real operating system implementation of DirectInput without
the stub DLL interfering, so build a path to
C:\Windows\System32\dinput.dll here. */
dll_path[0] = L'\0';
path_pos = GetSystemDirectoryW(dll_path, _countof(dll_path));
wcscat_s(
dll_path + path_pos,
_countof(dll_path) - path_pos,
L"\\dinput8.dll");
dinput8 = LoadLibraryW(dll_path);
if (dinput8 == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("DirectInput: LoadLibrary failed: %08x\n", (int) hr);
return hr;
}
api_entry = (void *) GetProcAddress(dinput8, "DirectInput8Create");
if (api_entry == NULL) {
dprintf("DirectInput: GetProcAddress failed\n");
return E_FAIL;
}
hr = api_entry(
inst,
DIRECTINPUT_VERSION,
&IID_IDirectInput8W,
(void **) &swdc_di_api,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: API create failed: %08x\n", (int) hr);
return hr;
}
hr = IDirectInput8_EnumDevices(
swdc_di_api,
DI8DEVCLASS_GAMECTRL,
swdc_di_enum_callback,
(void *) cfg,
DIEDFL_ATTACHEDONLY);
if (FAILED(hr)) {
dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr);
return hr;
}
if (swdc_di_dev == NULL) {
dprintf("Wheel: Controller not found\n");
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = swdc_di_dev_init(cfg, swdc_di_dev, swdc_di_wnd);
if (FAILED(hr)) {
return hr;
}
if (cfg->pedals_name[0] != L'\0') {
hr = IDirectInput8_EnumDevices(
swdc_di_api,
DI8DEVCLASS_GAMECTRL,
swdc_di_enum_callback_pedals,
(void *) cfg,
DIEDFL_ATTACHEDONLY);
if (FAILED(hr)) {
dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr);
return hr;
}
if (swdc_di_dev == NULL) {
dprintf("Pedals: Controller not found\n");
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = swdc_di_dev_start(swdc_di_pedals, swdc_di_wnd);
if (FAILED(hr)) {
return hr;
}
swdc_di_use_pedals = true;
} else {
swdc_di_use_pedals = false;
}
dprintf("DirectInput: Controller initialized\n");
*backend = &swdc_di_backend;
return S_OK;
}
static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg)
{
const struct swdc_di_axis *brake_axis;
const struct swdc_di_axis *accel_axis;
int i;
brake_axis = swdc_di_get_axis(cfg->brake_axis);
accel_axis = swdc_di_get_axis(cfg->accel_axis);
if (brake_axis == NULL) {
dprintf("Wheel: Invalid brake axis: %S\n", cfg->brake_axis);
return E_INVALIDARG;
}
if (accel_axis == NULL) {
dprintf("Wheel: Invalid accel axis: %S\n", cfg->accel_axis);
return E_INVALIDARG;
}
if (cfg->start > 32) {
dprintf("Wheel: Invalid start button: %i\n", cfg->start);
return E_INVALIDARG;
}
if (cfg->view_chg > 32) {
dprintf("Wheel: Invalid view change button: %i\n", cfg->view_chg);
return E_INVALIDARG;
}
if (cfg->paddle_left > 32) {
dprintf("Wheel: Invalid left paddle button: %i\n", cfg->paddle_left);
return E_INVALIDARG;
}
if (cfg->paddle_right > 32) {
dprintf("Wheel: Invalid right paddle button: %i\n", cfg->paddle_right);
return E_INVALIDARG;
}
if (cfg->wheel_green > 32) {
dprintf("Wheel: Invalid steering wheel green button: %i\n", cfg->wheel_green);
return E_INVALIDARG;
}
if (cfg->wheel_red > 32) {
dprintf("Wheel: Invalid steering wheel red button: %i\n", cfg->wheel_red);
return E_INVALIDARG;
}
if (cfg->wheel_blue > 32) {
dprintf("Wheel: Invalid steering wheel blue button: %i\n", cfg->wheel_blue);
return E_INVALIDARG;
}
if (cfg->wheel_yellow > 32) {
dprintf("Wheel: Invalid steering wheel yellow button: %i\n", cfg->wheel_yellow);
return E_INVALIDARG;
}
/* Print some debug output to make sure config works... */
dprintf("Wheel: --- Begin configuration ---\n");
dprintf("Wheel: Device name . . . . : Contains \"%S\"\n",
cfg->device_name);
if (cfg->pedals_name[0] == L'\0') {
dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name);
dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name);
}
dprintf("Wheel: Start button . . . . . : %i\n", cfg->start);
dprintf("Wheel: View Change button . . : %i\n", cfg->view_chg);
dprintf("Wheel: Paddle Left button . . : %i\n", cfg->paddle_left);
dprintf("Wheel: Paddle Right button . : %i\n", cfg->paddle_right);
dprintf("Wheel: Steering Green button : %i\n", cfg->wheel_green);
dprintf("Wheel: Steering Red button . : %i\n", cfg->wheel_red);
dprintf("Wheel: Steering Blue button . : %i\n", cfg->wheel_blue);
dprintf("Wheel: Steering Yellow button : %i\n", cfg->wheel_yellow);
dprintf("Wheel: Reverse Brake Axis . . : %i\n", cfg->reverse_brake_axis);
dprintf("Wheel: Reverse Accel Axis . . : %i\n", cfg->reverse_accel_axis);
dprintf("Wheel: --- End configuration ---\n");
if (cfg->pedals_name[0] != L'\0') {
dprintf("Pedals: --- Begin configuration ---\n");
dprintf("Pedals: Device name . . . : Contains \"%S\"\n",
cfg->pedals_name);
dprintf("Pedals: Brake axis . . . . : %S\n", brake_axis->name);
dprintf("Pedals: Accel axis . . . . : %S\n", accel_axis->name);
dprintf("Pedals: --- End configuration ---\n");
}
swdc_di_off_brake = brake_axis->off;
swdc_di_off_accel = accel_axis->off;
swdc_di_start = cfg->start;
swdc_di_view_chg = cfg->view_chg;
swdc_di_paddle_left = cfg->paddle_left;
swdc_di_paddle_right = cfg->paddle_right;
swdc_di_wheel_green = cfg->wheel_green;
swdc_di_wheel_red = cfg->wheel_red;
swdc_di_wheel_blue = cfg->wheel_blue;
swdc_di_wheel_yellow = cfg->wheel_yellow;
swdc_di_reverse_brake_axis = cfg->reverse_brake_axis;
swdc_di_reverse_accel_axis = cfg->reverse_accel_axis;
/* FFB configuration */
if (cfg->ffb_constant_force_strength < 0 || cfg->ffb_constant_force_strength > 100) {
dprintf("Wheel: Invalid constant force strength: %i\n", cfg->ffb_constant_force_strength);
return E_INVALIDARG;
}
if (cfg->ffb_rumble_strength < 0 || cfg->ffb_rumble_strength > 100) {
dprintf("Wheel: Invalid rumble strength: %i\n", cfg->ffb_rumble_strength);
return E_INVALIDARG;
}
if (cfg->ffb_damper_strength < 0 || cfg->ffb_damper_strength > 100) {
dprintf("Wheel: Invalid damper strength: %i\n", cfg->ffb_damper_strength);
return E_INVALIDARG;
}
return S_OK;
}
static const struct swdc_di_axis *swdc_di_get_axis(const wchar_t *name)
{
const struct swdc_di_axis *axis;
size_t i;
for (i = 0 ; i < _countof(swdc_di_axes) ; i++) {
axis = &swdc_di_axes[i];
if (wstr_ieq(name, axis->name)) {
return axis;
}
}
return NULL;
}
static BOOL CALLBACK swdc_di_enum_callback(
const DIDEVICEINSTANCEW *dev,
void *ctx)
{
const struct swdc_di_config *cfg;
HRESULT hr;
cfg = ctx;
if (wcsstr(dev->tszProductName, cfg->device_name) == NULL) {
return DIENUM_CONTINUE;
}
dprintf("Wheel: Using DirectInput device \"%S\"\n", dev->tszProductName);
hr = IDirectInput8_CreateDevice(
swdc_di_api,
&dev->guidInstance,
&swdc_di_dev,
NULL);
if (FAILED(hr)) {
dprintf("Wheel: CreateDevice failed: %08x\n", (int) hr);
}
return DIENUM_STOP;
}
static BOOL CALLBACK swdc_di_enum_callback_pedals(
const DIDEVICEINSTANCEW *dev,
void *ctx)
{
const struct swdc_di_config *cfg;
HRESULT hr;
cfg = ctx;
if (wcsstr(dev->tszProductName, cfg->pedals_name) == NULL) {
return DIENUM_CONTINUE;
}
dprintf("Pedals: Using DirectInput device \"%S\"\n", dev->tszProductName);
hr = IDirectInput8_CreateDevice(
swdc_di_api,
&dev->guidInstance,
&swdc_di_pedals,
NULL);
if (FAILED(hr)) {
dprintf("Pedals: CreateDevice failed: %08x\n", (int) hr);
}
return DIENUM_STOP;
}
static void swdc_di_get_buttons(uint16_t *gamebtn_out)
{
union swdc_di_state state;
uint16_t gamebtn;
HRESULT hr;
assert(gamebtn_out != NULL);
hr = swdc_di_dev_poll(swdc_di_dev, swdc_di_wnd, &state);
if (FAILED(hr)) {
return;
}
gamebtn = swdc_di_decode_pov(state.st.rgdwPOV[0]);
if (swdc_di_start && state.st.rgbButtons[swdc_di_start - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_START;
}
if (swdc_di_view_chg && state.st.rgbButtons[swdc_di_view_chg - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_VIEW_CHANGE;
}
if (swdc_di_paddle_left && state.st.rgbButtons[swdc_di_paddle_left - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT;
}
if (swdc_di_paddle_right && state.st.rgbButtons[swdc_di_paddle_right - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT;
}
if (swdc_di_wheel_green && state.st.rgbButtons[swdc_di_wheel_green - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_GREEN;
}
if (swdc_di_wheel_red && state.st.rgbButtons[swdc_di_wheel_red - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_RED;
}
if (swdc_di_wheel_blue && state.st.rgbButtons[swdc_di_wheel_blue - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_BLUE;
}
if (swdc_di_wheel_yellow && state.st.rgbButtons[swdc_di_wheel_yellow - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_YELLOW;
}
*gamebtn_out = gamebtn;
}
static uint8_t swdc_di_decode_pov(DWORD pov)
{
switch (pov) {
case 0: return SWDC_IO_GAMEBTN_UP;
case 4500: return SWDC_IO_GAMEBTN_UP | SWDC_IO_GAMEBTN_RIGHT;
case 9000: return SWDC_IO_GAMEBTN_RIGHT;
case 13500: return SWDC_IO_GAMEBTN_RIGHT | SWDC_IO_GAMEBTN_DOWN;
case 18000: return SWDC_IO_GAMEBTN_DOWN;
case 22500: return SWDC_IO_GAMEBTN_DOWN | SWDC_IO_GAMEBTN_LEFT;
case 27000: return SWDC_IO_GAMEBTN_LEFT;
case 31500: return SWDC_IO_GAMEBTN_LEFT | SWDC_IO_GAMEBTN_UP;
default: return 0;
}
}
static void swdc_di_get_analogs(struct swdc_io_analog_state *out)
{
union swdc_di_state state;
union swdc_di_state pedals_state;
const LONG *brake;
const LONG *accel;
HRESULT hr;
assert(out != NULL);
hr = swdc_di_dev_poll(swdc_di_dev, swdc_di_wnd, &state);
if (FAILED(hr)) {
return;
}
if (swdc_di_use_pedals) {
hr = swdc_di_dev_poll(swdc_di_pedals, swdc_di_wnd, &pedals_state);
if (FAILED(hr)) {
return;
}
brake = (LONG *) &pedals_state.bytes[swdc_di_off_brake];
accel = (LONG *) &pedals_state.bytes[swdc_di_off_accel];
} else {
brake = (LONG *) &state.bytes[swdc_di_off_brake];
accel = (LONG *) &state.bytes[swdc_di_off_accel];
}
out->wheel = state.st.lX - 32768;
if (swdc_di_reverse_brake_axis) {
out->brake = *brake;
} else {
out->brake = 65535 - *brake;
}
if (swdc_di_reverse_accel_axis) {
out->accel = *accel;
} else {
out->accel = 65535 - *accel;
}
}

9
games/swdcio/di.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include "swdcio/backend.h"
#include "swdcio/config.h"
HRESULT swdc_di_init(
const struct swdc_di_config *cfg,
HINSTANCE inst,
const struct swdc_io_backend **backend);

191
games/swdcio/dllmain.c Normal file
View File

@ -0,0 +1,191 @@
#include <windows.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "swdcio/backend.h"
#include "swdcio/config.h"
#include "swdcio/di.h"
#include "swdcio/swdcio.h"
#include "swdcio/xi.h"
#include "util/dprintf.h"
#include "util/str.h"
#include "util/env.h"
static struct swdc_io_config swdc_io_cfg;
static const struct swdc_io_backend *swdc_io_backend;
static bool swdc_io_coin;
uint16_t swdc_io_get_api_version(void)
{
return 0x0102;
}
HRESULT swdc_io_init(void)
{
HINSTANCE inst;
HRESULT hr;
assert(swdc_io_backend == NULL);
inst = GetModuleHandleW(NULL);
if (inst == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("GetModuleHandleW failed: %lx\n", hr);
return hr;
}
swdc_io_config_load(&swdc_io_cfg, get_config_path());
if (wstr_ieq(swdc_io_cfg.mode, L"dinput")) {
hr = swdc_di_init(&swdc_io_cfg.di, inst, &swdc_io_backend);
} else if (wstr_ieq(swdc_io_cfg.mode, L"xinput")) {
hr = swdc_xi_init(&swdc_io_cfg.xi, &swdc_io_backend);
} else {
hr = E_INVALIDARG;
dprintf("swdc IO: Invalid IO mode \"%S\", use dinput or xinput\n",
swdc_io_cfg.mode);
}
return hr;
}
void swdc_io_get_opbtns(uint8_t *opbtn_out)
{
uint8_t opbtn;
assert(swdc_io_backend != NULL);
assert(opbtn_out != NULL);
opbtn = 0;
/* Common operator buttons, not backend-specific */
if (GetAsyncKeyState(swdc_io_cfg.vk_test) & 0x8000) {
opbtn |= SWDC_IO_OPBTN_TEST;
}
if (GetAsyncKeyState(swdc_io_cfg.vk_service) & 0x8000) {
opbtn |= SWDC_IO_OPBTN_SERVICE;
}
if (GetAsyncKeyState(swdc_io_cfg.vk_coin) & 0x8000) {
if (!swdc_io_coin) {
swdc_io_coin = true;
opbtn |= SWDC_IO_OPBTN_COIN;
}
} else {
swdc_io_coin = false;
}
*opbtn_out = opbtn;
}
void swdc_io_get_gamebtns(uint16_t *gamebtn_out)
{
assert(swdc_io_backend != NULL);
assert(gamebtn_out != NULL);
swdc_io_backend->get_gamebtns(gamebtn_out);
}
void swdc_io_get_analogs(struct swdc_io_analog_state *out)
{
struct swdc_io_analog_state tmp;
assert(out != NULL);
assert(swdc_io_backend != NULL);
swdc_io_backend->get_analogs(&tmp);
/* Apply steering wheel restriction. Real cabs only report about 77% of
the IO-3's max ADC output value when the wheel is turned to either of
its maximum positions. To match this behavior we set the default value
for the wheel restriction config parameter to 97 (out of 128). This
scaling factor is applied using fixed-point arithmetic below. */
out->wheel = (tmp.wheel * swdc_io_cfg.restrict_) / 128;
out->accel = tmp.accel;
out->brake = tmp.brake;
}
HRESULT swdc_io_led_init(void)
{
return S_OK;
}
void swdc_io_led_set_fet_output(uint8_t board, const uint8_t *rgb)
{
#if 0
dprintf("SWDC LED: LEFT SEAT LED: %02X\n", rgb[0]);
dprintf("SWDC LED: RIGHT SEAT LED: %02X\n", rgb[1]);
#endif
return;
}
void swdc_io_led_gs_update(uint8_t board, const uint8_t *rgb)
{
#if 0
for (int i = 0; i < 9; i++) {
dprintf("SWDC LED: LED %d: %02X %02X %02X Speed: %02X\n",
i, rgb[i * 4], rgb[i * 4 + 1], rgb[i * 4 + 2], rgb[i * 4 + 3]);
}
#endif
return;
}
void swdc_io_led_set_leds(uint8_t board, const uint8_t *rgb)
{
#if 0
dprintf("SWDC LED: START: %02X\n", rgb[0]);
dprintf("SWDC LED: VIEW CHANGE: %02X\n", rgb[1]);
dprintf("SWDC LED: UP: %02X\n", rgb[2]);
dprintf("SWDC LED: DOWN: %02X\n", rgb[3]);
dprintf("SWDC LED: RIGHT: %02X\n", rgb[4]);
dprintf("SWDC LED: LEFT: %02X\n", rgb[5]);
#endif
return;
}
HRESULT swdc_io_ffb_init(void)
{
assert(swdc_io_backend != NULL);
return swdc_io_backend->ffb_init();
}
void swdc_io_ffb_toggle(bool active)
{
assert(swdc_io_backend != NULL);
swdc_io_backend->ffb_toggle(active);
}
void swdc_io_ffb_constant_force(uint8_t direction, uint8_t force)
{
assert(swdc_io_backend != NULL);
swdc_io_backend->ffb_constant_force(direction, force);
}
void swdc_io_ffb_rumble(uint8_t period, uint8_t force)
{
assert(swdc_io_backend != NULL);
swdc_io_backend->ffb_rumble(period, force);
}
void swdc_io_ffb_damper(uint8_t force)
{
assert(swdc_io_backend != NULL);
swdc_io_backend->ffb_damper(force);
}

29
games/swdcio/meson.build Normal file
View File

@ -0,0 +1,29 @@
swdcio_lib = static_library(
'swdccio',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
dependencies : [
dinput8_lib,
dxguid_lib,
xinput_lib,
],
link_with : [
util_lib,
],
sources : [
'backend.h',
'config.c',
'config.h',
'di.c',
'di.h',
'di-dev.c',
'di-dev.h',
'dllmain.c',
'swdcio.h',
'wnd.c',
'wnd.h',
'xi.c',
'xi.h',
],
)

16
games/swdcio/swdcio.def Normal file
View File

@ -0,0 +1,16 @@
LIBRARY swdcio
EXPORTS
swdc_io_init
swdc_io_get_opbtns
swdc_io_get_gamebtns
swdc_io_get_analogs
swdc_io_led_init
swdc_io_led_set_fet_output
swdc_io_led_gs_update
swdc_io_led_set_leds
swdc_io_ffb_init
swdc_io_ffb_toggle
swdc_io_ffb_constant_force
swdc_io_ffb_rumble
swdc_io_ffb_damper

207
games/swdcio/swdcio.h Normal file
View File

@ -0,0 +1,207 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
enum {
SWDC_IO_OPBTN_TEST = 0x01,
SWDC_IO_OPBTN_SERVICE = 0x02,
SWDC_IO_OPBTN_COIN = 0x04,
};
enum {
SWDC_IO_GAMEBTN_UP = 0x01,
SWDC_IO_GAMEBTN_DOWN = 0x02,
SWDC_IO_GAMEBTN_LEFT = 0x04,
SWDC_IO_GAMEBTN_RIGHT = 0x08,
SWDC_IO_GAMEBTN_START = 0x10,
SWDC_IO_GAMEBTN_VIEW_CHANGE = 0x20,
SWDC_IO_GAMEBTN_STEERING_BLUE = 0x40,
SWDC_IO_GAMEBTN_STEERING_GREEN = 0x80,
SWDC_IO_GAMEBTN_STEERING_RED = 0x100,
SWDC_IO_GAMEBTN_STEERING_YELLOW = 0x200,
SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT = 0x400,
SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT = 0x800,
};
enum {
/* These are the bitmasks to use when checking which
lights are triggered on incoming IO4 GPIO writes. */
SWDC_IO_LED_START = 1 << 31,
SWDC_IO_LED_VIEW_CHANGE = 1 << 30,
SWDC_IO_LED_UP = 1 << 25,
SWDC_IO_LED_DOWN = 1 << 24,
SWDC_IO_LED_LEFT = 1 << 23,
SWDC_IO_LED_RIGHT = 1 << 22,
};
struct swdc_io_analog_state {
/* Current steering wheel position, where zero is the centered position.
The game will accept any signed 16-bit position value, however a real
cabinet will report a value of approximately +/- 25230 when the wheel
is at full lock. Steering wheel positions of a magnitude greater than
this value are not possible on a real cabinet. */
int16_t wheel;
/* Current position of the accelerator pedal, where 0 is released. */
uint16_t accel;
/* Current position of the brake pedal, where 0 is released. */
uint16_t brake;
};
/* Get the version of the SWDC IO API that this DLL supports. This
function should return a positive 16-bit integer, where the high byte is
the major version and the low byte is the minor version (as defined by the
Semantic Versioning standard).
The latest API version as of this writing is 0x0100. */
uint16_t swdc_io_get_api_version(void);
/* Initialize the IO DLL. This is the second function that will be called on
your DLL, after SWDC_io_get_api_version.
All subsequent calls to this API may originate from arbitrary threads.
Minimum API version: 0x0100 */
HRESULT swdc_io_init(void);
/* Get the state of the cabinet's operator buttons as of the last poll. See
SWDC_IO_OPBTN enum above: this contains bit mask definitions for button
states returned in *opbtn. All buttons are active-high.
Minimum API version: 0x0100 */
void swdc_io_get_opbtns(uint8_t *opbtn);
/* Get the state of the cabinet's gameplay buttons as of the last poll. See
SWDC_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into
a left hand side set of inputs and a right hand side set of inputs: the bit
mappings are the same in both cases.
All buttons are active-high, even though some buttons' electrical signals
on a real cabinet are active-low.
Minimum API version: 0x0100 */
void swdc_io_get_gamebtns(uint16_t *gamebtn);
/* Poll the current state of the cabinet's JVS analog inputs. See structure
definition above for details.
Minimum API version: 0x0100 */
void swdc_io_get_analogs(struct swdc_io_analog_state *out);
/* Initialize LED emulation. This function will be called before any
other swdc_io_led_*() function calls.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0101 */
HRESULT swdc_io_led_init(void);
/* Update the FET outputs. rgb is a pointer to an array up to 3 bytes.
The following bits are used to control the FET outputs:
[0]: LEFT SEAT LED
[1]: RIGHT SEAT LED
The LED is truned on when the byte is 255 and turned off when the byte is 0.
Minimum API version: 0x0101 */
void swdc_io_led_set_fet_output(uint8_t board, const uint8_t *rgb);
/* Update the RGB LEDs. rgb is a pointer to an array up to 32 * 4 = 128 bytes.
The LEDs are laid out as follows:
[0]: LEFT UP LED
[1-2]: LEFT CENTER LED
[3]: LEFT DOWN LED
[5]: RIGHT UP LED
[6-7]: RIGHT CENTER LED
[8]: RIGHT DOWN LED
Each rgb value is comprised for 4 bytes in the order of R, G, B, Speed.
Speed is a value from 0 to 255, where 0 is the fastest speed and 255 is the slowest.
Minimum API version: 0x0101 */
void swdc_io_led_gs_update(uint8_t board, const uint8_t *rgb);
/* Update the cabinet button LEDs. rgb is a pointer to an array up to 6 bytes.
The LEDs are laid out as follows:
[0]: START LED
[1]: VIEW CHANGE LED
[2]: UP LED
[3]: DOWN LED
[4]: RIGHT LED
[5]: LEFT LED
The LED is turned on when the byte is 255 and turned off when the byte is 0.
Minimum API version: 0x0101 */
void swdc_io_led_set_leds(uint8_t board, const uint8_t *rgb);
/* Initialize FFB emulation. This function will be called before any
other swdc_io_ffb_*() function calls.
This will always be called even if FFB board emulation is disabled to allow
the IO DLL to initialize any necessary resources.
Minimum API version: 0x0102 */
HRESULT swdc_io_ffb_init(void);
/* Toggle FFB emulation. If active is true, FFB emulation should be enabled.
If active is false, FFB emulation should be disabled and all FFB effects
should be stopped and released.
Minimum API version: 0x0102 */
void swdc_io_ffb_toggle(bool active);
/* Set a constant force FFB effect.
Direction is 0 for right and 1 for left.
Force is the magnitude of the force, where 0 is no force and 127 is the
maximum force in a given direction.
Minimum API version: 0x0102 */
void swdc_io_ffb_constant_force(uint8_t direction, uint8_t force);
/* Set a (sine) periodic force FFB effect.
Period is the period of the effect in milliseconds (not sure).
Force is the magnitude of the force, where 0 is no force and 127 is the
maximum force.
Minimum API version: 0x0102 */
void swdc_io_ffb_rumble(uint8_t period, uint8_t force);
/* Set a damper FFB effect.
Force is the magnitude of the force, where 0 is no force and 40 is the
maximum force. Theoretically the maximum force is 127, but the game only
uses a maximum of 40.
Minimum API version: 0x0102 */
void swdc_io_ffb_damper(uint8_t force);

86
games/swdcio/wnd.c Normal file
View File

@ -0,0 +1,86 @@
#include <windows.h>
#include <assert.h>
#include <string.h>
#include "util/dprintf.h"
/* DirectInput requires a window for correct initialization (and also force
feedback), so this source file provides some utilities for creating a
generic message-only window. */
static LRESULT WINAPI swdc_io_wnd_proc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam);
HRESULT swdc_io_wnd_create(HINSTANCE inst, HWND *out)
{
HRESULT hr;
WNDCLASSEXW wcx;
ATOM atom;
HWND hwnd;
assert(inst != NULL); /* We are not an EXE */
assert(out != NULL);
*out = NULL;
memset(&wcx, 0, sizeof(wcx));
wcx.cbSize = sizeof(wcx);
wcx.lpfnWndProc = swdc_io_wnd_proc;
wcx.hInstance = inst;
wcx.lpszClassName = L"SWDCIO";
atom = RegisterClassExW(&wcx);
if (atom == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("SWDCIO: RegisterClassExW failed: %08x\n", (int) hr);
goto fail;
}
hwnd = CreateWindowExW(
0,
(wchar_t *) (intptr_t) atom,
L"",
0,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
HWND_MESSAGE,
NULL,
inst,
NULL);
if (hwnd == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("SWDCIO: CreateWindowExW failed: %08x\n", (int) hr);
goto fail;
}
*out = hwnd;
return S_OK;
fail:
UnregisterClassW((wchar_t *) (intptr_t) atom, inst);
return hr;
}
static LRESULT WINAPI swdc_io_wnd_proc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg) {
default:
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
}

5
games/swdcio/wnd.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include <windows.h>
HRESULT swdc_io_wnd_create(HINSTANCE inst, HWND *out);

255
games/swdcio/xi.c Normal file
View File

@ -0,0 +1,255 @@
#include <windows.h>
#include <xinput.h>
#include <math.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "swdcio/backend.h"
#include "swdcio/config.h"
#include "swdcio/swdcio.h"
#include "swdcio/xi.h"
#include "util/dprintf.h"
static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out);
static void swdc_xi_get_analogs(struct swdc_io_analog_state *out);
static HRESULT swdc_xi_ffb_init(void);
static void swdc_xi_ffb_toggle(bool active);
static void swdc_xi_ffb_constant_force(uint8_t direction, uint8_t force);
static void swdc_xi_ffb_rumble(uint8_t force, uint8_t period);
static void swdc_xi_ffb_damper(uint8_t force);
static HRESULT swdc_xi_config_apply(const struct swdc_xi_config *cfg);
static const struct swdc_io_backend swdc_xi_backend = {
.get_gamebtns = swdc_xi_get_gamebtns,
.get_analogs = swdc_xi_get_analogs,
.ffb_init = swdc_xi_ffb_init,
.ffb_toggle = swdc_xi_ffb_toggle,
.ffb_constant_force = swdc_xi_ffb_constant_force,
.ffb_rumble = swdc_xi_ffb_rumble,
.ffb_damper = swdc_xi_ffb_damper
};
static bool swdc_xi_single_stick_steering;
static bool swdc_xi_linear_steering;
static uint16_t swdc_xi_left_stick_deadzone;
static uint16_t swdc_xi_right_stick_deadzone;
const uint16_t max_stick_value = 32767;
/* Apply steering wheel restriction. Real cabs only report about 76% of
the output value when the wheel is turned to either of its maximum positions. */
const uint16_t max_wheel_value = 24831;
HRESULT swdc_xi_init(const struct swdc_xi_config *cfg, const struct swdc_io_backend **backend)
{
HRESULT hr;
assert(cfg != NULL);
assert(backend != NULL);
hr = swdc_xi_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
dprintf("XInput: Using XInput controller\n");
*backend = &swdc_xi_backend;
return S_OK;
}
HRESULT swdc_io_poll(void)
{
return S_OK;
}
static HRESULT swdc_xi_config_apply(const struct swdc_xi_config *cfg) {
/* Deadzones check */
if (cfg->left_stick_deadzone > 32767 || cfg->left_stick_deadzone < 0) {
dprintf("XInput: Left stick deadzone is too large or negative\n");
return E_INVALIDARG;
}
if (cfg->right_stick_deadzone > 32767 || cfg->right_stick_deadzone < 0) {
dprintf("XInput: Right stick deadzone is too large or negative\n");
return E_INVALIDARG;
}
dprintf("XInput: --- Begin configuration ---\n");
dprintf("XInput: Single Stick Steering : %i\n", cfg->single_stick_steering);
dprintf("XInput: Linear Steering . . . : %i\n", cfg->linear_steering);
dprintf("XInput: Left Deadzone . . . . : %i\n", cfg->left_stick_deadzone);
dprintf("XInput: Right Deadzone . . . : %i\n", cfg->right_stick_deadzone);
dprintf("XInput: --- End configuration ---\n");
swdc_xi_single_stick_steering = cfg->single_stick_steering;
swdc_xi_linear_steering = cfg->linear_steering;
swdc_xi_left_stick_deadzone = cfg->left_stick_deadzone;
swdc_xi_right_stick_deadzone = cfg->right_stick_deadzone;
return S_OK;
}
static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out)
{
uint16_t gamebtn;
XINPUT_STATE xi;
WORD xb;
assert(gamebtn_out != NULL);
gamebtn = 0;
memset(&xi, 0, sizeof(xi));
XInputGetState(0, &xi);
xb = xi.Gamepad.wButtons;
if (xb & XINPUT_GAMEPAD_DPAD_UP) {
gamebtn |= SWDC_IO_GAMEBTN_UP;
}
if (xb & XINPUT_GAMEPAD_DPAD_DOWN) {
gamebtn |= SWDC_IO_GAMEBTN_DOWN;
}
if (xb & XINPUT_GAMEPAD_DPAD_LEFT) {
gamebtn |= SWDC_IO_GAMEBTN_LEFT;
}
if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) {
gamebtn |= SWDC_IO_GAMEBTN_RIGHT;
}
if (xb & XINPUT_GAMEPAD_START) {
gamebtn |= SWDC_IO_GAMEBTN_START;
}
if (xb & XINPUT_GAMEPAD_BACK) {
gamebtn |= SWDC_IO_GAMEBTN_VIEW_CHANGE;
}
if (xb & XINPUT_GAMEPAD_A) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_GREEN;
}
if (xb & XINPUT_GAMEPAD_B) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_RED;
}
if (xb & XINPUT_GAMEPAD_X) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_BLUE;
}
if (xb & XINPUT_GAMEPAD_Y) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_YELLOW;
}
if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT;
}
if (xb & XINPUT_GAMEPAD_RIGHT_SHOULDER) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT;
}
*gamebtn_out = gamebtn;
}
static int16_t calculate_norm_steering(int16_t axis, uint16_t deadzone, bool linear_steering) {
// determine how far the controller is pushed
float magnitude = sqrt(axis*axis);
// determine the direction the controller is pushed
float norm_axis = axis / magnitude;
float norm_magnitude = 0.0;
// check if the controller is outside a circular dead zone
if (magnitude > deadzone)
{
// clip the magnitude at its expected maximum value
if (magnitude > max_stick_value) magnitude = max_stick_value;
// adjust magnitude relative to the end of the dead zone
magnitude -= deadzone;
// optionally normalize the magnitude with respect to its expected range
// giving a magnitude value of 0.0 to 1.0
norm_magnitude = (int16_t)(magnitude / (max_stick_value - deadzone));
} else // if the controller is in the deadzone zero out the magnitude
{
magnitude = 0.0;
norm_magnitude = 0.0;
}
// apply non-linear transform to the axis
if (!linear_steering) {
return (int16_t)(norm_axis * powf(norm_magnitude, 3.0f) * max_wheel_value);
}
return (int16_t)(norm_axis * norm_magnitude * max_wheel_value);
}
static void swdc_xi_get_analogs(struct swdc_io_analog_state *out)
{
XINPUT_STATE xi;
int left;
int right;
assert(out != NULL);
memset(&xi, 0, sizeof(xi));
XInputGetState(0, &xi);
left = xi.Gamepad.sThumbLX;
right = xi.Gamepad.sThumbRX;
// normalize the steering axis
left = calculate_norm_steering(left, swdc_xi_left_stick_deadzone, swdc_xi_linear_steering);
right = calculate_norm_steering(right, swdc_xi_right_stick_deadzone, swdc_xi_linear_steering);
if (swdc_xi_single_stick_steering) {
out->wheel = left;
} else {
out->wheel = (left + right) / 2;
}
out->accel = xi.Gamepad.bRightTrigger << 8;
out->brake = xi.Gamepad.bLeftTrigger << 8;
}
static HRESULT swdc_xi_ffb_init(void) {
return S_OK;
}
static void swdc_xi_ffb_toggle(bool active) {
XINPUT_VIBRATION vibration;
memset(&vibration, 0, sizeof(vibration));
XInputSetState(0, &vibration);
}
static void swdc_xi_ffb_constant_force(uint8_t direction, uint8_t force) {
return;
}
static void swdc_xi_ffb_rumble(uint8_t force, uint8_t period) {
XINPUT_VIBRATION vibration;
/* XInput max strength is 65.535, so multiply the 127.0 by 516. */
uint16_t strength = force * 516;
memset(&vibration, 0, sizeof(vibration));
vibration.wLeftMotorSpeed = strength;
vibration.wRightMotorSpeed = strength;
XInputSetState(0, &vibration);
}
static void swdc_xi_ffb_damper(uint8_t force) {
return;
}

10
games/swdcio/xi.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
/* Can't call this xinput.h or it will conflict with <xinput.h> */
#include <windows.h>
#include "swdcio/backend.h"
#include "swdcio/config.h"
HRESULT swdc_xi_init(const struct swdc_xi_config *cfg, const struct swdc_io_backend **backend);