2023-07-13 22:52:50 +00:00
|
|
|
#include <windows.h>
|
|
|
|
#include <dinput.h>
|
2024-09-30 18:23:28 +00:00
|
|
|
#include <stdbool.h>
|
2023-07-13 22:52:50 +00:00
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include "swdcio/di-dev.h"
|
|
|
|
|
|
|
|
#include "util/dprintf.h"
|
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
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(
|
2024-10-29 21:06:07 +00:00
|
|
|
IDirectInputDevice8W *dev,
|
|
|
|
HWND wnd,
|
|
|
|
union swdc_di_state *out)
|
2023-07-13 22:52:50 +00:00
|
|
|
{
|
|
|
|
HRESULT hr;
|
2024-09-30 18:23:28 +00:00
|
|
|
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(
|
2024-10-29 21:06:07 +00:00
|
|
|
dev,
|
|
|
|
sizeof(out->st),
|
|
|
|
&out->st);
|
2024-09-30 18:23:28 +00:00
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
2024-10-29 21:06:07 +00:00
|
|
|
dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr);
|
2024-09-30 18:23:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) {
|
|
|
|
HRESULT hr;
|
2023-07-13 22:52:50 +00:00
|
|
|
|
|
|
|
assert(dev != NULL);
|
|
|
|
assert(wnd != NULL);
|
|
|
|
|
|
|
|
hr = IDirectInputDevice8_SetCooperativeLevel(
|
2024-10-29 21:06:07 +00:00
|
|
|
dev,
|
|
|
|
wnd,
|
|
|
|
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
|
2023-07-13 22:52:50 +00:00
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
2024-10-29 21:06:07 +00:00
|
|
|
dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr);
|
2023-07-13 22:52:50 +00:00
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
|
|
|
hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick);
|
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
2024-10-29 21:06:07 +00:00
|
|
|
dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr);
|
2023-07-13 22:52:50 +00:00
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
|
|
|
hr = IDirectInputDevice8_Acquire(dev);
|
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
2024-10-29 21:06:07 +00:00
|
|
|
dprintf("DirectInput: Acquire failed: %08x\n", (int)hr);
|
2023-07-13 22:52:50 +00:00
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
HRESULT swdc_di_ffb_init(void)
|
2023-07-13 22:52:50 +00:00
|
|
|
{
|
2024-09-30 18:23:28 +00:00
|
|
|
HRESULT hr;
|
2023-07-13 22:52:50 +00:00
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
hr = swdc_di_dev_start(swdc_di_dev, swdc_di_wnd);
|
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
return hr;
|
|
|
|
}
|
2023-07-13 22:52:50 +00:00
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
return S_OK;
|
|
|
|
}
|
2023-07-13 22:52:50 +00:00
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
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;
|
|
|
|
}
|
2023-07-13 22:52:50 +00:00
|
|
|
|
|
|
|
DWORD axis;
|
|
|
|
LONG direction;
|
|
|
|
DIEFFECT fx;
|
2024-09-30 18:23:28 +00:00
|
|
|
DICONSTANTFORCE cf;
|
2023-07-13 22:52:50 +00:00
|
|
|
HRESULT hr;
|
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
/* 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;
|
2023-07-13 22:52:50 +00:00
|
|
|
|
|
|
|
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;
|
2024-09-30 18:23:28 +00:00
|
|
|
fx.cbTypeSpecificParams = sizeof(cf);
|
|
|
|
fx.lpvTypeSpecificParams = &cf;
|
|
|
|
|
2024-10-29 21:06:07 +00:00
|
|
|
/* Check if the effect already exists */
|
2024-09-30 18:23:28 +00:00
|
|
|
if (swdc_di_fx != NULL) {
|
|
|
|
hr = IDirectInputEffect_SetParameters(swdc_di_fx, &fx, DIEP_TYPESPECIFICPARAMS);
|
|
|
|
if (SUCCEEDED(hr)) {
|
2024-10-29 21:06:07 +00:00
|
|
|
return; // Successfully updated existing effect
|
|
|
|
}
|
|
|
|
else {
|
2024-09-30 18:23:28 +00:00
|
|
|
dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr);
|
|
|
|
IDirectInputEffect_Stop(swdc_di_fx);
|
|
|
|
IDirectInputEffect_Release(swdc_di_fx);
|
2024-10-29 21:06:07 +00:00
|
|
|
swdc_di_fx = NULL; // Reset the pointer
|
2024-09-30 18:23:28 +00:00
|
|
|
}
|
|
|
|
}
|
2023-07-13 22:52:50 +00:00
|
|
|
|
2024-10-29 21:06:07 +00:00
|
|
|
/* Create a new constant force effect */
|
2024-09-30 18:23:28 +00:00
|
|
|
IDirectInputEffect *obj;
|
2023-07-13 22:52:50 +00:00
|
|
|
hr = IDirectInputDevice8_CreateEffect(
|
2024-10-29 21:06:07 +00:00
|
|
|
swdc_di_dev,
|
|
|
|
&GUID_ConstantForce,
|
|
|
|
&fx,
|
|
|
|
&obj,
|
|
|
|
NULL);
|
2023-07-13 22:52:50 +00:00
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
2024-10-29 21:06:07 +00:00
|
|
|
dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int)hr);
|
2023-07-13 22:52:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-10-29 21:06:07 +00:00
|
|
|
/* Start the effect */
|
2023-07-13 22:52:50 +00:00
|
|
|
hr = IDirectInputEffect_Start(obj, INFINITE, 0);
|
|
|
|
if (FAILED(hr)) {
|
2024-10-29 21:06:07 +00:00
|
|
|
dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int)hr);
|
2023-07-13 22:52:50 +00:00
|
|
|
IDirectInputEffect_Release(obj);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
swdc_di_fx = obj;
|
|
|
|
}
|
2023-07-13 22:52:50 +00:00
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
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;
|
2024-10-29 21:06:07 +00:00
|
|
|
fx.dwDuration = INFINITE;
|
2024-09-30 18:23:28 +00:00
|
|
|
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;
|
|
|
|
|
2024-10-29 21:06:07 +00:00
|
|
|
/* Check if the effect already exists */
|
2024-09-30 18:23:28 +00:00
|
|
|
if (swdc_di_fx_rumble != NULL) {
|
|
|
|
hr = IDirectInputEffect_SetParameters(swdc_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
return;
|
2024-10-29 21:06:07 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
dprintf("DirectInput: Failed to update rumble feedback, recreating effect: %08x\n", (int)hr);
|
2024-09-30 18:23:28 +00:00
|
|
|
IDirectInputEffect_Stop(swdc_di_fx_rumble);
|
|
|
|
IDirectInputEffect_Release(swdc_di_fx_rumble);
|
|
|
|
swdc_di_fx_rumble = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-29 21:06:07 +00:00
|
|
|
/* Create a new rumble effect */
|
2024-09-30 18:23:28 +00:00
|
|
|
IDirectInputEffect *obj;
|
|
|
|
hr = IDirectInputDevice8_CreateEffect(
|
2024-10-29 21:06:07 +00:00
|
|
|
swdc_di_dev,
|
|
|
|
&GUID_Sine,
|
|
|
|
&fx,
|
|
|
|
&obj,
|
|
|
|
NULL);
|
2024-09-30 18:23:28 +00:00
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
2024-10-29 21:06:07 +00:00
|
|
|
dprintf("DirectInput: Rumble effect creation failed: %08x\n", (int)hr);
|
2024-09-30 18:23:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-10-29 21:06:07 +00:00
|
|
|
/* Start the effect */
|
2024-09-30 18:23:28 +00:00
|
|
|
hr = IDirectInputEffect_Start(obj, INFINITE, 0);
|
|
|
|
if (FAILED(hr)) {
|
2024-10-29 21:06:07 +00:00
|
|
|
dprintf("DirectInput: Rumble effect start failed: %08x\n", (int)hr);
|
2024-09-30 18:23:28 +00:00
|
|
|
IDirectInputEffect_Release(obj);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
swdc_di_fx_rumble = obj;
|
2023-07-13 22:52:50 +00:00
|
|
|
}
|
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
void swdc_di_ffb_damper(uint8_t force)
|
2023-07-13 22:52:50 +00:00
|
|
|
{
|
2024-09-30 18:23:28 +00:00
|
|
|
/* 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;
|
2023-07-13 22:52:50 +00:00
|
|
|
HRESULT hr;
|
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
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;
|
2023-07-13 22:52:50 +00:00
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
axis = DIJOFS_X;
|
|
|
|
direction = 0;
|
2023-07-13 22:52:50 +00:00
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
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;
|
2023-07-13 22:52:50 +00:00
|
|
|
|
2024-10-29 21:06:07 +00:00
|
|
|
/* Check if the damper effect already exists */
|
2024-09-30 18:23:28 +00:00
|
|
|
if (swdc_di_fx_damper != NULL) {
|
|
|
|
hr = IDirectInputEffect_SetParameters(swdc_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
return;
|
2024-10-29 21:06:07 +00:00
|
|
|
}
|
|
|
|
else {
|
2024-09-30 18:23:28 +00:00
|
|
|
IDirectInputEffect_Stop(swdc_di_fx_damper);
|
|
|
|
IDirectInputEffect_Release(swdc_di_fx_damper);
|
|
|
|
swdc_di_fx_damper = NULL;
|
|
|
|
}
|
2023-07-13 22:52:50 +00:00
|
|
|
}
|
|
|
|
|
2024-10-29 21:06:07 +00:00
|
|
|
/* Create a new damper effect */
|
2024-09-30 18:23:28 +00:00
|
|
|
IDirectInputEffect *obj;
|
|
|
|
hr = IDirectInputDevice8_CreateEffect(
|
2024-10-29 21:06:07 +00:00
|
|
|
swdc_di_dev,
|
|
|
|
&GUID_Damper,
|
|
|
|
&fx,
|
|
|
|
&obj,
|
|
|
|
NULL);
|
2023-07-13 22:52:50 +00:00
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
2024-09-30 18:23:28 +00:00
|
|
|
return;
|
2023-07-13 22:52:50 +00:00
|
|
|
}
|
|
|
|
|
2024-10-29 21:06:07 +00:00
|
|
|
/* Start the effect */
|
|
|
|
hr = IDirectInputEffect_Start(obj, fx.dwDuration, 0);
|
2024-09-30 18:23:28 +00:00
|
|
|
if (FAILED(hr)) {
|
|
|
|
IDirectInputEffect_Release(obj);
|
|
|
|
return;
|
|
|
|
}
|
2023-07-13 22:52:50 +00:00
|
|
|
|
2024-09-30 18:23:28 +00:00
|
|
|
swdc_di_fx_damper = obj;
|
2023-07-13 22:52:50 +00:00
|
|
|
}
|