Merge branch 'feature/ffb' into develop

This commit is contained in:
Dniel97 2024-09-30 23:20:00 +02:00
commit 36849bd09a
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
72 changed files with 2700 additions and 455 deletions

View File

@ -1,6 +1,6 @@
# Segatools
Version: `2024-08-20`
Version: `2024-09-30`
Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platforms.

View File

@ -93,3 +93,11 @@ void vfd_config_load(struct vfd_config *cfg, const wchar_t *filename)
cfg->port = GetPrivateProfileIntW(L"vfd", L"portNo", 0, filename);
cfg->utf_conversion = GetPrivateProfileIntW(L"vfd", L"utfConversion", 0, filename);
}
void ffb_config_load(struct ffb_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"ffb", L"enable", 1, filename);
}

View File

@ -6,7 +6,9 @@
#include "board/io4.h"
#include "board/sg-reader.h"
#include "board/vfd.h"
#include "board/ffb.h"
void aime_config_load(struct aime_config *cfg, const wchar_t *filename);
void io4_config_load(struct io4_config *cfg, const wchar_t *filename);
void vfd_config_load(struct vfd_config *cfg, const wchar_t *filename);
void ffb_config_load(struct ffb_config *cfg, const wchar_t *filename);

235
board/ffb.c Normal file
View File

@ -0,0 +1,235 @@
/*
Force Feedback Board (FFB)
This board is used by many SEGA games to provide force feedback to the player.
It is driven by the game software over a serial connection and is used by many
games such as SEGA World Drivers Championship, Initial D Arcade, ...
Part number in schematics is "838-15069 MOTOR DRIVE BD RS232/422 Board".
Some observations:
The maximal strength for any effect is 127, except Damper which maxes out at 40.
The period for rumble effects is in the range 0-40.
*/
#include "board/ffb.h"
#include <assert.h>
#include <stdint.h>
#include <windows.h>
#include "hook/iohook.h"
#include "hooklib/uart.h"
#include "util/dprintf.h"
#include "util/dump.h"
// request format:
// 0x?? - sync + command
// 0x?? - direction/additional command
// 0x?? - strength
// 0x?? - checksum (sum of everything except the sync byte)
enum {
FFB_CMD_TOGGLE = 0x80,
FFB_CMD_CONSTANT_FORCE = 0x84,
FFB_CMD_RUMBLE = 0x85,
FFB_CMD_DAMPER = 0x86,
};
struct ffb_hdr {
uint8_t cmd;
};
union ffb_req_any {
struct ffb_hdr hdr;
uint8_t bytes[3];
};
static HRESULT ffb_handle_irp(struct irp *irp);
static HRESULT ffb_req_dispatch(const union ffb_req_any *req);
static HRESULT ffb_req_toggle(const uint8_t *bytes);
static HRESULT ffb_req_constant_force(const uint8_t *bytes);
static HRESULT ffb_req_rumble(const uint8_t *bytes);
static HRESULT ffb_req_damper(const uint8_t *bytes);
static const struct ffb_ops *ffb_ops;
static struct uart ffb_uart;
static bool ffb_started;
static HRESULT ffb_start_hr;
static uint8_t ffb_written[4];
static uint8_t ffb_readable[4];
/* Static variables to store maximum strength values */
static uint8_t max_constant_force = 0;
static uint8_t max_rumble = 0;
static uint8_t max_period = 0;
static uint8_t max_damper = 0;
HRESULT ffb_hook_init(
const struct ffb_config *cfg,
const struct ffb_ops *ops,
unsigned int port_no)
{
assert(cfg != NULL);
assert(ops != NULL);
if (!cfg->enable) {
return S_FALSE;
}
ffb_ops = ops;
uart_init(&ffb_uart, port_no);
ffb_uart.written.bytes = ffb_written;
ffb_uart.written.nbytes = sizeof(ffb_written);
ffb_uart.readable.bytes = ffb_readable;
ffb_uart.readable.nbytes = sizeof(ffb_readable);
dprintf("FFB: hook enabled.\n");
return iohook_push_handler(ffb_handle_irp);
}
static HRESULT ffb_handle_irp(struct irp *irp)
{
HRESULT hr;
assert(irp != NULL);
if (!uart_match_irp(&ffb_uart, irp)) {
return iohook_invoke_next(irp);
}
hr = uart_handle_irp(&ffb_uart, irp);
if (FAILED(hr) || irp->op != IRP_OP_WRITE) {
return hr;
}
assert(&ffb_uart.written != NULL);
assert(ffb_uart.written.bytes != NULL || ffb_uart.written.nbytes == 0);
assert(ffb_uart.written.pos <= ffb_uart.written.nbytes);
// dprintf("FFB TX:\n");
hr = ffb_req_dispatch((const union ffb_req_any *) ffb_uart.written.bytes);
if (FAILED(hr)) {
dprintf("FFB: Processing error: %x\n", (int)hr);
}
// dump_iobuf(&ffb_uart.written);
ffb_uart.written.pos = 0;
return hr;
}
static HRESULT ffb_req_dispatch(const union ffb_req_any *req)
{
switch (req->hdr.cmd) {
case FFB_CMD_TOGGLE:
return ffb_req_toggle(req->bytes);
case FFB_CMD_CONSTANT_FORCE:
return ffb_req_constant_force(req->bytes);
case FFB_CMD_RUMBLE:
return ffb_req_rumble(req->bytes);
case FFB_CMD_DAMPER:
return ffb_req_damper(req->bytes);
/* There are some test mode specfic commands which doesn't seem to be used in
game at all. The same is true for the initialization phase. */
default:
dprintf("FFB: Unhandled command %02x\n", req->hdr.cmd);
return S_OK;
}
}
static HRESULT ffb_req_toggle(const uint8_t *bytes)
{
uint8_t activate = bytes[2];
if (activate == 0x01) {
dprintf("FFB: Activated\n");
} else {
dprintf("FFB: Deactivated\n");
}
if (ffb_ops->toggle != NULL) {
ffb_ops->toggle(activate == 0x01);
}
return S_OK;
}
static HRESULT ffb_req_constant_force(const uint8_t *bytes)
{
// dprintf("FFB: Constant force\n");
uint8_t direction = bytes[1];
uint8_t force = bytes[2];
if (direction == 0x0) {
// Right
force = 128 - force;
}
// Update max strength if the current force is greater
if (force > max_constant_force) {
max_constant_force = force;
}
// dprintf("FFB: Constant Force Strength: %d (Max: %d)\n", force, max_constant_force);
if (ffb_ops->constant_force != NULL) {
ffb_ops->constant_force(direction, force);
}
return S_OK;
}
static HRESULT ffb_req_rumble(const uint8_t *bytes)
{
// dprintf("FFB: Rumble\n");
uint8_t force = bytes[1];
uint8_t period = bytes[2];
// Update max strength if the current force is greater
if (force > max_rumble) {
max_rumble = force;
}
if (period > max_period) {
max_period = period;
}
// dprintf("FFB: Rumble Period: %d (Max %d), Strength: %d (Max: %d)\n", period, max_period, force, max_rumble);
if (ffb_ops->rumble != NULL) {
ffb_ops->rumble(force, period);
}
return S_OK;
}
static HRESULT ffb_req_damper(const uint8_t *bytes)
{
// dprintf("FFB: Damper\n");
uint8_t force = bytes[2];
// Update max strength if the current force is greater
if (force > max_damper) {
max_damper = force;
}
// dprintf("FFB: Damper Strength: %d (Max: %d)\n", force, max_damper);
if (ffb_ops->damper != NULL) {
ffb_ops->damper(force);
}
return S_OK;
}

20
board/ffb.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
struct ffb_config {
bool enable;
};
struct ffb_ops {
void (*toggle)(bool active);
void (*constant_force)(uint8_t direction, uint8_t force);
void (*rumble)(uint8_t force, uint8_t period);
void (*damper)(uint8_t force);
};
HRESULT ffb_hook_init(
const struct ffb_config *cfg,
const struct ffb_ops *ops,
unsigned int port_no);

View File

@ -50,5 +50,7 @@ board_lib = static_library(
'vfd-cmd.h',
'vfd-frame.c',
'vfd-frame.h',
'ffb.c',
'ffb.h'
],
)

View File

@ -75,6 +75,11 @@ dipsw3=0
dipsw4=0
dipsw5=0
[ffb]
; Enable force feedback (838-15069) board emulation. This is required for
; both DirectInput and XInput steering wheel effects.
enable=1
; -----------------------------------------------------------------------------
; LED settings
; -----------------------------------------------------------------------------
@ -231,6 +236,21 @@ reverseAccelAxis=0
reverseBrakeAxis=0
; Force feedback settings.
; Strength of the force feedback spring effect in percent. Possible values
; are 0-100.
centerSpringStrength=30
; Only works when FFB board emulation is enabled!
;
; It is recommended to change the strength inside the Game Test Mode!
;
; These settings are only used when using DirectInput for the wheel.
; The values are in the range 0%-100%, where 0 disables the effect and
; 100 is the maximum.
; Constant force strength, used for centering spring effect.
constantForceStrength=100
; Damper strength, used for steering wheel damper effect.
damperStrength=100
; Rumble strength, used for road surface effects.
; WARNING: THIS WILL CRASH ON FANATEC (maybe even more) WHEELS!
rumbleStrength=100
; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect.
rumbleDuration=1000

View File

@ -69,6 +69,20 @@ region=4
; exactly one machine and set this to 0 on all others.
dipsw1=1
[ffb]
; Enable force feedback (838-15069) board emulation. This is required for
; both DirectInput and XInput steering wheel effects.
enable=1
; -----------------------------------------------------------------------------
; LED settings
; -----------------------------------------------------------------------------
[led15070]
; Enable emulation of the 837-15070-02 controlled lights, which handle the
; cabinet and seat LEDs.
enable=1
; -----------------------------------------------------------------------------
; Misc. hooks settings
; -----------------------------------------------------------------------------
@ -212,6 +226,21 @@ reverseAccelAxis=0
reverseBrakeAxis=0
; Force feedback settings.
; Strength of the force feedback spring effect in percent. Possible values
; are 0-100.
centerSpringStrength=30
; Only works when FFB board emulation is enabled!
;
; It is recommended to change the strength inside the Game Test Mode!
;
; These settings are only used when using DirectInput for the wheel.
; The values are in the range 0%-100%, where 0 disables the effect and
; 100 is the maximum.
; Constant force strength, used for centering spring effect.
constantForceStrength=100
; Damper strength, used for steering wheel damper effect.
damperStrength=100
; Rumble strength, used for road surface effects.
; WARNING: THIS WILL CRASH ON FANATEC (maybe even more) WHEELS!
rumbleStrength=100
; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect.
rumbleDuration=1000

5
dist/idz/start.bat vendored
View File

@ -2,10 +2,11 @@
pushd %~dp0
inject -k idzhook.dll InitialD0_DX11_Nu.exe
start /min "AM Daemon" inject -d -k idzhook.dll amdaemon.exe -c configDHCP_Final_Common.json configDHCP_Final_JP.json configDHCP_Final_JP_ST1.json configDHCP_Final_JP_ST2.json configDHCP_Final_EX.json configDHCP_Final_EX_ST1.json configDHCP_Final_EX_ST2.json
rem Set dipsw1=0 and uncomment the ServerBox for in store battle?
rem inject -k idzhook.dll ServerBoxD8_Nu_x64.exe
inject -d -k idzhook.dll amdaemon.exe -c configDHCP_Final_Common.json configDHCP_Final_JP.json configDHCP_Final_JP_ST1.json configDHCP_Final_JP_ST2.json configDHCP_Final_EX.json configDHCP_Final_EX_ST1.json configDHCP_Final_EX_ST2.json
inject -d -k idzhook.dll InitialD0_DX11_Nu.exe
taskkill /im ServerBoxD8_Nu_x64.exe > nul 2>&1

View File

@ -65,6 +65,11 @@ enable=1
; allow you to start a game in freeplay mode.
freeplay=0
[ffb]
; Enable force feedback (838-15069) board emulation. This is required for
; both DirectInput and XInput steering wheel effects.
enable=1
; -----------------------------------------------------------------------------
; Custom IO settings
; -----------------------------------------------------------------------------
@ -181,6 +186,21 @@ reverseAccelAxis=0
reverseBrakeAxis=0
; Force feedback settings.
; Strength of the force feedback spring effect in percent. Possible values
; are 0-100.
centerSpringStrength=30
; Only works when FFB board emulation is enabled!
;
; It is recommended to change the strength inside the Game Test Mode!
;
; These settings are only used when using DirectInput for the wheel.
; The values are in the range 0%-100%, where 0 disables the effect and
; 100 is the maximum.
; Constant force strength, used for centering spring effect.
constantForceStrength=100
; Damper strength, used for steering wheel damper effect.
damperStrength=100
; Rumble strength, used for road surface effects.
; WARNING: THIS WILL CRASH ON FANATEC (maybe even more) WHEELS!
rumbleStrength=100
; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect.
rumbleDuration=1000

View File

@ -82,7 +82,7 @@ int WINAPI fwdlusb_getErrorLog(uint16_t index, uint8_t *rData, uint16_t *rResult
int WINAPI chcusb_MakeThread(uint16_t maxCount);
int WINAPI chcusb_open(uint16_t *rResult);
__stdcall void chcusb_close();
void WINAPI chcusb_close();
int WINAPI chcusb_ReleaseThread(uint16_t *rResult);
int WINAPI chcusb_listupPrinter(uint8_t *rIdArray);
int WINAPI chcusb_listupPrinterSN(uint64_t *rSerialArray);

View File

@ -89,6 +89,7 @@ void idac_hook_config_load(
zinput_config_load(&cfg->zinput, filename);
dvd_config_load(&cfg->dvd, filename);
io4_config_load(&cfg->io4, filename);
ffb_config_load(&cfg->ffb, filename);
led15070_config_load(&cfg->led15070, filename);
indrun_config_load(&cfg->indrun, filename);
}

View File

@ -19,6 +19,7 @@ struct idac_hook_config {
struct aime_config aime;
struct dvd_config dvd;
struct io4_config io4;
struct ffb_config ffb;
struct idac_dll_config dll;
struct zinput_config zinput;
struct led15070_config led15070;

View File

@ -5,7 +5,9 @@
USB: 837-15257 "Type 4" I/O Board
COM1: 838-15069 MOTOR DRIVE BD RS232/422 Board
COM2: 837-15070-02 IC BD LED Controller Board
COM2: 837-15070-02 IC BD LED Controller Board (DIPSW2 OFF)
OR
837-15070-04 IC BD LED Controller Board (DIPSW2 ON)
COM3: 837-15286 "Gen 2" Aime Reader (DIPSW2 OFF)
OR
837-15396 "Gen 3" Aime Reader (DIPSW2 ON)
@ -28,6 +30,7 @@
#include "idachook/config.h"
#include "idachook/idac-dll.h"
#include "idachook/io4.h"
#include "idachook/ffb.h"
#include "idachook/zinput.h"
#include "platform/platform.h"
@ -84,6 +87,12 @@ static DWORD CALLBACK idac_pre_startup(void)
goto fail;
}
hr = idac_ffb_hook_init(&idac_hook_cfg.ffb, 1);
if (FAILED(hr)) {
goto fail;
}
hr = led15070_hook_init(&idac_hook_cfg.led15070, idac_dll.led_init,
idac_dll.led_set_fet_output, NULL, idac_dll.led_gs_update, 2, 1);

59
idachook/ffb.c Normal file
View File

@ -0,0 +1,59 @@
#include <windows.h>
#include <xinput.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "board/ffb.h"
#include "idachook/idac-dll.h"
#include "util/dprintf.h"
static void idac_ffb_toggle(bool active);
static void idac_ffb_constant_force(uint8_t direction, uint8_t force);
static void idac_ffb_rumble(uint8_t force, uint8_t period);
static void idac_ffb_damper(uint8_t force);
static const struct ffb_ops idac_ffb_ops = {
.toggle = idac_ffb_toggle,
.constant_force = idac_ffb_constant_force,
.rumble = idac_ffb_rumble,
.damper = idac_ffb_damper
};
HRESULT idac_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no)
{
HRESULT hr;
assert(idac_dll.init != NULL);
hr = ffb_hook_init(cfg, &idac_ffb_ops, port_no);
if (FAILED(hr)) {
return hr;
}
return idac_dll.ffb_init();
}
static void idac_ffb_toggle(bool active)
{
idac_dll.ffb_toggle(active);
}
static void idac_ffb_constant_force(uint8_t direction, uint8_t force)
{
idac_dll.ffb_constant_force(direction, force);
}
static void idac_ffb_rumble(uint8_t force, uint8_t period)
{
idac_dll.ffb_rumble(force, period);
}
static void idac_ffb_damper(uint8_t force)
{
idac_dll.ffb_damper(force);
}

7
idachook/ffb.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "board/ffb.h"
HRESULT idac_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no);

View File

@ -36,6 +36,21 @@ const struct dll_bind_sym idac_dll_syms[] = {
}, {
.sym = "idac_io_led_set_leds",
.off = offsetof(struct idac_dll, led_set_leds),
}, {
.sym = "idac_io_ffb_init",
.off = offsetof(struct idac_dll, ffb_init),
}, {
.sym = "idac_io_ffb_toggle",
.off = offsetof(struct idac_dll, ffb_toggle),
}, {
.sym = "idac_io_ffb_constant_force",
.off = offsetof(struct idac_dll, ffb_constant_force),
}, {
.sym = "idac_io_ffb_rumble",
.off = offsetof(struct idac_dll, ffb_rumble),
}, {
.sym = "idac_io_ffb_damper",
.off = offsetof(struct idac_dll, ffb_damper),
}
};

View File

@ -15,6 +15,11 @@ struct idac_dll {
void (*led_set_fet_output)(const uint8_t *rgb);
void (*led_gs_update)(const uint8_t *rgb);
void (*led_set_leds)(const uint8_t *rgb);
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);
};
struct idac_dll_config {

View File

@ -21,3 +21,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

@ -15,7 +15,7 @@ static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len);
static uint16_t coins;
static const struct io4_ops idac_io4_ops = {
.poll = idac_io4_poll,
.poll = idac_io4_poll,
.write_gpio = idac_io4_write_gpio
};
@ -133,6 +133,8 @@ static HRESULT idac_io4_poll(void *ctx, struct io4_state *state)
static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len)
{
assert(idac_dll.led_set_leds != NULL);
// Just fast fail if there aren't enough bytes in the payload
if (len < 3)
return S_OK;

View File

@ -30,5 +30,7 @@ shared_library(
'zinput.h',
'indrun.c',
'indrun.h',
'ffb.c',
'ffb.h',
],
)

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;
@ -75,7 +79,6 @@ static uint8_t idac_di_gear[6];
static bool idac_di_use_pedals;
static bool idac_di_reverse_brake_axis;
static bool idac_di_reverse_accel_axis;
static uint16_t idac_di_center_spring_strength;
HRESULT idac_di_init(
const struct idac_di_config *cfg,
@ -105,7 +108,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 +171,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 +366,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;
}

View File

@ -18,6 +18,42 @@
#include "platform/config.h"
#include "platform/platform.h"
void led15070_config_load(struct led15070_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
wchar_t tmpstr[16];
cfg->enable = GetPrivateProfileIntW(L"led15070", L"enable", 1, filename);
cfg->port_no = GetPrivateProfileIntW(L"led15070", L"portNo", 0, filename);
cfg->fw_ver = GetPrivateProfileIntW(L"led15070", L"fwVer", 0x90, filename);
/* TODO: Unknown, no firmware file available */
cfg->fw_sum = GetPrivateProfileIntW(L"led15070", L"fwSum", 0x0000, filename);
GetPrivateProfileStringW(
L"led15070",
L"boardNumber",
L"15070-02",
tmpstr,
_countof(tmpstr),
filename);
size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number));
for (int i = n; i < sizeof(cfg->board_number); i++)
{
cfg->board_number[i] = ' ';
}
GetPrivateProfileStringW(
L"led15070",
L"eepromPath",
L"DEVICE",
cfg->eeprom_path,
_countof(cfg->eeprom_path),
filename);
}
void idz_dll_config_load(
struct idz_dll_config *cfg,
const wchar_t *filename)
@ -47,6 +83,8 @@ void idz_hook_config_load(
dvd_config_load(&cfg->dvd, filename);
gfx_config_load(&cfg->gfx, filename);
idz_dll_config_load(&cfg->dll, filename);
ffb_config_load(&cfg->ffb, filename);
led15070_config_load(&cfg->led15070, filename);
zinput_config_load(&cfg->zinput, filename);
}

View File

@ -5,7 +5,8 @@
#include "amex/amex.h"
#include "board/sg-reader.h"
#include "board/config.h"
#include "board/led15070.h"
#include "gfxhook/gfx.h"
@ -23,6 +24,8 @@ struct idz_hook_config {
struct dvd_config dvd;
struct gfx_config gfx;
struct idz_dll_config dll;
struct ffb_config ffb;
struct led15070_config led15070;
struct zinput_config zinput;
};

View File

@ -33,6 +33,7 @@
#include "idzhook/config.h"
#include "idzhook/idz-dll.h"
#include "idzhook/jvs.h"
#include "idzhook/ffb.h"
#include "idzhook/zinput.h"
#include "platform/platform.h"
@ -102,6 +103,12 @@ static DWORD CALLBACK idz_pre_startup(void)
goto fail;
}
hr = idz_jvs_hook_init();
if (FAILED(hr)) {
goto fail;
}
hr = amex_hook_init(&idz_hook_cfg.amex, idz_jvs_init);
if (FAILED(hr)) {
@ -114,6 +121,19 @@ static DWORD CALLBACK idz_pre_startup(void)
goto fail;
}
hr = idz_ffb_hook_init(&idz_hook_cfg.ffb, 1);
if (FAILED(hr)) {
goto fail;
}
hr = led15070_hook_init(&idz_hook_cfg.led15070, idz_dll.led_init,
idz_dll.led_set_fet_output, NULL, idz_dll.led_gs_update, 11, 1);
if (FAILED(hr)) {
goto fail;
}
/* Initialize debug helpers */
spike_hook_init(L".\\segatools.ini");

59
idzhook/ffb.c Normal file
View File

@ -0,0 +1,59 @@
#include <windows.h>
#include <xinput.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "board/ffb.h"
#include "idzhook/idz-dll.h"
#include "util/dprintf.h"
static void idz_ffb_toggle(bool active);
static void idz_ffb_constant_force(uint8_t direction, uint8_t force);
static void idz_ffb_rumble(uint8_t force, uint8_t period);
static void idz_ffb_damper(uint8_t force);
static const struct ffb_ops idz_ffb_ops = {
.toggle = idz_ffb_toggle,
.constant_force = idz_ffb_constant_force,
.rumble = idz_ffb_rumble,
.damper = idz_ffb_damper
};
HRESULT idz_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no)
{
HRESULT hr;
assert(idz_dll.jvs_init != NULL);
hr = ffb_hook_init(cfg, &idz_ffb_ops, port_no);
if (FAILED(hr)) {
return hr;
}
return idz_dll.ffb_init();
}
static void idz_ffb_toggle(bool active)
{
idz_dll.ffb_toggle(active);
}
static void idz_ffb_constant_force(uint8_t direction, uint8_t force)
{
idz_dll.ffb_constant_force(direction, force);
}
static void idz_ffb_rumble(uint8_t force, uint8_t period)
{
idz_dll.ffb_rumble(force, period);
}
static void idz_ffb_damper(uint8_t force)
{
idz_dll.ffb_damper(force);
}

7
idzhook/ffb.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "board/ffb.h"
HRESULT idz_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no);

View File

@ -24,6 +24,33 @@ const struct dll_bind_sym idz_dll_syms[] = {
}, {
.sym = "idz_io_jvs_read_coin_counter",
.off = offsetof(struct idz_dll, jvs_read_coin_counter),
}, {
.sym = "idz_io_led_init",
.off = offsetof(struct idz_dll, led_init),
}, {
.sym = "idz_io_led_set_fet_output",
.off = offsetof(struct idz_dll, led_set_fet_output),
}, {
.sym = "idz_io_led_gs_update",
.off = offsetof(struct idz_dll, led_gs_update),
}, {
.sym = "idz_io_led_set_leds",
.off = offsetof(struct idz_dll, led_set_leds),
}, {
.sym = "idz_io_ffb_init",
.off = offsetof(struct idz_dll, ffb_init),
}, {
.sym = "idz_io_ffb_toggle",
.off = offsetof(struct idz_dll, ffb_toggle),
}, {
.sym = "idz_io_ffb_constant_force",
.off = offsetof(struct idz_dll, ffb_constant_force),
}, {
.sym = "idz_io_ffb_rumble",
.off = offsetof(struct idz_dll, ffb_rumble),
}, {
.sym = "idz_io_ffb_damper",
.off = offsetof(struct idz_dll, ffb_damper),
}
};

View File

@ -11,6 +11,15 @@ struct idz_dll {
void (*jvs_read_buttons)(uint8_t *opbtn, uint8_t *gamebtn);
void (*jvs_read_shifter)(uint8_t *gear);
void (*jvs_read_coin_counter)(uint16_t *total);
HRESULT (*led_init)(void);
void (*led_set_fet_output)(const uint8_t *rgb);
void (*led_gs_update)(const uint8_t *rgb);
void (*led_set_leds)(const uint8_t *rgb);
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);
};
struct idz_dll_config {

View File

@ -22,3 +22,12 @@ EXPORTS
idz_io_jvs_read_buttons
idz_io_jvs_read_coin_counter
idz_io_jvs_read_shifter
idz_io_led_init
idz_io_led_set_fet_output
idz_io_led_gs_update
idz_io_led_set_leds
idz_io_ffb_init
idz_io_ffb_toggle
idz_io_ffb_constant_force
idz_io_ffb_rumble
idz_io_ffb_damper

View File

@ -24,11 +24,13 @@ static void idz_jvs_read_coin_counter(
void *ctx,
uint8_t slot_no,
uint16_t *out);
static void idz_jvs_write_gpio(void *ctx, uint32_t state);
static const struct io3_ops idz_jvs_io3_ops = {
.read_switches = idz_jvs_read_switches,
.read_analogs = idz_jvs_read_analogs,
.read_coin_counter = idz_jvs_read_coin_counter,
.write_gpio = idz_jvs_write_gpio
};
static const uint16_t idz_jvs_gear_signals[] = {
@ -50,21 +52,20 @@ static const uint16_t idz_jvs_gear_signals[] = {
static struct io3 idz_jvs_io3;
HRESULT idz_jvs_hook_init(void)
{
HRESULT hr;
assert(idz_dll.jvs_init != NULL);
return idz_dll.jvs_init();
}
HRESULT idz_jvs_init(struct jvs_node **out)
{
HRESULT hr;
assert(out != NULL);
assert(idz_dll.jvs_init != NULL);
dprintf("JVS I/O: Starting Initial D Zero backend DLL\n");
hr = idz_dll.jvs_init();
if (FAILED(hr)) {
dprintf("JVS I/O: Backend error, I/O disconnected; %x\n", (int) hr);
return hr;
}
io3_init(&idz_jvs_io3, NULL, &idz_jvs_io3_ops, NULL);
*out = io3_to_jvs_node(&idz_jvs_io3);
@ -175,3 +176,21 @@ static void idz_jvs_read_coin_counter(
idz_dll.jvs_read_coin_counter(out);
}
static void idz_jvs_write_gpio(void *ctx, uint32_t state)
{
assert(idz_dll.led_set_leds != NULL);
// Since Sega uses an odd ordering for the first part of the bitfield,
// let's normalize the data and just send over bytes for the receiver
// to interpret as ON/OFF values.
uint8_t rgb_out[6] = {
state & IDZ_IO_LED_START ? 0xFF : 0x00,
state & IDZ_IO_LED_VIEW_CHANGE ? 0xFF : 0x00,
state & IDZ_IO_LED_UP ? 0xFF : 0x00,
state & IDZ_IO_LED_DOWN ? 0xFF : 0x00,
state & IDZ_IO_LED_RIGHT ? 0xFF : 0x00,
state & IDZ_IO_LED_LEFT ? 0xFF : 0x00,
};
idz_dll.led_set_leds(rgb_out);
}

View File

@ -4,4 +4,6 @@
#include "jvs/jvs-bus.h"
HRESULT idz_jvs_hook_init(void);
HRESULT idz_jvs_init(struct jvs_node **root);

View File

@ -32,5 +32,7 @@ shared_library(
'jvs.h',
'zinput.c',
'zinput.h',
'ffb.c',
'ffb.h',
],
)

View File

@ -8,4 +8,9 @@ struct idz_io_backend {
void (*jvs_read_buttons)(uint8_t *gamebtn);
void (*jvs_read_shifter)(uint8_t *gear);
void (*jvs_read_analogs)(struct idz_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

@ -78,12 +78,29 @@ void idz_di_config_load(struct idz_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 idz_xi_config_load(struct idz_xi_config *cfg, const wchar_t *filename)

View File

@ -23,7 +23,11 @@ struct idz_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 idz_xi_config {

View File

@ -1,134 +1,39 @@
#include <windows.h>
#include <dinput.h>
#include <stdbool.h>
#include <assert.h>
#include "idzio/di-dev.h"
#include "util/dprintf.h"
HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd)
const struct idz_di_config *idz_di_cfg;
static HWND idz_di_wnd;
static IDirectInputDevice8W *idz_di_dev;
/* Individual DI Effects */
static IDirectInputEffect *idz_di_fx;
static IDirectInputEffect *idz_di_fx_rumble;
static IDirectInputEffect *idz_di_fx_damper;
/* Max FFB Board value is 127 */
static const double idz_di_ffb_scale = 127.0;
HRESULT idz_di_dev_init(
const struct idz_di_config *cfg,
IDirectInputDevice8W *dev,
HWND wnd)
{
HRESULT hr;
assert(dev != NULL);
assert(wnd != NULL);
hr = IDirectInputDevice8_SetCooperativeLevel(
dev,
wnd,
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
idz_di_cfg = cfg;
idz_di_dev = dev;
idz_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 idz_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 idz_di_dev_poll(
@ -167,3 +72,312 @@ HRESULT idz_di_dev_poll(
return hr;
}
HRESULT idz_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 idz_di_ffb_init(void)
{
HRESULT hr;
hr = idz_di_dev_start(idz_di_dev, idz_di_wnd);
if (FAILED(hr)) {
return hr;
}
return S_OK;
}
void idz_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 (idz_di_fx != NULL) {
IDirectInputEffect_Stop(idz_di_fx);
IDirectInputEffect_Release(idz_di_fx);
idz_di_fx = NULL;
}
if (idz_di_fx_rumble != NULL) {
IDirectInputEffect_Stop(idz_di_fx_rumble);
IDirectInputEffect_Release(idz_di_fx_rumble);
idz_di_fx_rumble = NULL;
}
if (idz_di_fx_damper != NULL) {
IDirectInputEffect_Stop(idz_di_fx_damper);
IDirectInputEffect_Release(idz_di_fx_damper);
idz_di_fx_damper = NULL;
}
}
void idz_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 = idz_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 / idz_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 (idz_di_fx != NULL) {
// Try to update the existing effect
hr = IDirectInputEffect_SetParameters(idz_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(idz_di_fx);
IDirectInputEffect_Release(idz_di_fx);
idz_di_fx = NULL;
}
}
// Create a new constant force effect
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
idz_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;
}
idz_di_fx = obj;
}
void idz_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 = idz_di_cfg->ffb_rumble_strength * 100;
if (ffb_strength == 0) {
return;
}
uint32_t ffb_duration = idz_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 / idz_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 (idz_di_fx_rumble != NULL) {
// Try to update the existing effect
hr = IDirectInputEffect_SetParameters(idz_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(idz_di_fx_rumble);
IDirectInputEffect_Release(idz_di_fx_rumble);
idz_di_fx_rumble = NULL;
}
}
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
idz_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;
}
idz_di_fx_rumble = obj;
}
void idz_di_ffb_damper(uint8_t force)
{
/* DI expects a coefficient in the range of -10.000 to 10.000 */
uint16_t ffb_strength = idz_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 / idz_di_ffb_scale) * ffb_strength);
cond.lNegativeCoefficient = (LONG)(((double)force / idz_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 (idz_di_fx_damper != NULL) {
// Try to update the existing effect
hr = IDirectInputEffect_SetParameters(idz_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(idz_di_fx_damper);
IDirectInputEffect_Release(idz_di_fx_damper);
idz_di_fx_damper = NULL;
}
}
// Create a new damper force effect
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
idz_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;
}
idz_di_fx_damper = obj;
}

View File

@ -5,15 +5,26 @@
#include <stdint.h>
#include "idzio/config.h"
union idz_di_state {
DIJOYSTATE st;
uint8_t bytes[sizeof(DIJOYSTATE)];
};
HRESULT idz_di_dev_init(
const struct idz_di_config *cfg,
IDirectInputDevice8W *dev,
HWND wnd);
HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength);
HRESULT idz_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,
union idz_di_state *out);
HRESULT idz_di_ffb_init(void);
void idz_di_ffb_toggle(bool active);
void idz_di_ffb_constant_force(uint8_t direction, uint8_t force);
void idz_di_ffb_rumble(uint8_t force, uint8_t period);
void idz_di_ffb_damper(uint8_t force);

View File

@ -55,6 +55,11 @@ static const struct idz_io_backend idz_di_backend = {
.jvs_read_buttons = idz_di_jvs_read_buttons,
.jvs_read_shifter = idz_di_jvs_read_shifter,
.jvs_read_analogs = idz_di_jvs_read_analogs,
.ffb_init = idz_di_ffb_init,
.ffb_toggle = idz_di_ffb_toggle,
.ffb_constant_force = idz_di_ffb_constant_force,
.ffb_rumble = idz_di_ffb_rumble,
.ffb_damper = idz_di_ffb_damper
};
static HWND idz_di_wnd;
@ -73,7 +78,6 @@ static uint8_t idz_di_gear[6];
static bool idz_di_use_pedals;
static bool idz_di_reverse_brake_axis;
static bool idz_di_reverse_accel_axis;
static uint16_t idz_di_center_spring_strength;
HRESULT idz_di_init(
const struct idz_di_config *cfg,
@ -166,16 +170,12 @@ HRESULT idz_di_init(
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = idz_di_dev_start(idz_di_dev, idz_di_wnd);
hr = idz_di_dev_init(cfg, idz_di_dev, idz_di_wnd);
if (FAILED(hr)) {
return hr;
}
// Convert the strength from 0-100 to 0-10000 for DirectInput
idz_di_dev_start_fx(idz_di_dev, &idz_di_fx,
idz_di_center_spring_strength * 100);
if (cfg->pedals_name[0] != L'\0') {
hr = IDirectInput8_EnumDevices(
idz_di_api,
@ -349,15 +349,24 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg)
idz_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;
}
idz_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

@ -20,7 +20,7 @@ static uint16_t idz_io_coins;
uint16_t idz_io_get_api_version(void)
{
return 0x0100;
return 0x0102;
}
HRESULT idz_io_jvs_init(void)
@ -123,3 +123,79 @@ void idz_io_jvs_read_coin_counter(uint16_t *out)
*out = idz_io_coins;
}
HRESULT idz_io_led_init(void)
{
return S_OK;
}
void idz_io_led_set_fet_output(const uint8_t *rgb)
{
#if 0
dprintf("IDZ LED: LEFT SEAT LED: %02X\n", rgb[0]);
dprintf("IDZ LED: RIGHT SEAT LED: %02X\n", rgb[1]);
#endif
return;
}
void idz_io_led_gs_update(const uint8_t *rgb)
{
#if 0
for (int i = 0; i < 9; i++) {
dprintf("IDZ 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 idz_io_led_set_leds(const uint8_t *rgb)
{
#if 0
dprintf("IDZ LED: START: %02X\n", rgb[0]);
dprintf("IDZ LED: VIEW CHANGE: %02X\n", rgb[1]);
dprintf("IDZ LED: UP: %02X\n", rgb[2]);
dprintf("IDZ LED: DOWN: %02X\n", rgb[3]);
dprintf("IDZ LED: RIGHT: %02X\n", rgb[4]);
dprintf("IDZ LED: LEFT: %02X\n", rgb[5]);
#endif
return;
}
HRESULT idz_io_ffb_init(void)
{
assert(idz_io_backend != NULL);
return idz_io_backend->ffb_init();
}
void idz_io_ffb_toggle(bool active)
{
assert(idz_io_backend != NULL);
idz_io_backend->ffb_toggle(active);
}
void idz_io_ffb_constant_force(uint8_t direction, uint8_t force)
{
assert(idz_io_backend != NULL);
idz_io_backend->ffb_constant_force(direction, force);
}
void idz_io_ffb_rumble(uint8_t period, uint8_t force)
{
assert(idz_io_backend != NULL);
idz_io_backend->ffb_rumble(period, force);
}
void idz_io_ffb_damper(uint8_t force)
{
assert(idz_io_backend != NULL);
idz_io_backend->ffb_damper(force);
}

View File

@ -6,3 +6,12 @@ EXPORTS
idz_io_jvs_read_buttons
idz_io_jvs_read_coin_counter
idz_io_jvs_read_shifter
idz_io_led_init
idz_io_led_set_fet_output
idz_io_led_gs_update
idz_io_led_set_leds
idz_io_ffb_init
idz_io_ffb_toggle
idz_io_ffb_constant_force
idz_io_ffb_rumble
idz_io_ffb_damper

View File

@ -14,6 +14,7 @@
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
enum {
@ -30,6 +31,17 @@ enum {
IDZ_IO_GAMEBTN_VIEW_CHANGE = 0x20,
};
enum {
/* These are the bitmasks to use when checking which
lights are triggered on incoming IO4 GPIO writes. */
IDZ_IO_LED_START = 1 << 7,
IDZ_IO_LED_VIEW_CHANGE = 1 << 6,
IDZ_IO_LED_UP = 1 << 1,
IDZ_IO_LED_DOWN = 1 << 0,
IDZ_IO_LED_RIGHT = 1 << 14,
IDZ_IO_LED_LEFT = 1 << 15
};
struct idz_io_analog_state {
/* Current steering wheel position, where zero is the centered position.
@ -104,3 +116,107 @@ void idz_io_jvs_read_shifter(uint8_t *gear);
Minimum API version: 0x0100 */
void idz_io_jvs_read_coin_counter(uint16_t *total);
/* Initialize LED emulation. This function will be called before any
other idz_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 idz_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 idz_io_led_set_fet_output(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 idz_io_led_gs_update(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 idz_io_led_set_leds(const uint8_t *rgb);
/* Initialize FFB emulation. This function will be called before any
other idz_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 idz_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 idz_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 idz_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 idz_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 idz_io_ffb_damper(uint8_t force);

View File

@ -18,12 +18,23 @@ static void idz_xi_jvs_read_buttons(uint8_t *gamebtn_out);
static void idz_xi_jvs_read_shifter(uint8_t *gear);
static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out);
static HRESULT idz_xi_ffb_init(void);
static void idz_xi_ffb_toggle(bool active);
static void idz_xi_ffb_constant_force(uint8_t direction, uint8_t force);
static void idz_xi_ffb_rumble(uint8_t force, uint8_t period);
static void idz_xi_ffb_damper(uint8_t force);
static HRESULT idz_xi_config_apply(const struct idz_xi_config *cfg);
static const struct idz_io_backend idz_xi_backend = {
.jvs_read_buttons = idz_xi_jvs_read_buttons,
.jvs_read_shifter = idz_xi_jvs_read_shifter,
.jvs_read_analogs = idz_xi_jvs_read_analogs,
.ffb_init = idz_xi_ffb_init,
.ffb_toggle = idz_xi_ffb_toggle,
.ffb_constant_force = idz_xi_ffb_constant_force,
.ffb_rumble = idz_xi_ffb_rumble,
.ffb_damper = idz_xi_ffb_damper
};
static bool idz_xi_single_stick_steering;
@ -210,3 +221,35 @@ static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out)
out->accel = xi.Gamepad.bRightTrigger << 8;
out->brake = xi.Gamepad.bLeftTrigger << 8;
}
static HRESULT idz_xi_ffb_init(void) {
return S_OK;
}
static void idz_xi_ffb_toggle(bool active) {
XINPUT_VIBRATION vibration;
memset(&vibration, 0, sizeof(vibration));
XInputSetState(0, &vibration);
}
static void idz_xi_ffb_constant_force(uint8_t direction, uint8_t force) {
return;
}
static void idz_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 idz_xi_ffb_damper(uint8_t force) {
return;
}

View File

@ -13,6 +13,43 @@
#include "platform/config.h"
#include "platform/platform.h"
void led15070_config_load(struct led15070_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
wchar_t tmpstr[16];
cfg->enable = GetPrivateProfileIntW(L"led15070", L"enable", 1, filename);
cfg->port_no = GetPrivateProfileIntW(L"led15070", L"portNo", 0, filename);
cfg->fw_ver = GetPrivateProfileIntW(L"led15070", L"fwVer", 0x90, filename);
/* TODO: Unknown, no firmware file available */
cfg->fw_sum = GetPrivateProfileIntW(L"led15070", L"fwSum", 0xdead, filename);
GetPrivateProfileStringW(
L"led15070",
L"boardNumber",
L"15070-04",
tmpstr,
_countof(tmpstr),
filename);
size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number));
for (int i = n; i < sizeof(cfg->board_number); i++)
{
cfg->board_number[i] = ' ';
}
GetPrivateProfileStringW(
L"led15070",
L"eepromPath",
L"DEVICE",
cfg->eeprom_path,
_countof(cfg->eeprom_path),
filename);
}
void swdc_dll_config_load(
struct swdc_dll_config *cfg,
const wchar_t *filename)
@ -29,6 +66,14 @@ void swdc_dll_config_load(
filename);
}
void zinput_config_load(struct zinput_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"zinput", L"enable", 1, filename);
}
void swdc_hook_config_load(
struct swdc_hook_config *cfg,
const wchar_t *filename)
@ -42,13 +87,7 @@ void swdc_hook_config_load(
zinput_config_load(&cfg->zinput, filename);
dvd_config_load(&cfg->dvd, filename);
io4_config_load(&cfg->io4, filename);
ffb_config_load(&cfg->ffb, filename);
led15070_config_load(&cfg->led15070, filename);
vfd_config_load(&cfg->vfd, filename);
}
void zinput_config_load(struct zinput_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"zinput", L"enable", 1, filename);
}

View File

@ -4,6 +4,7 @@
#include <stddef.h>
#include "board/config.h"
#include "board/led15070.h"
#include "hooklib/dvd.h"
@ -17,7 +18,9 @@ struct swdc_hook_config {
struct aime_config aime;
struct dvd_config dvd;
struct io4_config io4;
struct ffb_config ffb;
struct vfd_config vfd;
struct led15070_config led15070;
struct swdc_dll_config dll;
struct zinput_config zinput;
};

View File

@ -8,8 +8,8 @@
WITH
838-15416 Indicator BD LED Board
COM1: 838-15069 MOTOR DRIVE BD RS232/422 board
COM2: 837-15396 "Gen 3" Aime reader
COM3: 837-15070-04 IC BD LED controller board
COM2: 837-15070-04 IC BD LED controller board
COM3: 837-15396 "Gen 3" Aime reader
COM4: 200-6275 VFD GP1232A02A FUTABA board
*/
@ -31,6 +31,7 @@
#include "swdchook/config.h"
#include "swdchook/swdc-dll.h"
#include "swdchook/io4.h"
#include "swdchook/ffb.h"
#include "platform/platform.h"
@ -91,6 +92,20 @@ static DWORD CALLBACK swdc_pre_startup(void)
goto fail;
}
hr = swdc_ffb_hook_init(&swdc_hook_cfg.ffb, 1);
if (FAILED(hr)) {
goto fail;
}
/* Not working, different board -04 instead of -02? */
hr = led15070_hook_init(&swdc_hook_cfg.led15070, swdc_dll.led_init,
swdc_dll.led_set_fet_output, NULL, swdc_dll.led_gs_update, 2, 1);
if (FAILED(hr)) {
goto fail;
}
/* Hook external DLL APIs */
zinput_hook_init(&swdc_hook_cfg.zinput);

59
swdchook/ffb.c Normal file
View File

@ -0,0 +1,59 @@
#include <windows.h>
#include <xinput.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "board/ffb.h"
#include "swdchook/swdc-dll.h"
#include "util/dprintf.h"
static void swdc_ffb_toggle(bool active);
static void swdc_ffb_constant_force(uint8_t direction, uint8_t force);
static void swdc_ffb_rumble(uint8_t force, uint8_t period);
static void swdc_ffb_damper(uint8_t force);
static const struct ffb_ops swdc_ffb_ops = {
.toggle = swdc_ffb_toggle,
.constant_force = swdc_ffb_constant_force,
.rumble = swdc_ffb_rumble,
.damper = swdc_ffb_damper
};
HRESULT swdc_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no)
{
HRESULT hr;
assert(swdc_dll.init != NULL);
hr = ffb_hook_init(cfg, &swdc_ffb_ops, port_no);
if (FAILED(hr)) {
return hr;
}
return swdc_dll.ffb_init();
}
static void swdc_ffb_toggle(bool active)
{
swdc_dll.ffb_toggle(active);
}
static void swdc_ffb_constant_force(uint8_t direction, uint8_t force)
{
swdc_dll.ffb_constant_force(direction, force);
}
static void swdc_ffb_rumble(uint8_t force, uint8_t period)
{
swdc_dll.ffb_rumble(force, period);
}
static void swdc_ffb_damper(uint8_t force)
{
swdc_dll.ffb_damper(force);
}

7
swdchook/ffb.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "board/ffb.h"
HRESULT swdc_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no);

View File

@ -15,10 +15,12 @@ static HRESULT init_mmf(void);
static void swdc_set_gamebtns(uint16_t value);
static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state);
static HRESULT swdc_io4_write_gpio(uint8_t* payload, size_t len);
static uint16_t coins;
static const struct io4_ops swdc_io4_ops = {
.poll = swdc_io4_poll,
.poll = swdc_io4_poll,
.write_gpio = swdc_io4_write_gpio
};
HRESULT swdc_io4_hook_init(const struct io4_config *cfg) {
@ -172,3 +174,36 @@ static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state) {
return S_OK;
}
static HRESULT swdc_io4_write_gpio(uint8_t* payload, size_t len)
{
assert(swdc_dll.led_set_leds != NULL);
// Just fast fail if there aren't enough bytes in the payload
if (len < 3)
return S_OK;
// This command is used for lights in SWDC, but it only contains button lights,
// and only in the first 3 bytes of the payload; everything else is padding to
// make the payload 62 bytes. The rest of the cabinet lights and the side button
// lights are handled separately, by the 15070 lights controller.
uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 |
(uint8_t)(payload[1]) << 16 |
(uint8_t)(payload[2]) << 8);
// Since Sega uses an odd ordering for the first part of the bitfield,
// let's normalize the data and just send over bytes for the receiver
// to interpret as ON/OFF values.
uint8_t rgb_out[6] = {
lights_data & SWDC_IO_LED_START ? 0xFF : 0x00,
lights_data & SWDC_IO_LED_VIEW_CHANGE ? 0xFF : 0x00,
lights_data & SWDC_IO_LED_UP ? 0xFF : 0x00,
lights_data & SWDC_IO_LED_DOWN ? 0xFF : 0x00,
lights_data & SWDC_IO_LED_RIGHT ? 0xFF : 0x00,
lights_data & SWDC_IO_LED_LEFT ? 0xFF : 0x00,
};
swdc_dll.led_set_leds(rgb_out);
return S_OK;
}

View File

@ -28,5 +28,7 @@ shared_library(
'io4.h',
'zinput.c',
'zinput.h',
'ffb.c',
'ffb.h'
],
)

View File

@ -21,6 +21,33 @@ const struct dll_bind_sym swdc_dll_syms[] = {
}, {
.sym = "swdc_io_get_analogs",
.off = offsetof(struct swdc_dll, get_analogs),
}, {
.sym = "swdc_io_led_init",
.off = offsetof(struct swdc_dll, led_init),
}, {
.sym = "swdc_io_led_set_fet_output",
.off = offsetof(struct swdc_dll, led_set_fet_output),
}, {
.sym = "swdc_io_led_gs_update",
.off = offsetof(struct swdc_dll, led_gs_update),
}, {
.sym = "swdc_io_led_set_leds",
.off = offsetof(struct swdc_dll, led_set_leds),
}, {
.sym = "swdc_io_ffb_init",
.off = offsetof(struct swdc_dll, ffb_init),
}, {
.sym = "swdc_io_ffb_toggle",
.off = offsetof(struct swdc_dll, ffb_toggle),
}, {
.sym = "swdc_io_ffb_constant_force",
.off = offsetof(struct swdc_dll, ffb_constant_force),
}, {
.sym = "swdc_io_ffb_rumble",
.off = offsetof(struct swdc_dll, ffb_rumble),
}, {
.sym = "swdc_io_ffb_damper",
.off = offsetof(struct swdc_dll, ffb_damper),
}
};

View File

@ -10,6 +10,15 @@ struct swdc_dll {
void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint16_t *gamebtn);
void (*get_analogs)(struct swdc_io_analog_state *out);
HRESULT (*led_init)(void);
void (*led_set_fet_output)(const uint8_t *rgb);
void (*led_gs_update)(const uint8_t *rgb);
void (*led_set_leds)(const uint8_t *rgb);
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);
};
struct swdc_dll_config {

View File

@ -16,3 +16,12 @@ EXPORTS
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

View File

@ -8,4 +8,9 @@ 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);
};

View File

@ -69,12 +69,29 @@ void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename)
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 swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename)

View File

@ -21,7 +21,11 @@ struct swdc_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 swdc_xi_config {

View File

@ -1,134 +1,39 @@
#include <windows.h>
#include <dinput.h>
#include <stdbool.h>
#include <assert.h>
#include "swdcio/di-dev.h"
#include "util/dprintf.h"
HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd)
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);
hr = IDirectInputDevice8_SetCooperativeLevel(
dev,
wnd,
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
swdc_di_cfg = cfg;
swdc_di_dev = dev;
swdc_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 swdc_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 swdc_di_dev_poll(
@ -167,3 +72,312 @@ HRESULT swdc_di_dev_poll(
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;
/* 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 (swdc_di_fx != NULL) {
// Try to update the existing effect
hr = IDirectInputEffect_SetParameters(swdc_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(swdc_di_fx);
IDirectInputEffect_Release(swdc_di_fx);
swdc_di_fx = NULL;
}
}
// 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;
}
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;
/* 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 / 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 = 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 (swdc_di_fx_rumble != NULL) {
// Try to update the existing effect
hr = IDirectInputEffect_SetParameters(swdc_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(swdc_di_fx_rumble);
IDirectInputEffect_Release(swdc_di_fx_rumble);
swdc_di_fx_rumble = NULL;
}
}
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
swdc_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;
}
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;
if (swdc_di_fx_damper != NULL) {
// Try to update the existing effect
hr = IDirectInputEffect_SetParameters(swdc_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(swdc_di_fx_damper);
IDirectInputEffect_Release(swdc_di_fx_damper);
swdc_di_fx_damper = NULL;
}
}
// Create a new damper force effect
IDirectInputEffect *obj;
hr = IDirectInputDevice8_CreateEffect(
swdc_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;
}
swdc_di_fx_damper = obj;
}

View File

@ -5,15 +5,26 @@
#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);
void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength);
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);

View File

@ -45,8 +45,13 @@ static const struct swdc_di_axis swdc_di_axes[] = {
};
static const struct swdc_io_backend swdc_di_backend = {
.get_gamebtns = swdc_di_get_buttons,
.get_analogs = swdc_di_get_analogs,
.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;
@ -67,7 +72,6 @@ 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;
static uint16_t swdc_di_center_spring_strength;
HRESULT swdc_di_init(
const struct swdc_di_config *cfg,
@ -160,16 +164,12 @@ HRESULT swdc_di_init(
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = swdc_di_dev_start(swdc_di_dev, swdc_di_wnd);
hr = swdc_di_dev_init(cfg, swdc_di_dev, swdc_di_wnd);
if (FAILED(hr)) {
return hr;
}
// Convert the strength from 0-100 to 0-10000 for DirectInput
swdc_di_dev_start_fx(swdc_di_dev, &swdc_di_fx,
swdc_di_center_spring_strength * 100);
if (cfg->pedals_name[0] != L'\0') {
hr = IDirectInput8_EnumDevices(
swdc_di_api,
@ -320,15 +320,24 @@ static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg)
swdc_di_reverse_brake_axis = cfg->reverse_brake_axis;
swdc_di_reverse_accel_axis = cfg->reverse_accel_axis;
// 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;
}
swdc_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 swdc_io_coin;
uint16_t swdc_io_get_api_version(void)
{
return 0x0100;
return 0x0102;
}
HRESULT swdc_io_init(void)
@ -62,6 +62,8 @@ void swdc_io_get_opbtns(uint8_t *opbtn_out)
opbtn = 0;
/* Common operator buttons, not backend-specific */
if (GetAsyncKeyState(swdc_io_cfg.vk_test) & 0x8000) {
opbtn |= SWDC_IO_OPBTN_TEST;
}
@ -110,3 +112,79 @@ void swdc_io_get_analogs(struct swdc_io_analog_state *out)
out->accel = tmp.accel;
out->brake = tmp.brake;
}
HRESULT swdc_io_led_init(void)
{
return S_OK;
}
void swdc_io_led_set_fet_output(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(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(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);
}

View File

@ -5,3 +5,12 @@ EXPORTS
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

View File

@ -2,6 +2,7 @@
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
enum {
@ -26,6 +27,17 @@ enum {
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.
@ -45,7 +57,7 @@ struct swdc_io_analog_state {
uint16_t brake;
};
/* Get the version of the IDAC IO API that this DLL supports. This
/* 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).
@ -89,3 +101,107 @@ void swdc_io_get_gamebtns(uint16_t *gamebtn);
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(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(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(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);

View File

@ -16,11 +16,22 @@
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,
.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;
@ -210,3 +221,35 @@ static void swdc_xi_get_analogs(struct swdc_io_analog_state *out)
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;
}