forked from TeamTofuShop/segatools
idac: add ffb emulation
This commit is contained in:
446
idacio/di-dev.c
446
idacio/di-dev.c
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user