forked from Hay1tsme/segatools
refactor all common parts and games
This commit is contained in:
16
games/swdcio/backend.h
Normal file
16
games/swdcio/backend.h
Normal 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
147
games/swdcio/config.c
Normal 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
50
games/swdcio/config.h
Normal 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
371
games/swdcio/di-dev.c
Normal 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
30
games/swdcio/di-dev.h
Normal 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
528
games/swdcio/di.c
Normal 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
9
games/swdcio/di.h
Normal 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
191
games/swdcio/dllmain.c
Normal 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
29
games/swdcio/meson.build
Normal 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
16
games/swdcio/swdcio.def
Normal 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
207
games/swdcio/swdcio.h
Normal 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
86
games/swdcio/wnd.c
Normal 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
5
games/swdcio/wnd.h
Normal 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
255
games/swdcio/xi.c
Normal 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
10
games/swdcio/xi.h
Normal 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);
|
Reference in New Issue
Block a user