idac: add ffb emulation

This commit is contained in:
2024-09-30 18:50:46 +02:00
parent 53fb8c28ea
commit c06bb408e7
26 changed files with 930 additions and 149 deletions

View File

@ -9,4 +9,9 @@ struct idac_io_backend {
void (*get_gamebtns)(uint8_t *gamebtn);
void (*get_shifter)(uint8_t *gear);
void (*get_analogs)(struct idac_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);
};

View File

@ -80,13 +80,29 @@ void idac_di_config_load(struct idac_di_config *cfg, const wchar_t *filename)
}
// FFB configuration
cfg->ffb_constant_force_strength = GetPrivateProfileIntW(
L"dinput",
L"constantForceStrength",
100,
filename);
cfg->center_spring_strength = GetPrivateProfileIntW(
L"dinput",
L"centerSpringStrength",
30,
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 idac_xi_config_load(struct idac_xi_config *cfg, const wchar_t *filename)

View File

@ -25,7 +25,11 @@ struct idac_di_config {
bool reverse_accel_axis;
// FFB configuration
uint16_t center_spring_strength;
uint8_t ffb_constant_force_strength;
uint8_t ffb_rumble_strength;
uint8_t ffb_damper_strength;
uint32_t ffb_rumble_duration;
};
struct idac_xi_config {

View File

@ -1,134 +1,39 @@
#include <windows.h>
#include <dinput.h>
#include <stdbool.h>
#include <assert.h>
#include "idacio/di-dev.h"
#include "util/dprintf.h"
HRESULT idac_di_dev_start(IDirectInputDevice8W *dev, HWND wnd)
const struct idac_di_config *idac_di_cfg;
static HWND idac_di_wnd;
static IDirectInputDevice8W *idac_di_dev;
/* Individual DI Effects */
static IDirectInputEffect *idac_di_fx;
static IDirectInputEffect *idac_di_fx_rumble;
static IDirectInputEffect *idac_di_fx_damper;
/* Max FFB Board value is 127 */
static const double idac_di_ffb_scale = 127.0;
HRESULT idac_di_dev_init(
const struct idac_di_config *cfg,
IDirectInputDevice8W *dev,
HWND wnd)
{
HRESULT hr;
assert(dev != NULL);
assert(wnd != NULL);
hr = IDirectInputDevice8_SetCooperativeLevel(
dev,
wnd,
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
idac_di_cfg = cfg;
idac_di_dev = dev;
idac_di_wnd = wnd;
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;
}
void idac_di_dev_start_fx(
IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength)
{
/* Set up force-feedback on devices that support it. This is just a stub
for the time being, since we don't yet know how the serial port force
feedback protocol works.
I'm currently developing with an Xbox One Thrustmaster TMX wheel, if
we don't perform at least some perfunctory FFB initialization of this
nature (or indeed if no DirectInput application is running) then the
wheel exhibits considerable resistance, similar to that of a stationary
car. Changing cf.lMagnitude to a nonzero value does cause the wheel to
continuously turn in the given direction with the given force as one
would expect (max magnitude per DirectInput docs is +/- 10000).
Failure here is non-fatal, we log any errors and move on.
https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416353(v=vs.85)
*/
IDirectInputEffect *obj;
DWORD axis;
LONG direction;
DIEFFECT fx;
DICONDITION cond;
HRESULT hr;
assert(dev != NULL);
assert(out != NULL);
*out = NULL;
dprintf("DirectInput: Starting force feedback (may take a sec)\n");
// Auto-centering effect
axis = DIJOFS_X;
direction = 0;
memset(&cond, 0, sizeof(cond));
cond.lOffset = 0;
cond.lPositiveCoefficient = strength;
cond.lNegativeCoefficient = strength;
cond.dwPositiveSaturation = strength; // For FG920?
cond.dwNegativeSaturation = strength; // For FG920?
cond.lDeadBand = 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;
hr = IDirectInputDevice8_CreateEffect(
dev,
&GUID_Spring,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: Centering spring force feedback unavailable: %08x\n",
(int) hr);
return;
}
hr = IDirectInputEffect_Start(obj, INFINITE, 0);
if (FAILED(hr)) {
IDirectInputEffect_Release(obj);
dprintf("DirectInput: Centering spring force feedback start failed: %08x\n",
(int) hr);
return;
}
*out = obj;
dprintf("DirectInput: Centering spring effects initialized with strength %d%%\n",
strength / 100);
return S_OK;
}
HRESULT idac_di_dev_poll(
@ -167,3 +72,312 @@ HRESULT idac_di_dev_poll(
return hr;
}
HRESULT idac_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 idac_di_ffb_init(void)
{
HRESULT hr;
hr = idac_di_dev_start(idac_di_dev, idac_di_wnd);
if (FAILED(hr)) {
return hr;
}
return S_OK;
}
void idac_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 (idac_di_fx != NULL) {
IDirectInputEffect_Stop(idac_di_fx);
IDirectInputEffect_Release(idac_di_fx);
idac_di_fx = NULL;
}
if (idac_di_fx_rumble != NULL) {
IDirectInputEffect_Stop(idac_di_fx_rumble);
IDirectInputEffect_Release(idac_di_fx_rumble);
idac_di_fx_rumble = NULL;
}
if (idac_di_fx_damper != NULL) {
IDirectInputEffect_Stop(idac_di_fx_damper);
IDirectInputEffect_Release(idac_di_fx_damper);
idac_di_fx_damper = NULL;
}
}
void idac_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 = idac_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 / idac_di_ffb_scale) * ffb_strength);
cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude;
axis = DIJOFS_X;
/* Irrelevant as magnitude descripbes the direction */
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;
if (idac_di_fx != NULL) {
// Try to update the existing effect
hr = IDirectInputEffect_SetParameters(idac_di_fx, &fx, DIEP_TYPESPECIFICPARAMS);
if (SUCCEEDED(hr)) {
return;
} else {
dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr);
// Stop and release the current effect if updating fails
IDirectInputEffect_Stop(idac_di_fx);
IDirectInputEffect_Release(idac_di_fx);
idac_di_fx = NULL;
}
}
// Create a new constant force effect
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
idac_di_dev,
&GUID_ConstantForce,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int) hr);
return;
}
hr = IDirectInputEffect_Start(obj, INFINITE, 0);
if (FAILED(hr)) {
dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int) hr);
IDirectInputEffect_Release(obj);
return;
}
idac_di_fx = obj;
}
void idac_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 = idac_di_cfg->ffb_rumble_strength * 100;
if (ffb_strength == 0) {
return;
}
uint32_t ffb_duration = idac_di_cfg->ffb_rumble_duration;
DWORD axis;
LONG direction;
DIEFFECT fx;
DIPERIODIC pe;
HRESULT hr;
/* Duration in microseconds,
Might be totally wrong as especially on FANATEC wheels as this code will
crash the game. TODO: Figure out why this effect will crash on FANATEC! */
DWORD duration = (DWORD)((double)force * ffb_duration);
memset(&pe, 0, sizeof(pe));
pe.dwMagnitude = (DWORD)(((double)force / idac_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 = duration;
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;
if (idac_di_fx_rumble != NULL) {
// Try to update the existing effect
hr = IDirectInputEffect_SetParameters(idac_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS);
if (SUCCEEDED(hr)) {
return;
} else {
dprintf("DirectInput: Failed to update periodic force feedback, recreating effect: %08x\n", (int)hr);
// Stop and release the current effect if updating fails
IDirectInputEffect_Stop(idac_di_fx_rumble);
IDirectInputEffect_Release(idac_di_fx_rumble);
idac_di_fx_rumble = NULL;
}
}
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
idac_di_dev,
&GUID_Sine,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: Periodic force feedback creation failed: %08x\n", (int) hr);
return;
}
hr = IDirectInputEffect_Start(obj, INFINITE, 0);
if (FAILED(hr)) {
dprintf("DirectInput: Periodic force feedback start failed: %08x\n", (int) hr);
IDirectInputEffect_Release(obj);
return;
}
idac_di_fx_rumble = obj;
}
void idac_di_ffb_damper(uint8_t force)
{
/* DI expects a coefficient in the range of -10.000 to 10.000 */
uint16_t ffb_strength = idac_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 / idac_di_ffb_scale) * ffb_strength);
cond.lNegativeCoefficient = (LONG)(((double)force / idac_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;
if (idac_di_fx_damper != NULL) {
// Try to update the existing effect
hr = IDirectInputEffect_SetParameters(idac_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS);
if (SUCCEEDED(hr)) {
return;
} else {
dprintf("DirectInput: Failed to update damper force feedback, recreating effect: %08x\n", (int)hr);
// Stop and release the current effect if updating fails
IDirectInputEffect_Stop(idac_di_fx_damper);
IDirectInputEffect_Release(idac_di_fx_damper);
idac_di_fx_damper = NULL;
}
}
// Create a new damper force effect
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
idac_di_dev,
&GUID_Damper,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: Damper force feedback creation failed: %08x\n", (int) hr);
return;
}
hr = IDirectInputEffect_Start(obj, INFINITE, 0);
if (FAILED(hr)) {
dprintf("DirectInput: Damper force feedback start failed: %08x\n", (int) hr);
IDirectInputEffect_Release(obj);
return;
}
idac_di_fx_damper = obj;
}

View File

@ -5,15 +5,26 @@
#include <stdint.h>
#include "idacio/config.h"
union idac_di_state {
DIJOYSTATE st;
uint8_t bytes[sizeof(DIJOYSTATE)];
};
HRESULT idac_di_dev_init(
const struct idac_di_config *cfg,
IDirectInputDevice8W *dev,
HWND wnd);
HRESULT idac_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
void idac_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength);
HRESULT idac_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,
union idac_di_state *out);
HRESULT idac_di_ffb_init(void);
void idac_di_ffb_toggle(bool active);
void idac_di_ffb_constant_force(uint8_t direction, uint8_t force);
void idac_di_ffb_rumble(uint8_t force, uint8_t period);
void idac_di_ffb_damper(uint8_t force);

View File

@ -52,9 +52,14 @@ static const struct idac_di_axis idac_di_axes[] = {
};
static const struct idac_io_backend idac_di_backend = {
.get_gamebtns = idac_di_get_buttons,
.get_shifter = idac_di_get_shifter,
.get_analogs = idac_di_get_analogs,
.get_gamebtns = idac_di_get_buttons,
.get_shifter = idac_di_get_shifter,
.get_analogs = idac_di_get_analogs,
.ffb_init = idac_di_ffb_init,
.ffb_toggle = idac_di_ffb_toggle,
.ffb_constant_force = idac_di_ffb_constant_force,
.ffb_rumble = idac_di_ffb_rumble,
.ffb_damper = idac_di_ffb_damper
};
static HWND idac_di_wnd;
@ -62,7 +67,6 @@ static IDirectInput8W *idac_di_api;
static IDirectInputDevice8W *idac_di_dev;
static IDirectInputDevice8W *idac_di_pedals;
static IDirectInputDevice8W *idac_di_shifter;
static IDirectInputEffect *idac_di_fx;
static size_t idac_di_off_brake;
static size_t idac_di_off_accel;
static uint8_t idac_di_shift_dn;
@ -105,7 +109,7 @@ HRESULT idac_di_init(
return hr;
}
/* Initial D Zero has some built-in DirectInput support that is not
/* Initial D THE ARCADE has some built-in DirectInput support that is not
particularly useful. idachook shorts this out by redirecting dinput8.dll
to a no-op implementation of DirectInput. However, idacio does need to
talk to the real operating system implementation of DirectInput without
@ -168,16 +172,12 @@ HRESULT idac_di_init(
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = idac_di_dev_start(idac_di_dev, idac_di_wnd);
hr = idac_di_dev_init(cfg, idac_di_dev, idac_di_wnd);
if (FAILED(hr)) {
return hr;
}
// Convert the strength from 0-100 to 0-10000 for DirectInput
idac_di_dev_start_fx(idac_di_dev, &idac_di_fx,
idac_di_center_spring_strength * 100);
if (cfg->pedals_name[0] != L'\0') {
hr = IDirectInput8_EnumDevices(
idac_di_api,
@ -367,15 +367,24 @@ static HRESULT idac_di_config_apply(const struct idac_di_config *cfg)
idac_di_gear[i] = cfg->gear[i];
}
// FFB configuration
/* 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);
if (cfg->center_spring_strength < 0 || cfg->center_spring_strength > 100) {
dprintf("Wheel: Invalid center spring strength: %i\n", cfg->center_spring_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;
}
idac_di_center_spring_strength = cfg->center_spring_strength;
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;
}

View File

@ -19,7 +19,7 @@ static bool idac_io_coin;
uint16_t idac_io_get_api_version(void)
{
return 0x0101;
return 0x0102;
}
HRESULT idac_io_init(void)
@ -62,6 +62,8 @@ void idac_io_get_opbtns(uint8_t *opbtn_out)
opbtn = 0;
/* Common operator buttons, not backend-specific */
if (GetAsyncKeyState(idac_io_cfg.vk_test) & 0x8000) {
opbtn |= IDAC_IO_OPBTN_TEST;
}
@ -159,3 +161,38 @@ void idac_io_led_set_leds(const uint8_t *rgb)
return;
}
HRESULT idac_io_ffb_init(void)
{
assert(idac_io_backend != NULL);
return idac_io_backend->ffb_init();
}
void idac_io_ffb_toggle(bool active)
{
assert(idac_io_backend != NULL);
idac_io_backend->ffb_toggle(active);
}
void idac_io_ffb_constant_force(uint8_t direction, uint8_t force)
{
assert(idac_io_backend != NULL);
idac_io_backend->ffb_constant_force(direction, force);
}
void idac_io_ffb_rumble(uint8_t period, uint8_t force)
{
assert(idac_io_backend != NULL);
idac_io_backend->ffb_rumble(period, force);
}
void idac_io_ffb_damper(uint8_t force)
{
assert(idac_io_backend != NULL);
idac_io_backend->ffb_damper(force);
}

View File

@ -10,3 +10,8 @@ EXPORTS
idac_io_led_set_fet_output
idac_io_led_gs_update
idac_io_led_set_leds
idac_io_ffb_init
idac_io_ffb_toggle
idac_io_ffb_constant_force
idac_io_ffb_rumble
idac_io_ffb_damper

View File

@ -2,6 +2,7 @@
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
enum {
@ -160,3 +161,51 @@ void idac_io_led_gs_update(const uint8_t *rgb);
Minimum API version: 0x0101 */
void idac_io_led_set_leds(const uint8_t *rgb);
/* Initialize FFB emulation. This function will be called before any
other idac_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 idac_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 idac_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 idac_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 idac_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 idac_io_ffb_damper(uint8_t force);

View File

@ -1,5 +1,3 @@
#include "idacio/xi.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
@ -7,22 +5,35 @@
#include <windows.h>
#include <xinput.h>
#include "idacio/xi.h"
#include "idacio/backend.h"
#include "idacio/config.h"
#include "idacio/idacio.h"
#include "idacio/shifter.h"
#include "util/dprintf.h"
static void idac_xi_get_gamebtns(uint8_t *gamebtn_out);
static void idac_xi_get_shifter(uint8_t *gear);
static void idac_xi_get_analogs(struct idac_io_analog_state *out);
static HRESULT idac_xi_ffb_init(void);
static void idac_xi_ffb_toggle(bool active);
static void idac_xi_ffb_constant_force(uint8_t direction, uint8_t force);
static void idac_xi_ffb_rumble(uint8_t force, uint8_t period);
static void idac_xi_ffb_damper(uint8_t force);
static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg);
static const struct idac_io_backend idac_xi_backend = {
.get_gamebtns = idac_xi_get_gamebtns,
.get_shifter = idac_xi_get_shifter,
.get_analogs = idac_xi_get_analogs,
.get_gamebtns = idac_xi_get_gamebtns,
.get_shifter = idac_xi_get_shifter,
.get_analogs = idac_xi_get_analogs,
.ffb_init = idac_xi_ffb_init,
.ffb_toggle = idac_xi_ffb_toggle,
.ffb_constant_force = idac_xi_ffb_constant_force,
.ffb_rumble = idac_xi_ffb_rumble,
.ffb_damper = idac_xi_ffb_damper
};
static bool idac_xi_single_stick_steering;
@ -46,7 +57,7 @@ HRESULT idac_xi_init(const struct idac_xi_config *cfg, const struct idac_io_back
return hr;
}
dprintf("XInput: Using XInput controller\n");
dprintf("IDACIO: Using XInput controller\n");
*backend = &idac_xi_backend;
return S_OK;
@ -205,3 +216,35 @@ static void idac_xi_get_analogs(struct idac_io_analog_state *out) {
out->accel = xi.Gamepad.bRightTrigger << 8;
out->brake = xi.Gamepad.bLeftTrigger << 8;
}
static HRESULT idac_xi_ffb_init(void) {
return S_OK;
}
static void idac_xi_ffb_toggle(bool active) {
XINPUT_VIBRATION vibration;
memset(&vibration, 0, sizeof(vibration));
XInputSetState(0, &vibration);
}
static void idac_xi_ffb_constant_force(uint8_t direction, uint8_t force) {
return;
}
static void idac_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 idac_xi_ffb_damper(uint8_t force) {
return;
}