commit
36849bd09a
|
@ -1,6 +1,6 @@
|
||||||
# Segatools
|
# Segatools
|
||||||
|
|
||||||
Version: `2024-08-20`
|
Version: `2024-09-30`
|
||||||
|
|
||||||
Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platforms.
|
Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platforms.
|
||||||
|
|
||||||
|
|
|
@ -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->port = GetPrivateProfileIntW(L"vfd", L"portNo", 0, filename);
|
||||||
cfg->utf_conversion = GetPrivateProfileIntW(L"vfd", L"utfConversion", 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);
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
#include "board/io4.h"
|
#include "board/io4.h"
|
||||||
#include "board/sg-reader.h"
|
#include "board/sg-reader.h"
|
||||||
#include "board/vfd.h"
|
#include "board/vfd.h"
|
||||||
|
#include "board/ffb.h"
|
||||||
|
|
||||||
void aime_config_load(struct aime_config *cfg, const wchar_t *filename);
|
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 io4_config_load(struct io4_config *cfg, const wchar_t *filename);
|
||||||
void vfd_config_load(struct vfd_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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
|
@ -50,5 +50,7 @@ board_lib = static_library(
|
||||||
'vfd-cmd.h',
|
'vfd-cmd.h',
|
||||||
'vfd-frame.c',
|
'vfd-frame.c',
|
||||||
'vfd-frame.h',
|
'vfd-frame.h',
|
||||||
|
'ffb.c',
|
||||||
|
'ffb.h'
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -75,6 +75,11 @@ dipsw3=0
|
||||||
dipsw4=0
|
dipsw4=0
|
||||||
dipsw5=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
|
; LED settings
|
||||||
; -----------------------------------------------------------------------------
|
; -----------------------------------------------------------------------------
|
||||||
|
@ -231,6 +236,21 @@ reverseAccelAxis=0
|
||||||
reverseBrakeAxis=0
|
reverseBrakeAxis=0
|
||||||
|
|
||||||
; Force feedback settings.
|
; Force feedback settings.
|
||||||
; Strength of the force feedback spring effect in percent. Possible values
|
; Only works when FFB board emulation is enabled!
|
||||||
; are 0-100.
|
;
|
||||||
centerSpringStrength=30
|
; 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
|
||||||
|
|
|
@ -69,6 +69,20 @@ region=4
|
||||||
; exactly one machine and set this to 0 on all others.
|
; exactly one machine and set this to 0 on all others.
|
||||||
dipsw1=1
|
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
|
; Misc. hooks settings
|
||||||
; -----------------------------------------------------------------------------
|
; -----------------------------------------------------------------------------
|
||||||
|
@ -212,6 +226,21 @@ reverseAccelAxis=0
|
||||||
reverseBrakeAxis=0
|
reverseBrakeAxis=0
|
||||||
|
|
||||||
; Force feedback settings.
|
; Force feedback settings.
|
||||||
; Strength of the force feedback spring effect in percent. Possible values
|
; Only works when FFB board emulation is enabled!
|
||||||
; are 0-100.
|
;
|
||||||
centerSpringStrength=30
|
; 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
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
pushd %~dp0
|
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 Set dipsw1=0 and uncomment the ServerBox for in store battle?
|
||||||
rem inject -k idzhook.dll ServerBoxD8_Nu_x64.exe
|
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
|
taskkill /im ServerBoxD8_Nu_x64.exe > nul 2>&1
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,11 @@ enable=1
|
||||||
; allow you to start a game in freeplay mode.
|
; allow you to start a game in freeplay mode.
|
||||||
freeplay=0
|
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
|
; Custom IO settings
|
||||||
; -----------------------------------------------------------------------------
|
; -----------------------------------------------------------------------------
|
||||||
|
@ -181,6 +186,21 @@ reverseAccelAxis=0
|
||||||
reverseBrakeAxis=0
|
reverseBrakeAxis=0
|
||||||
|
|
||||||
; Force feedback settings.
|
; Force feedback settings.
|
||||||
; Strength of the force feedback spring effect in percent. Possible values
|
; Only works when FFB board emulation is enabled!
|
||||||
; are 0-100.
|
;
|
||||||
centerSpringStrength=30
|
; 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
|
||||||
|
|
|
@ -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_MakeThread(uint16_t maxCount);
|
||||||
int WINAPI chcusb_open(uint16_t *rResult);
|
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_ReleaseThread(uint16_t *rResult);
|
||||||
int WINAPI chcusb_listupPrinter(uint8_t *rIdArray);
|
int WINAPI chcusb_listupPrinter(uint8_t *rIdArray);
|
||||||
int WINAPI chcusb_listupPrinterSN(uint64_t *rSerialArray);
|
int WINAPI chcusb_listupPrinterSN(uint64_t *rSerialArray);
|
||||||
|
|
|
@ -89,6 +89,7 @@ void idac_hook_config_load(
|
||||||
zinput_config_load(&cfg->zinput, filename);
|
zinput_config_load(&cfg->zinput, filename);
|
||||||
dvd_config_load(&cfg->dvd, filename);
|
dvd_config_load(&cfg->dvd, filename);
|
||||||
io4_config_load(&cfg->io4, filename);
|
io4_config_load(&cfg->io4, filename);
|
||||||
|
ffb_config_load(&cfg->ffb, filename);
|
||||||
led15070_config_load(&cfg->led15070, filename);
|
led15070_config_load(&cfg->led15070, filename);
|
||||||
indrun_config_load(&cfg->indrun, filename);
|
indrun_config_load(&cfg->indrun, filename);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ struct idac_hook_config {
|
||||||
struct aime_config aime;
|
struct aime_config aime;
|
||||||
struct dvd_config dvd;
|
struct dvd_config dvd;
|
||||||
struct io4_config io4;
|
struct io4_config io4;
|
||||||
|
struct ffb_config ffb;
|
||||||
struct idac_dll_config dll;
|
struct idac_dll_config dll;
|
||||||
struct zinput_config zinput;
|
struct zinput_config zinput;
|
||||||
struct led15070_config led15070;
|
struct led15070_config led15070;
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
|
|
||||||
USB: 837-15257 "Type 4" I/O Board
|
USB: 837-15257 "Type 4" I/O Board
|
||||||
COM1: 838-15069 MOTOR DRIVE BD RS232/422 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)
|
COM3: 837-15286 "Gen 2" Aime Reader (DIPSW2 OFF)
|
||||||
OR
|
OR
|
||||||
837-15396 "Gen 3" Aime Reader (DIPSW2 ON)
|
837-15396 "Gen 3" Aime Reader (DIPSW2 ON)
|
||||||
|
@ -28,6 +30,7 @@
|
||||||
#include "idachook/config.h"
|
#include "idachook/config.h"
|
||||||
#include "idachook/idac-dll.h"
|
#include "idachook/idac-dll.h"
|
||||||
#include "idachook/io4.h"
|
#include "idachook/io4.h"
|
||||||
|
#include "idachook/ffb.h"
|
||||||
#include "idachook/zinput.h"
|
#include "idachook/zinput.h"
|
||||||
|
|
||||||
#include "platform/platform.h"
|
#include "platform/platform.h"
|
||||||
|
@ -84,6 +87,12 @@ static DWORD CALLBACK idac_pre_startup(void)
|
||||||
goto fail;
|
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,
|
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);
|
idac_dll.led_set_fet_output, NULL, idac_dll.led_gs_update, 2, 1);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
|
@ -36,6 +36,21 @@ const struct dll_bind_sym idac_dll_syms[] = {
|
||||||
}, {
|
}, {
|
||||||
.sym = "idac_io_led_set_leds",
|
.sym = "idac_io_led_set_leds",
|
||||||
.off = offsetof(struct idac_dll, 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),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,11 @@ struct idac_dll {
|
||||||
void (*led_set_fet_output)(const uint8_t *rgb);
|
void (*led_set_fet_output)(const uint8_t *rgb);
|
||||||
void (*led_gs_update)(const uint8_t *rgb);
|
void (*led_gs_update)(const uint8_t *rgb);
|
||||||
void (*led_set_leds)(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 {
|
struct idac_dll_config {
|
||||||
|
|
|
@ -21,3 +21,8 @@ EXPORTS
|
||||||
idac_io_led_set_fet_output
|
idac_io_led_set_fet_output
|
||||||
idac_io_led_gs_update
|
idac_io_led_gs_update
|
||||||
idac_io_led_set_leds
|
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
|
||||||
|
|
|
@ -15,7 +15,7 @@ static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len);
|
||||||
static uint16_t coins;
|
static uint16_t coins;
|
||||||
|
|
||||||
static const struct io4_ops idac_io4_ops = {
|
static const struct io4_ops idac_io4_ops = {
|
||||||
.poll = idac_io4_poll,
|
.poll = idac_io4_poll,
|
||||||
.write_gpio = idac_io4_write_gpio
|
.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)
|
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
|
// Just fast fail if there aren't enough bytes in the payload
|
||||||
if (len < 3)
|
if (len < 3)
|
||||||
return S_OK;
|
return S_OK;
|
||||||
|
|
|
@ -30,5 +30,7 @@ shared_library(
|
||||||
'zinput.h',
|
'zinput.h',
|
||||||
'indrun.c',
|
'indrun.c',
|
||||||
'indrun.h',
|
'indrun.h',
|
||||||
|
'ffb.c',
|
||||||
|
'ffb.h',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,4 +9,9 @@ struct idac_io_backend {
|
||||||
void (*get_gamebtns)(uint8_t *gamebtn);
|
void (*get_gamebtns)(uint8_t *gamebtn);
|
||||||
void (*get_shifter)(uint8_t *gear);
|
void (*get_shifter)(uint8_t *gear);
|
||||||
void (*get_analogs)(struct idac_io_analog_state *state);
|
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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -80,13 +80,29 @@ void idac_di_config_load(struct idac_di_config *cfg, const wchar_t *filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FFB configuration
|
// FFB configuration
|
||||||
|
cfg->ffb_constant_force_strength = GetPrivateProfileIntW(
|
||||||
|
L"dinput",
|
||||||
|
L"constantForceStrength",
|
||||||
|
100,
|
||||||
|
filename);
|
||||||
|
|
||||||
cfg->center_spring_strength = GetPrivateProfileIntW(
|
cfg->ffb_rumble_strength = GetPrivateProfileIntW(
|
||||||
L"dinput",
|
L"dinput",
|
||||||
L"centerSpringStrength",
|
L"rumbleStrength",
|
||||||
30,
|
100,
|
||||||
filename);
|
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)
|
void idac_xi_config_load(struct idac_xi_config *cfg, const wchar_t *filename)
|
||||||
|
|
|
@ -25,7 +25,11 @@ struct idac_di_config {
|
||||||
bool reverse_accel_axis;
|
bool reverse_accel_axis;
|
||||||
|
|
||||||
// FFB configuration
|
// 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 {
|
struct idac_xi_config {
|
||||||
|
|
446
idacio/di-dev.c
446
idacio/di-dev.c
|
@ -1,134 +1,39 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <dinput.h>
|
#include <dinput.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "idacio/di-dev.h"
|
#include "idacio/di-dev.h"
|
||||||
|
|
||||||
#include "util/dprintf.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;
|
HRESULT hr;
|
||||||
|
|
||||||
assert(dev != NULL);
|
assert(dev != NULL);
|
||||||
assert(wnd != NULL);
|
assert(wnd != NULL);
|
||||||
|
|
||||||
hr = IDirectInputDevice8_SetCooperativeLevel(
|
idac_di_cfg = cfg;
|
||||||
dev,
|
idac_di_dev = dev;
|
||||||
wnd,
|
idac_di_wnd = wnd;
|
||||||
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
|
|
||||||
|
|
||||||
if (FAILED(hr)) {
|
return S_OK;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT idac_di_dev_poll(
|
HRESULT idac_di_dev_poll(
|
||||||
|
@ -167,3 +72,312 @@ HRESULT idac_di_dev_poll(
|
||||||
|
|
||||||
return hr;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -5,15 +5,26 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "idacio/config.h"
|
||||||
|
|
||||||
union idac_di_state {
|
union idac_di_state {
|
||||||
DIJOYSTATE st;
|
DIJOYSTATE st;
|
||||||
uint8_t bytes[sizeof(DIJOYSTATE)];
|
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);
|
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(
|
HRESULT idac_di_dev_poll(
|
||||||
IDirectInputDevice8W *dev,
|
IDirectInputDevice8W *dev,
|
||||||
HWND wnd,
|
HWND wnd,
|
||||||
union idac_di_state *out);
|
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);
|
||||||
|
|
38
idacio/di.c
38
idacio/di.c
|
@ -52,9 +52,14 @@ static const struct idac_di_axis idac_di_axes[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct idac_io_backend idac_di_backend = {
|
static const struct idac_io_backend idac_di_backend = {
|
||||||
.get_gamebtns = idac_di_get_buttons,
|
.get_gamebtns = idac_di_get_buttons,
|
||||||
.get_shifter = idac_di_get_shifter,
|
.get_shifter = idac_di_get_shifter,
|
||||||
.get_analogs = idac_di_get_analogs,
|
.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;
|
static HWND idac_di_wnd;
|
||||||
|
@ -62,7 +67,6 @@ static IDirectInput8W *idac_di_api;
|
||||||
static IDirectInputDevice8W *idac_di_dev;
|
static IDirectInputDevice8W *idac_di_dev;
|
||||||
static IDirectInputDevice8W *idac_di_pedals;
|
static IDirectInputDevice8W *idac_di_pedals;
|
||||||
static IDirectInputDevice8W *idac_di_shifter;
|
static IDirectInputDevice8W *idac_di_shifter;
|
||||||
static IDirectInputEffect *idac_di_fx;
|
|
||||||
static size_t idac_di_off_brake;
|
static size_t idac_di_off_brake;
|
||||||
static size_t idac_di_off_accel;
|
static size_t idac_di_off_accel;
|
||||||
static uint8_t idac_di_shift_dn;
|
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_use_pedals;
|
||||||
static bool idac_di_reverse_brake_axis;
|
static bool idac_di_reverse_brake_axis;
|
||||||
static bool idac_di_reverse_accel_axis;
|
static bool idac_di_reverse_accel_axis;
|
||||||
static uint16_t idac_di_center_spring_strength;
|
|
||||||
|
|
||||||
HRESULT idac_di_init(
|
HRESULT idac_di_init(
|
||||||
const struct idac_di_config *cfg,
|
const struct idac_di_config *cfg,
|
||||||
|
@ -105,7 +108,7 @@ HRESULT idac_di_init(
|
||||||
return hr;
|
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
|
particularly useful. idachook shorts this out by redirecting dinput8.dll
|
||||||
to a no-op implementation of DirectInput. However, idacio does need to
|
to a no-op implementation of DirectInput. However, idacio does need to
|
||||||
talk to the real operating system implementation of DirectInput without
|
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);
|
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)) {
|
if (FAILED(hr)) {
|
||||||
return 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') {
|
if (cfg->pedals_name[0] != L'\0') {
|
||||||
hr = IDirectInput8_EnumDevices(
|
hr = IDirectInput8_EnumDevices(
|
||||||
idac_di_api,
|
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];
|
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) {
|
return E_INVALIDARG;
|
||||||
dprintf("Wheel: Invalid center spring strength: %i\n", cfg->center_spring_strength);
|
}
|
||||||
|
|
||||||
|
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;
|
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;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ static bool idac_io_coin;
|
||||||
|
|
||||||
uint16_t idac_io_get_api_version(void)
|
uint16_t idac_io_get_api_version(void)
|
||||||
{
|
{
|
||||||
return 0x0101;
|
return 0x0102;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT idac_io_init(void)
|
HRESULT idac_io_init(void)
|
||||||
|
@ -62,6 +62,8 @@ void idac_io_get_opbtns(uint8_t *opbtn_out)
|
||||||
|
|
||||||
opbtn = 0;
|
opbtn = 0;
|
||||||
|
|
||||||
|
/* Common operator buttons, not backend-specific */
|
||||||
|
|
||||||
if (GetAsyncKeyState(idac_io_cfg.vk_test) & 0x8000) {
|
if (GetAsyncKeyState(idac_io_cfg.vk_test) & 0x8000) {
|
||||||
opbtn |= IDAC_IO_OPBTN_TEST;
|
opbtn |= IDAC_IO_OPBTN_TEST;
|
||||||
}
|
}
|
||||||
|
@ -159,3 +161,38 @@ void idac_io_led_set_leds(const uint8_t *rgb)
|
||||||
|
|
||||||
return;
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -10,3 +10,8 @@ EXPORTS
|
||||||
idac_io_led_set_fet_output
|
idac_io_led_set_fet_output
|
||||||
idac_io_led_gs_update
|
idac_io_led_gs_update
|
||||||
idac_io_led_set_leds
|
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
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -160,3 +161,51 @@ void idac_io_led_gs_update(const uint8_t *rgb);
|
||||||
Minimum API version: 0x0101 */
|
Minimum API version: 0x0101 */
|
||||||
|
|
||||||
void idac_io_led_set_leds(const uint8_t *rgb);
|
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);
|
||||||
|
|
55
idacio/xi.c
55
idacio/xi.c
|
@ -1,5 +1,3 @@
|
||||||
#include "idacio/xi.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
@ -7,22 +5,35 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <xinput.h>
|
#include <xinput.h>
|
||||||
|
|
||||||
|
#include "idacio/xi.h"
|
||||||
#include "idacio/backend.h"
|
#include "idacio/backend.h"
|
||||||
#include "idacio/config.h"
|
#include "idacio/config.h"
|
||||||
#include "idacio/idacio.h"
|
#include "idacio/idacio.h"
|
||||||
#include "idacio/shifter.h"
|
#include "idacio/shifter.h"
|
||||||
|
|
||||||
#include "util/dprintf.h"
|
#include "util/dprintf.h"
|
||||||
|
|
||||||
static void idac_xi_get_gamebtns(uint8_t *gamebtn_out);
|
static void idac_xi_get_gamebtns(uint8_t *gamebtn_out);
|
||||||
static void idac_xi_get_shifter(uint8_t *gear);
|
static void idac_xi_get_shifter(uint8_t *gear);
|
||||||
static void idac_xi_get_analogs(struct idac_io_analog_state *out);
|
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 HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg);
|
||||||
|
|
||||||
static const struct idac_io_backend idac_xi_backend = {
|
static const struct idac_io_backend idac_xi_backend = {
|
||||||
.get_gamebtns = idac_xi_get_gamebtns,
|
.get_gamebtns = idac_xi_get_gamebtns,
|
||||||
.get_shifter = idac_xi_get_shifter,
|
.get_shifter = idac_xi_get_shifter,
|
||||||
.get_analogs = idac_xi_get_analogs,
|
.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;
|
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;
|
return hr;
|
||||||
}
|
}
|
||||||
|
|
||||||
dprintf("XInput: Using XInput controller\n");
|
dprintf("IDACIO: Using XInput controller\n");
|
||||||
*backend = &idac_xi_backend;
|
*backend = &idac_xi_backend;
|
||||||
|
|
||||||
return S_OK;
|
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->accel = xi.Gamepad.bRightTrigger << 8;
|
||||||
out->brake = xi.Gamepad.bLeftTrigger << 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;
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,42 @@
|
||||||
#include "platform/config.h"
|
#include "platform/config.h"
|
||||||
#include "platform/platform.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(
|
void idz_dll_config_load(
|
||||||
struct idz_dll_config *cfg,
|
struct idz_dll_config *cfg,
|
||||||
const wchar_t *filename)
|
const wchar_t *filename)
|
||||||
|
@ -47,6 +83,8 @@ void idz_hook_config_load(
|
||||||
dvd_config_load(&cfg->dvd, filename);
|
dvd_config_load(&cfg->dvd, filename);
|
||||||
gfx_config_load(&cfg->gfx, filename);
|
gfx_config_load(&cfg->gfx, filename);
|
||||||
idz_dll_config_load(&cfg->dll, 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);
|
zinput_config_load(&cfg->zinput, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
#include "amex/amex.h"
|
#include "amex/amex.h"
|
||||||
|
|
||||||
#include "board/sg-reader.h"
|
#include "board/config.h"
|
||||||
|
#include "board/led15070.h"
|
||||||
|
|
||||||
#include "gfxhook/gfx.h"
|
#include "gfxhook/gfx.h"
|
||||||
|
|
||||||
|
@ -23,6 +24,8 @@ struct idz_hook_config {
|
||||||
struct dvd_config dvd;
|
struct dvd_config dvd;
|
||||||
struct gfx_config gfx;
|
struct gfx_config gfx;
|
||||||
struct idz_dll_config dll;
|
struct idz_dll_config dll;
|
||||||
|
struct ffb_config ffb;
|
||||||
|
struct led15070_config led15070;
|
||||||
struct zinput_config zinput;
|
struct zinput_config zinput;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include "idzhook/config.h"
|
#include "idzhook/config.h"
|
||||||
#include "idzhook/idz-dll.h"
|
#include "idzhook/idz-dll.h"
|
||||||
#include "idzhook/jvs.h"
|
#include "idzhook/jvs.h"
|
||||||
|
#include "idzhook/ffb.h"
|
||||||
#include "idzhook/zinput.h"
|
#include "idzhook/zinput.h"
|
||||||
|
|
||||||
#include "platform/platform.h"
|
#include "platform/platform.h"
|
||||||
|
@ -102,6 +103,12 @@ static DWORD CALLBACK idz_pre_startup(void)
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hr = idz_jvs_hook_init();
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
hr = amex_hook_init(&idz_hook_cfg.amex, idz_jvs_init);
|
hr = amex_hook_init(&idz_hook_cfg.amex, idz_jvs_init);
|
||||||
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
|
@ -114,6 +121,19 @@ static DWORD CALLBACK idz_pre_startup(void)
|
||||||
goto fail;
|
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 */
|
/* Initialize debug helpers */
|
||||||
|
|
||||||
spike_hook_init(L".\\segatools.ini");
|
spike_hook_init(L".\\segatools.ini");
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
|
@ -24,6 +24,33 @@ const struct dll_bind_sym idz_dll_syms[] = {
|
||||||
}, {
|
}, {
|
||||||
.sym = "idz_io_jvs_read_coin_counter",
|
.sym = "idz_io_jvs_read_coin_counter",
|
||||||
.off = offsetof(struct idz_dll, 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),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,15 @@ struct idz_dll {
|
||||||
void (*jvs_read_buttons)(uint8_t *opbtn, uint8_t *gamebtn);
|
void (*jvs_read_buttons)(uint8_t *opbtn, uint8_t *gamebtn);
|
||||||
void (*jvs_read_shifter)(uint8_t *gear);
|
void (*jvs_read_shifter)(uint8_t *gear);
|
||||||
void (*jvs_read_coin_counter)(uint16_t *total);
|
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 {
|
struct idz_dll_config {
|
||||||
|
|
|
@ -22,3 +22,12 @@ EXPORTS
|
||||||
idz_io_jvs_read_buttons
|
idz_io_jvs_read_buttons
|
||||||
idz_io_jvs_read_coin_counter
|
idz_io_jvs_read_coin_counter
|
||||||
idz_io_jvs_read_shifter
|
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
|
||||||
|
|
|
@ -24,11 +24,13 @@ static void idz_jvs_read_coin_counter(
|
||||||
void *ctx,
|
void *ctx,
|
||||||
uint8_t slot_no,
|
uint8_t slot_no,
|
||||||
uint16_t *out);
|
uint16_t *out);
|
||||||
|
static void idz_jvs_write_gpio(void *ctx, uint32_t state);
|
||||||
|
|
||||||
static const struct io3_ops idz_jvs_io3_ops = {
|
static const struct io3_ops idz_jvs_io3_ops = {
|
||||||
.read_switches = idz_jvs_read_switches,
|
.read_switches = idz_jvs_read_switches,
|
||||||
.read_analogs = idz_jvs_read_analogs,
|
.read_analogs = idz_jvs_read_analogs,
|
||||||
.read_coin_counter = idz_jvs_read_coin_counter,
|
.read_coin_counter = idz_jvs_read_coin_counter,
|
||||||
|
.write_gpio = idz_jvs_write_gpio
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint16_t idz_jvs_gear_signals[] = {
|
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;
|
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 idz_jvs_init(struct jvs_node **out)
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
assert(out != NULL);
|
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);
|
io3_init(&idz_jvs_io3, NULL, &idz_jvs_io3_ops, NULL);
|
||||||
*out = io3_to_jvs_node(&idz_jvs_io3);
|
*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);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -4,4 +4,6 @@
|
||||||
|
|
||||||
#include "jvs/jvs-bus.h"
|
#include "jvs/jvs-bus.h"
|
||||||
|
|
||||||
|
HRESULT idz_jvs_hook_init(void);
|
||||||
|
|
||||||
HRESULT idz_jvs_init(struct jvs_node **root);
|
HRESULT idz_jvs_init(struct jvs_node **root);
|
||||||
|
|
|
@ -32,5 +32,7 @@ shared_library(
|
||||||
'jvs.h',
|
'jvs.h',
|
||||||
'zinput.c',
|
'zinput.c',
|
||||||
'zinput.h',
|
'zinput.h',
|
||||||
|
'ffb.c',
|
||||||
|
'ffb.h',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,4 +8,9 @@ struct idz_io_backend {
|
||||||
void (*jvs_read_buttons)(uint8_t *gamebtn);
|
void (*jvs_read_buttons)(uint8_t *gamebtn);
|
||||||
void (*jvs_read_shifter)(uint8_t *gear);
|
void (*jvs_read_shifter)(uint8_t *gear);
|
||||||
void (*jvs_read_analogs)(struct idz_io_analog_state *state);
|
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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -78,12 +78,29 @@ void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FFB configuration
|
// FFB configuration
|
||||||
|
cfg->ffb_constant_force_strength = GetPrivateProfileIntW(
|
||||||
|
L"dinput",
|
||||||
|
L"constantForceStrength",
|
||||||
|
100,
|
||||||
|
filename);
|
||||||
|
|
||||||
cfg->center_spring_strength = GetPrivateProfileIntW(
|
cfg->ffb_rumble_strength = GetPrivateProfileIntW(
|
||||||
L"dinput",
|
L"dinput",
|
||||||
L"centerSpringStrength",
|
L"rumbleStrength",
|
||||||
30,
|
100,
|
||||||
filename);
|
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)
|
void idz_xi_config_load(struct idz_xi_config *cfg, const wchar_t *filename)
|
||||||
|
|
|
@ -23,7 +23,11 @@ struct idz_di_config {
|
||||||
bool reverse_accel_axis;
|
bool reverse_accel_axis;
|
||||||
|
|
||||||
// FFB configuration
|
// 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 {
|
struct idz_xi_config {
|
||||||
|
|
446
idzio/di-dev.c
446
idzio/di-dev.c
|
@ -1,134 +1,39 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <dinput.h>
|
#include <dinput.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "idzio/di-dev.h"
|
#include "idzio/di-dev.h"
|
||||||
|
|
||||||
#include "util/dprintf.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;
|
HRESULT hr;
|
||||||
|
|
||||||
assert(dev != NULL);
|
assert(dev != NULL);
|
||||||
assert(wnd != NULL);
|
assert(wnd != NULL);
|
||||||
|
|
||||||
hr = IDirectInputDevice8_SetCooperativeLevel(
|
idz_di_cfg = cfg;
|
||||||
dev,
|
idz_di_dev = dev;
|
||||||
wnd,
|
idz_di_wnd = wnd;
|
||||||
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
|
|
||||||
|
|
||||||
if (FAILED(hr)) {
|
return S_OK;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT idz_di_dev_poll(
|
HRESULT idz_di_dev_poll(
|
||||||
|
@ -167,3 +72,312 @@ HRESULT idz_di_dev_poll(
|
||||||
|
|
||||||
return hr;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -5,15 +5,26 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "idzio/config.h"
|
||||||
|
|
||||||
union idz_di_state {
|
union idz_di_state {
|
||||||
DIJOYSTATE st;
|
DIJOYSTATE st;
|
||||||
uint8_t bytes[sizeof(DIJOYSTATE)];
|
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);
|
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(
|
HRESULT idz_di_dev_poll(
|
||||||
IDirectInputDevice8W *dev,
|
IDirectInputDevice8W *dev,
|
||||||
HWND wnd,
|
HWND wnd,
|
||||||
union idz_di_state *out);
|
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);
|
||||||
|
|
29
idzio/di.c
29
idzio/di.c
|
@ -55,6 +55,11 @@ static const struct idz_io_backend idz_di_backend = {
|
||||||
.jvs_read_buttons = idz_di_jvs_read_buttons,
|
.jvs_read_buttons = idz_di_jvs_read_buttons,
|
||||||
.jvs_read_shifter = idz_di_jvs_read_shifter,
|
.jvs_read_shifter = idz_di_jvs_read_shifter,
|
||||||
.jvs_read_analogs = idz_di_jvs_read_analogs,
|
.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;
|
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_use_pedals;
|
||||||
static bool idz_di_reverse_brake_axis;
|
static bool idz_di_reverse_brake_axis;
|
||||||
static bool idz_di_reverse_accel_axis;
|
static bool idz_di_reverse_accel_axis;
|
||||||
static uint16_t idz_di_center_spring_strength;
|
|
||||||
|
|
||||||
HRESULT idz_di_init(
|
HRESULT idz_di_init(
|
||||||
const struct idz_di_config *cfg,
|
const struct idz_di_config *cfg,
|
||||||
|
@ -166,16 +170,12 @@ HRESULT idz_di_init(
|
||||||
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
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)) {
|
if (FAILED(hr)) {
|
||||||
return 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') {
|
if (cfg->pedals_name[0] != L'\0') {
|
||||||
hr = IDirectInput8_EnumDevices(
|
hr = IDirectInput8_EnumDevices(
|
||||||
idz_di_api,
|
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];
|
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) {
|
return E_INVALIDARG;
|
||||||
dprintf("Wheel: Invalid center spring strength: %i\n", cfg->center_spring_strength);
|
}
|
||||||
|
|
||||||
|
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;
|
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;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ static uint16_t idz_io_coins;
|
||||||
|
|
||||||
uint16_t idz_io_get_api_version(void)
|
uint16_t idz_io_get_api_version(void)
|
||||||
{
|
{
|
||||||
return 0x0100;
|
return 0x0102;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT idz_io_jvs_init(void)
|
HRESULT idz_io_jvs_init(void)
|
||||||
|
@ -123,3 +123,79 @@ void idz_io_jvs_read_coin_counter(uint16_t *out)
|
||||||
|
|
||||||
*out = idz_io_coins;
|
*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);
|
||||||
|
}
|
||||||
|
|
|
@ -6,3 +6,12 @@ EXPORTS
|
||||||
idz_io_jvs_read_buttons
|
idz_io_jvs_read_buttons
|
||||||
idz_io_jvs_read_coin_counter
|
idz_io_jvs_read_coin_counter
|
||||||
idz_io_jvs_read_shifter
|
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
|
||||||
|
|
116
idzio/idzio.h
116
idzio/idzio.h
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -30,6 +31,17 @@ enum {
|
||||||
IDZ_IO_GAMEBTN_VIEW_CHANGE = 0x20,
|
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 {
|
struct idz_io_analog_state {
|
||||||
/* Current steering wheel position, where zero is the centered position.
|
/* 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 */
|
Minimum API version: 0x0100 */
|
||||||
|
|
||||||
void idz_io_jvs_read_coin_counter(uint16_t *total);
|
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);
|
||||||
|
|
43
idzio/xi.c
43
idzio/xi.c
|
@ -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_shifter(uint8_t *gear);
|
||||||
static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out);
|
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 HRESULT idz_xi_config_apply(const struct idz_xi_config *cfg);
|
||||||
|
|
||||||
static const struct idz_io_backend idz_xi_backend = {
|
static const struct idz_io_backend idz_xi_backend = {
|
||||||
.jvs_read_buttons = idz_xi_jvs_read_buttons,
|
.jvs_read_buttons = idz_xi_jvs_read_buttons,
|
||||||
.jvs_read_shifter = idz_xi_jvs_read_shifter,
|
.jvs_read_shifter = idz_xi_jvs_read_shifter,
|
||||||
.jvs_read_analogs = idz_xi_jvs_read_analogs,
|
.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;
|
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->accel = xi.Gamepad.bRightTrigger << 8;
|
||||||
out->brake = xi.Gamepad.bLeftTrigger << 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;
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,43 @@
|
||||||
#include "platform/config.h"
|
#include "platform/config.h"
|
||||||
#include "platform/platform.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(
|
void swdc_dll_config_load(
|
||||||
struct swdc_dll_config *cfg,
|
struct swdc_dll_config *cfg,
|
||||||
const wchar_t *filename)
|
const wchar_t *filename)
|
||||||
|
@ -29,6 +66,14 @@ void swdc_dll_config_load(
|
||||||
filename);
|
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(
|
void swdc_hook_config_load(
|
||||||
struct swdc_hook_config *cfg,
|
struct swdc_hook_config *cfg,
|
||||||
const wchar_t *filename)
|
const wchar_t *filename)
|
||||||
|
@ -42,13 +87,7 @@ void swdc_hook_config_load(
|
||||||
zinput_config_load(&cfg->zinput, filename);
|
zinput_config_load(&cfg->zinput, filename);
|
||||||
dvd_config_load(&cfg->dvd, filename);
|
dvd_config_load(&cfg->dvd, filename);
|
||||||
io4_config_load(&cfg->io4, 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);
|
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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#include "board/config.h"
|
#include "board/config.h"
|
||||||
|
#include "board/led15070.h"
|
||||||
|
|
||||||
#include "hooklib/dvd.h"
|
#include "hooklib/dvd.h"
|
||||||
|
|
||||||
|
@ -17,7 +18,9 @@ struct swdc_hook_config {
|
||||||
struct aime_config aime;
|
struct aime_config aime;
|
||||||
struct dvd_config dvd;
|
struct dvd_config dvd;
|
||||||
struct io4_config io4;
|
struct io4_config io4;
|
||||||
|
struct ffb_config ffb;
|
||||||
struct vfd_config vfd;
|
struct vfd_config vfd;
|
||||||
|
struct led15070_config led15070;
|
||||||
struct swdc_dll_config dll;
|
struct swdc_dll_config dll;
|
||||||
struct zinput_config zinput;
|
struct zinput_config zinput;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
WITH
|
WITH
|
||||||
838-15416 Indicator BD LED Board
|
838-15416 Indicator BD LED Board
|
||||||
COM1: 838-15069 MOTOR DRIVE BD RS232/422 board
|
COM1: 838-15069 MOTOR DRIVE BD RS232/422 board
|
||||||
COM2: 837-15396 "Gen 3" Aime reader
|
COM2: 837-15070-04 IC BD LED controller board
|
||||||
COM3: 837-15070-04 IC BD LED controller board
|
COM3: 837-15396 "Gen 3" Aime reader
|
||||||
COM4: 200-6275 VFD GP1232A02A FUTABA board
|
COM4: 200-6275 VFD GP1232A02A FUTABA board
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@
|
||||||
#include "swdchook/config.h"
|
#include "swdchook/config.h"
|
||||||
#include "swdchook/swdc-dll.h"
|
#include "swdchook/swdc-dll.h"
|
||||||
#include "swdchook/io4.h"
|
#include "swdchook/io4.h"
|
||||||
|
#include "swdchook/ffb.h"
|
||||||
|
|
||||||
#include "platform/platform.h"
|
#include "platform/platform.h"
|
||||||
|
|
||||||
|
@ -91,6 +92,20 @@ static DWORD CALLBACK swdc_pre_startup(void)
|
||||||
goto fail;
|
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 */
|
/* Hook external DLL APIs */
|
||||||
|
|
||||||
zinput_hook_init(&swdc_hook_cfg.zinput);
|
zinput_hook_init(&swdc_hook_cfg.zinput);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
|
@ -15,10 +15,12 @@ static HRESULT init_mmf(void);
|
||||||
static void swdc_set_gamebtns(uint16_t value);
|
static void swdc_set_gamebtns(uint16_t value);
|
||||||
|
|
||||||
static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state);
|
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 uint16_t coins;
|
||||||
|
|
||||||
static const struct io4_ops swdc_io4_ops = {
|
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) {
|
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;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -28,5 +28,7 @@ shared_library(
|
||||||
'io4.h',
|
'io4.h',
|
||||||
'zinput.c',
|
'zinput.c',
|
||||||
'zinput.h',
|
'zinput.h',
|
||||||
|
'ffb.c',
|
||||||
|
'ffb.h'
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,6 +21,33 @@ const struct dll_bind_sym swdc_dll_syms[] = {
|
||||||
}, {
|
}, {
|
||||||
.sym = "swdc_io_get_analogs",
|
.sym = "swdc_io_get_analogs",
|
||||||
.off = offsetof(struct swdc_dll, 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),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,15 @@ struct swdc_dll {
|
||||||
void (*get_opbtns)(uint8_t *opbtn);
|
void (*get_opbtns)(uint8_t *opbtn);
|
||||||
void (*get_gamebtns)(uint16_t *gamebtn);
|
void (*get_gamebtns)(uint16_t *gamebtn);
|
||||||
void (*get_analogs)(struct swdc_io_analog_state *out);
|
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 {
|
struct swdc_dll_config {
|
||||||
|
|
|
@ -16,3 +16,12 @@ EXPORTS
|
||||||
swdc_io_get_opbtns
|
swdc_io_get_opbtns
|
||||||
swdc_io_get_gamebtns
|
swdc_io_get_gamebtns
|
||||||
swdc_io_get_analogs
|
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
|
||||||
|
|
|
@ -8,4 +8,9 @@ struct swdc_io_backend {
|
||||||
void (*get_opbtns)(uint8_t *opbtn);
|
void (*get_opbtns)(uint8_t *opbtn);
|
||||||
void (*get_gamebtns)(uint16_t *gamebtn);
|
void (*get_gamebtns)(uint16_t *gamebtn);
|
||||||
void (*get_analogs)(struct swdc_io_analog_state *state);
|
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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -69,12 +69,29 @@ void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename)
|
||||||
filename);
|
filename);
|
||||||
|
|
||||||
// FFB configuration
|
// FFB configuration
|
||||||
|
cfg->ffb_constant_force_strength = GetPrivateProfileIntW(
|
||||||
|
L"dinput",
|
||||||
|
L"constantForceStrength",
|
||||||
|
100,
|
||||||
|
filename);
|
||||||
|
|
||||||
cfg->center_spring_strength = GetPrivateProfileIntW(
|
cfg->ffb_rumble_strength = GetPrivateProfileIntW(
|
||||||
L"dinput",
|
L"dinput",
|
||||||
L"centerSpringStrength",
|
L"rumbleStrength",
|
||||||
30,
|
100,
|
||||||
filename);
|
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)
|
void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename)
|
||||||
|
|
|
@ -21,7 +21,11 @@ struct swdc_di_config {
|
||||||
bool reverse_accel_axis;
|
bool reverse_accel_axis;
|
||||||
|
|
||||||
// FFB configuration
|
// 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 {
|
struct swdc_xi_config {
|
||||||
|
|
446
swdcio/di-dev.c
446
swdcio/di-dev.c
|
@ -1,134 +1,39 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <dinput.h>
|
#include <dinput.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "swdcio/di-dev.h"
|
#include "swdcio/di-dev.h"
|
||||||
|
|
||||||
#include "util/dprintf.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;
|
HRESULT hr;
|
||||||
|
|
||||||
assert(dev != NULL);
|
assert(dev != NULL);
|
||||||
assert(wnd != NULL);
|
assert(wnd != NULL);
|
||||||
|
|
||||||
hr = IDirectInputDevice8_SetCooperativeLevel(
|
swdc_di_cfg = cfg;
|
||||||
dev,
|
swdc_di_dev = dev;
|
||||||
wnd,
|
swdc_di_wnd = wnd;
|
||||||
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
|
|
||||||
|
|
||||||
if (FAILED(hr)) {
|
return S_OK;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT swdc_di_dev_poll(
|
HRESULT swdc_di_dev_poll(
|
||||||
|
@ -167,3 +72,312 @@ HRESULT swdc_di_dev_poll(
|
||||||
|
|
||||||
return hr;
|
return hr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) {
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
assert(dev != NULL);
|
||||||
|
assert(wnd != NULL);
|
||||||
|
|
||||||
|
hr = IDirectInputDevice8_SetCooperativeLevel(
|
||||||
|
dev,
|
||||||
|
wnd,
|
||||||
|
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int) hr);
|
||||||
|
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick);
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
dprintf("DirectInput: SetDataFormat failed: %08x\n", (int) hr);
|
||||||
|
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = IDirectInputDevice8_Acquire(dev);
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
dprintf("DirectInput: Acquire failed: %08x\n", (int) hr);
|
||||||
|
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT swdc_di_ffb_init(void)
|
||||||
|
{
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
hr = swdc_di_dev_start(swdc_di_dev, swdc_di_wnd);
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void swdc_di_ffb_toggle(bool active)
|
||||||
|
{
|
||||||
|
if (active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop and release all effects */
|
||||||
|
/* I never programmed DirectInput Effects, so this might be bad practice. */
|
||||||
|
if (swdc_di_fx != NULL) {
|
||||||
|
IDirectInputEffect_Stop(swdc_di_fx);
|
||||||
|
IDirectInputEffect_Release(swdc_di_fx);
|
||||||
|
swdc_di_fx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swdc_di_fx_rumble != NULL) {
|
||||||
|
IDirectInputEffect_Stop(swdc_di_fx_rumble);
|
||||||
|
IDirectInputEffect_Release(swdc_di_fx_rumble);
|
||||||
|
swdc_di_fx_rumble = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swdc_di_fx_damper != NULL) {
|
||||||
|
IDirectInputEffect_Stop(swdc_di_fx_damper);
|
||||||
|
IDirectInputEffect_Release(swdc_di_fx_damper);
|
||||||
|
swdc_di_fx_damper = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void swdc_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force)
|
||||||
|
{
|
||||||
|
/* DI expects a magnitude in the range of -10.000 to 10.000 */
|
||||||
|
uint16_t ffb_strength = swdc_di_cfg->ffb_constant_force_strength * 100;
|
||||||
|
if (ffb_strength == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD axis;
|
||||||
|
LONG direction;
|
||||||
|
DIEFFECT fx;
|
||||||
|
DICONSTANTFORCE cf;
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
/* Direction 0: move to the right, 1: move to the left */
|
||||||
|
LONG magnitude = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength);
|
||||||
|
cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude;
|
||||||
|
|
||||||
|
axis = DIJOFS_X;
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
|
@ -5,15 +5,26 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "swdcio/config.h"
|
||||||
|
|
||||||
union swdc_di_state {
|
union swdc_di_state {
|
||||||
DIJOYSTATE st;
|
DIJOYSTATE st;
|
||||||
uint8_t bytes[sizeof(DIJOYSTATE)];
|
uint8_t bytes[sizeof(DIJOYSTATE)];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
HRESULT swdc_di_dev_init(
|
||||||
|
const struct swdc_di_config *cfg,
|
||||||
|
IDirectInputDevice8W *dev,
|
||||||
|
HWND wnd);
|
||||||
|
|
||||||
HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
|
HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
|
||||||
void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength);
|
|
||||||
HRESULT swdc_di_dev_poll(
|
HRESULT swdc_di_dev_poll(
|
||||||
IDirectInputDevice8W *dev,
|
IDirectInputDevice8W *dev,
|
||||||
HWND wnd,
|
HWND wnd,
|
||||||
union swdc_di_state *out);
|
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);
|
||||||
|
|
33
swdcio/di.c
33
swdcio/di.c
|
@ -45,8 +45,13 @@ static const struct swdc_di_axis swdc_di_axes[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct swdc_io_backend swdc_di_backend = {
|
static const struct swdc_io_backend swdc_di_backend = {
|
||||||
.get_gamebtns = swdc_di_get_buttons,
|
.get_gamebtns = swdc_di_get_buttons,
|
||||||
.get_analogs = swdc_di_get_analogs,
|
.get_analogs = swdc_di_get_analogs,
|
||||||
|
.ffb_init = swdc_di_ffb_init,
|
||||||
|
.ffb_toggle = swdc_di_ffb_toggle,
|
||||||
|
.ffb_constant_force = swdc_di_ffb_constant_force,
|
||||||
|
.ffb_rumble = swdc_di_ffb_rumble,
|
||||||
|
.ffb_damper = swdc_di_ffb_damper
|
||||||
};
|
};
|
||||||
|
|
||||||
static HWND swdc_di_wnd;
|
static 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_use_pedals;
|
||||||
static bool swdc_di_reverse_brake_axis;
|
static bool swdc_di_reverse_brake_axis;
|
||||||
static bool swdc_di_reverse_accel_axis;
|
static bool swdc_di_reverse_accel_axis;
|
||||||
static uint16_t swdc_di_center_spring_strength;
|
|
||||||
|
|
||||||
HRESULT swdc_di_init(
|
HRESULT swdc_di_init(
|
||||||
const struct swdc_di_config *cfg,
|
const struct swdc_di_config *cfg,
|
||||||
|
@ -160,16 +164,12 @@ HRESULT swdc_di_init(
|
||||||
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
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)) {
|
if (FAILED(hr)) {
|
||||||
return 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') {
|
if (cfg->pedals_name[0] != L'\0') {
|
||||||
hr = IDirectInput8_EnumDevices(
|
hr = IDirectInput8_EnumDevices(
|
||||||
swdc_di_api,
|
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_brake_axis = cfg->reverse_brake_axis;
|
||||||
swdc_di_reverse_accel_axis = cfg->reverse_accel_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) {
|
return E_INVALIDARG;
|
||||||
dprintf("Wheel: Invalid center spring strength: %i\n", cfg->center_spring_strength);
|
}
|
||||||
|
|
||||||
|
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;
|
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;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ static bool swdc_io_coin;
|
||||||
|
|
||||||
uint16_t swdc_io_get_api_version(void)
|
uint16_t swdc_io_get_api_version(void)
|
||||||
{
|
{
|
||||||
return 0x0100;
|
return 0x0102;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT swdc_io_init(void)
|
HRESULT swdc_io_init(void)
|
||||||
|
@ -62,6 +62,8 @@ void swdc_io_get_opbtns(uint8_t *opbtn_out)
|
||||||
|
|
||||||
opbtn = 0;
|
opbtn = 0;
|
||||||
|
|
||||||
|
/* Common operator buttons, not backend-specific */
|
||||||
|
|
||||||
if (GetAsyncKeyState(swdc_io_cfg.vk_test) & 0x8000) {
|
if (GetAsyncKeyState(swdc_io_cfg.vk_test) & 0x8000) {
|
||||||
opbtn |= SWDC_IO_OPBTN_TEST;
|
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->accel = tmp.accel;
|
||||||
out->brake = tmp.brake;
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -5,3 +5,12 @@ EXPORTS
|
||||||
swdc_io_get_opbtns
|
swdc_io_get_opbtns
|
||||||
swdc_io_get_gamebtns
|
swdc_io_get_gamebtns
|
||||||
swdc_io_get_analogs
|
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
|
||||||
|
|
118
swdcio/swdcio.h
118
swdcio/swdcio.h
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -26,6 +27,17 @@ enum {
|
||||||
SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT = 0x800,
|
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 {
|
struct swdc_io_analog_state {
|
||||||
/* Current steering wheel position, where zero is the centered position.
|
/* Current steering wheel position, where zero is the centered position.
|
||||||
|
|
||||||
|
@ -45,7 +57,7 @@ struct swdc_io_analog_state {
|
||||||
uint16_t brake;
|
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
|
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
|
the major version and the low byte is the minor version (as defined by the
|
||||||
Semantic Versioning standard).
|
Semantic Versioning standard).
|
||||||
|
@ -89,3 +101,107 @@ void swdc_io_get_gamebtns(uint16_t *gamebtn);
|
||||||
Minimum API version: 0x0100 */
|
Minimum API version: 0x0100 */
|
||||||
|
|
||||||
void swdc_io_get_analogs(struct swdc_io_analog_state *out);
|
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);
|
||||||
|
|
47
swdcio/xi.c
47
swdcio/xi.c
|
@ -16,11 +16,22 @@
|
||||||
static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out);
|
static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out);
|
||||||
static void swdc_xi_get_analogs(struct swdc_io_analog_state *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 HRESULT swdc_xi_config_apply(const struct swdc_xi_config *cfg);
|
||||||
|
|
||||||
static const struct swdc_io_backend swdc_xi_backend = {
|
static const struct swdc_io_backend swdc_xi_backend = {
|
||||||
.get_gamebtns = swdc_xi_get_gamebtns,
|
.get_gamebtns = swdc_xi_get_gamebtns,
|
||||||
.get_analogs = swdc_xi_get_analogs,
|
.get_analogs = swdc_xi_get_analogs,
|
||||||
|
.ffb_init = swdc_xi_ffb_init,
|
||||||
|
.ffb_toggle = swdc_xi_ffb_toggle,
|
||||||
|
.ffb_constant_force = swdc_xi_ffb_constant_force,
|
||||||
|
.ffb_rumble = swdc_xi_ffb_rumble,
|
||||||
|
.ffb_damper = swdc_xi_ffb_damper
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool swdc_xi_single_stick_steering;
|
static bool swdc_xi_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->accel = xi.Gamepad.bRightTrigger << 8;
|
||||||
out->brake = xi.Gamepad.bLeftTrigger << 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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue