diff --git a/board/ffb.c b/board/ffb.c index 48e15ad..e7a11f7 100644 --- a/board/ffb.c +++ b/board/ffb.c @@ -138,6 +138,7 @@ static HRESULT ffb_req_dispatch(const union ffb_req_any *req) 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. */ @@ -206,7 +207,7 @@ static HRESULT ffb_req_rumble(const uint8_t *bytes) max_period = period; } - // dprintf("FFB: Rumble Period: %d (Min %d, Max %d), Strength: %d (Max: %d)\n", period, min_period, max_period, force, max_rumble); + // 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); } diff --git a/dist/idz/segatools.ini b/dist/idz/segatools.ini index c37bab6..126a5b7 100644 --- a/dist/idz/segatools.ini +++ b/dist/idz/segatools.ini @@ -69,6 +69,20 @@ region=4 ; exactly one machine and set this to 0 on all others. dipsw1=1 +[ffb] +; Enable force feedback (838-15069) board emulation. This is required for +; both DirectInput and XInput steering wheel effects. +enable=1 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15070] +; Enable emulation of the 837-15070-02 controlled lights, which handle the +; cabinet and seat LEDs. +enable=1 + ; ----------------------------------------------------------------------------- ; Misc. hooks settings ; ----------------------------------------------------------------------------- @@ -212,6 +226,21 @@ reverseAccelAxis=0 reverseBrakeAxis=0 ; Force feedback settings. -; Strength of the force feedback spring effect in percent. Possible values -; are 0-100. -centerSpringStrength=30 +; Only works when FFB board emulation is enabled! +; +; It is recommended to change the strength inside the Game Test Mode! +; +; These settings are only used when using DirectInput for the wheel. +; The values are in the range 0%-100%, where 0 disables the effect and +; 100 is the maximum. + +; Constant force strength, used for centering spring effect. +constantForceStrength=100 +; Damper strength, used for steering wheel damper effect. +damperStrength=100 + +; Rumble strength, used for road surface effects. +; WARNING: THIS WILL CRASH ON FANATEC (maybe even more) WHEELS! +rumbleStrength=100 +; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect. +rumbleDuration=1000 \ No newline at end of file diff --git a/dist/idz/start.bat b/dist/idz/start.bat index 2d3d9c3..d64aa9b 100644 --- a/dist/idz/start.bat +++ b/dist/idz/start.bat @@ -2,10 +2,11 @@ pushd %~dp0 -inject -k idzhook.dll InitialD0_DX11_Nu.exe +start /min "AM Daemon" inject -d -k idzhook.dll amdaemon.exe -c configDHCP_Final_Common.json configDHCP_Final_JP.json configDHCP_Final_JP_ST1.json configDHCP_Final_JP_ST2.json configDHCP_Final_EX.json configDHCP_Final_EX_ST1.json configDHCP_Final_EX_ST2.json + rem Set dipsw1=0 and uncomment the ServerBox for in store battle? rem inject -k idzhook.dll ServerBoxD8_Nu_x64.exe -inject -d -k idzhook.dll amdaemon.exe -c configDHCP_Final_Common.json configDHCP_Final_JP.json configDHCP_Final_JP_ST1.json configDHCP_Final_JP_ST2.json configDHCP_Final_EX.json configDHCP_Final_EX_ST1.json configDHCP_Final_EX_ST2.json +inject -d -k idzhook.dll InitialD0_DX11_Nu.exe taskkill /im ServerBoxD8_Nu_x64.exe > nul 2>&1 diff --git a/idachook/dllmain.c b/idachook/dllmain.c index 3336690..2e82882 100644 --- a/idachook/dllmain.c +++ b/idachook/dllmain.c @@ -5,7 +5,9 @@ USB: 837-15257 "Type 4" I/O Board COM1: 838-15069 MOTOR DRIVE BD RS232/422 Board - COM2: 837-15070-02 IC BD LED Controller Board + COM2: 837-15070-02 IC BD LED Controller Board (DIPSW2 OFF) + OR + 837-15070-04 IC BD LED Controller Board (DIPSW2 ON) COM3: 837-15286 "Gen 2" Aime Reader (DIPSW2 OFF) OR 837-15396 "Gen 3" Aime Reader (DIPSW2 ON) diff --git a/idachook/io4.c b/idachook/io4.c index c13e7a5..1e4d990 100644 --- a/idachook/io4.c +++ b/idachook/io4.c @@ -133,6 +133,8 @@ static HRESULT idac_io4_poll(void *ctx, struct io4_state *state) static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len) { + assert(idac_dll.led_set_leds != NULL); + // Just fast fail if there aren't enough bytes in the payload if (len < 3) return S_OK; diff --git a/idzhook/config.c b/idzhook/config.c index db83b9f..7948c84 100644 --- a/idzhook/config.c +++ b/idzhook/config.c @@ -18,6 +18,42 @@ #include "platform/config.h" #include "platform/platform.h" +void led15070_config_load(struct led15070_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + cfg->enable = GetPrivateProfileIntW(L"led15070", L"enable", 1, filename); + cfg->port_no = GetPrivateProfileIntW(L"led15070", L"portNo", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15070", L"fwVer", 0x90, filename); + /* TODO: Unknown, no firmware file available */ + cfg->fw_sum = GetPrivateProfileIntW(L"led15070", L"fwSum", 0x0000, filename); + + GetPrivateProfileStringW( + L"led15070", + L"boardNumber", + L"15070-02", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15070", + L"eepromPath", + L"DEVICE", + cfg->eeprom_path, + _countof(cfg->eeprom_path), + filename); +} + void idz_dll_config_load( struct idz_dll_config *cfg, const wchar_t *filename) @@ -47,6 +83,8 @@ void idz_hook_config_load( dvd_config_load(&cfg->dvd, filename); gfx_config_load(&cfg->gfx, filename); idz_dll_config_load(&cfg->dll, filename); + ffb_config_load(&cfg->ffb, filename); + led15070_config_load(&cfg->led15070, filename); zinput_config_load(&cfg->zinput, filename); } diff --git a/idzhook/config.h b/idzhook/config.h index 0684949..47174ad 100644 --- a/idzhook/config.h +++ b/idzhook/config.h @@ -5,7 +5,8 @@ #include "amex/amex.h" -#include "board/sg-reader.h" +#include "board/config.h" +#include "board/led15070.h" #include "gfxhook/gfx.h" @@ -23,6 +24,8 @@ struct idz_hook_config { struct dvd_config dvd; struct gfx_config gfx; struct idz_dll_config dll; + struct ffb_config ffb; + struct led15070_config led15070; struct zinput_config zinput; }; diff --git a/idzhook/dllmain.c b/idzhook/dllmain.c index cb2fdde..e9d807a 100644 --- a/idzhook/dllmain.c +++ b/idzhook/dllmain.c @@ -33,6 +33,7 @@ #include "idzhook/config.h" #include "idzhook/idz-dll.h" #include "idzhook/jvs.h" +#include "idzhook/ffb.h" #include "idzhook/zinput.h" #include "platform/platform.h" @@ -102,6 +103,12 @@ static DWORD CALLBACK idz_pre_startup(void) goto fail; } + hr = idz_jvs_hook_init(); + + if (FAILED(hr)) { + goto fail; + } + hr = amex_hook_init(&idz_hook_cfg.amex, idz_jvs_init); if (FAILED(hr)) { @@ -114,6 +121,19 @@ static DWORD CALLBACK idz_pre_startup(void) goto fail; } + hr = idz_ffb_hook_init(&idz_hook_cfg.ffb, 1); + + if (FAILED(hr)) { + goto fail; + } + + hr = led15070_hook_init(&idz_hook_cfg.led15070, idz_dll.led_init, + idz_dll.led_set_fet_output, NULL, idz_dll.led_gs_update, 11, 1); + + if (FAILED(hr)) { + goto fail; + } + /* Initialize debug helpers */ spike_hook_init(L".\\segatools.ini"); diff --git a/idzhook/ffb.c b/idzhook/ffb.c new file mode 100644 index 0000000..7ac1086 --- /dev/null +++ b/idzhook/ffb.c @@ -0,0 +1,59 @@ +#include + +#include +#include +#include +#include + +#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); +} diff --git a/idzhook/ffb.h b/idzhook/ffb.h new file mode 100644 index 0000000..eaee98c --- /dev/null +++ b/idzhook/ffb.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/ffb.h" + +HRESULT idz_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no); diff --git a/idzhook/idz-dll.c b/idzhook/idz-dll.c index eb1b6e1..a497319 100644 --- a/idzhook/idz-dll.c +++ b/idzhook/idz-dll.c @@ -24,6 +24,33 @@ const struct dll_bind_sym idz_dll_syms[] = { }, { .sym = "idz_io_jvs_read_coin_counter", .off = offsetof(struct idz_dll, jvs_read_coin_counter), + }, { + .sym = "idz_io_led_init", + .off = offsetof(struct idz_dll, led_init), + }, { + .sym = "idz_io_led_set_fet_output", + .off = offsetof(struct idz_dll, led_set_fet_output), + }, { + .sym = "idz_io_led_gs_update", + .off = offsetof(struct idz_dll, led_gs_update), + }, { + .sym = "idz_io_led_set_leds", + .off = offsetof(struct idz_dll, led_set_leds), + }, { + .sym = "idz_io_ffb_init", + .off = offsetof(struct idz_dll, ffb_init), + }, { + .sym = "idz_io_ffb_toggle", + .off = offsetof(struct idz_dll, ffb_toggle), + }, { + .sym = "idz_io_ffb_constant_force", + .off = offsetof(struct idz_dll, ffb_constant_force), + }, { + .sym = "idz_io_ffb_rumble", + .off = offsetof(struct idz_dll, ffb_rumble), + }, { + .sym = "idz_io_ffb_damper", + .off = offsetof(struct idz_dll, ffb_damper), } }; diff --git a/idzhook/idz-dll.h b/idzhook/idz-dll.h index e67cb45..e827f8e 100644 --- a/idzhook/idz-dll.h +++ b/idzhook/idz-dll.h @@ -11,6 +11,15 @@ struct idz_dll { void (*jvs_read_buttons)(uint8_t *opbtn, uint8_t *gamebtn); void (*jvs_read_shifter)(uint8_t *gear); void (*jvs_read_coin_counter)(uint16_t *total); + HRESULT (*led_init)(void); + void (*led_set_fet_output)(const uint8_t *rgb); + void (*led_gs_update)(const uint8_t *rgb); + void (*led_set_leds)(const uint8_t *rgb); + HRESULT (*ffb_init)(void); + void (*ffb_toggle)(bool active); + void (*ffb_constant_force)(uint8_t direction, uint8_t force); + void (*ffb_rumble)(uint8_t period, uint8_t force); + void (*ffb_damper)(uint8_t force); }; struct idz_dll_config { diff --git a/idzhook/idzhook.def b/idzhook/idzhook.def index d8db3b0..53e01c6 100644 --- a/idzhook/idzhook.def +++ b/idzhook/idzhook.def @@ -22,3 +22,12 @@ EXPORTS idz_io_jvs_read_buttons idz_io_jvs_read_coin_counter idz_io_jvs_read_shifter + idz_io_led_init + idz_io_led_set_fet_output + idz_io_led_gs_update + idz_io_led_set_leds + idz_io_ffb_init + idz_io_ffb_toggle + idz_io_ffb_constant_force + idz_io_ffb_rumble + idz_io_ffb_damper diff --git a/idzhook/jvs.c b/idzhook/jvs.c index ab2f6aa..3531d55 100644 --- a/idzhook/jvs.c +++ b/idzhook/jvs.c @@ -24,11 +24,13 @@ static void idz_jvs_read_coin_counter( void *ctx, uint8_t slot_no, uint16_t *out); +static void idz_jvs_write_gpio(void *ctx, uint32_t state); static const struct io3_ops idz_jvs_io3_ops = { .read_switches = idz_jvs_read_switches, .read_analogs = idz_jvs_read_analogs, .read_coin_counter = idz_jvs_read_coin_counter, + .write_gpio = idz_jvs_write_gpio }; static const uint16_t idz_jvs_gear_signals[] = { @@ -50,21 +52,20 @@ static const uint16_t idz_jvs_gear_signals[] = { static struct io3 idz_jvs_io3; +HRESULT idz_jvs_hook_init(void) +{ + HRESULT hr; + + assert(idz_dll.jvs_init != NULL); + + return idz_dll.jvs_init(); +} + HRESULT idz_jvs_init(struct jvs_node **out) { HRESULT hr; assert(out != NULL); - assert(idz_dll.jvs_init != NULL); - - dprintf("JVS I/O: Starting Initial D Zero backend DLL\n"); - hr = idz_dll.jvs_init(); - - if (FAILED(hr)) { - dprintf("JVS I/O: Backend error, I/O disconnected; %x\n", (int) hr); - - return hr; - } io3_init(&idz_jvs_io3, NULL, &idz_jvs_io3_ops, NULL); *out = io3_to_jvs_node(&idz_jvs_io3); @@ -175,3 +176,21 @@ static void idz_jvs_read_coin_counter( idz_dll.jvs_read_coin_counter(out); } +static void idz_jvs_write_gpio(void *ctx, uint32_t state) +{ + assert(idz_dll.led_set_leds != NULL); + + // Since Sega uses an odd ordering for the first part of the bitfield, + // let's normalize the data and just send over bytes for the receiver + // to interpret as ON/OFF values. + uint8_t rgb_out[6] = { + state & IDZ_IO_LED_START ? 0xFF : 0x00, + state & IDZ_IO_LED_VIEW_CHANGE ? 0xFF : 0x00, + state & IDZ_IO_LED_UP ? 0xFF : 0x00, + state & IDZ_IO_LED_DOWN ? 0xFF : 0x00, + state & IDZ_IO_LED_RIGHT ? 0xFF : 0x00, + state & IDZ_IO_LED_LEFT ? 0xFF : 0x00, + }; + + idz_dll.led_set_leds(rgb_out); +} diff --git a/idzhook/jvs.h b/idzhook/jvs.h index 6a7c72a..82de143 100644 --- a/idzhook/jvs.h +++ b/idzhook/jvs.h @@ -4,4 +4,6 @@ #include "jvs/jvs-bus.h" +HRESULT idz_jvs_hook_init(void); + HRESULT idz_jvs_init(struct jvs_node **root); diff --git a/idzhook/meson.build b/idzhook/meson.build index ab90815..eb9ba6c 100644 --- a/idzhook/meson.build +++ b/idzhook/meson.build @@ -32,5 +32,7 @@ shared_library( 'jvs.h', 'zinput.c', 'zinput.h', + 'ffb.c', + 'ffb.h', ], ) diff --git a/idzio/backend.h b/idzio/backend.h index e0a958f..1993076 100644 --- a/idzio/backend.h +++ b/idzio/backend.h @@ -8,4 +8,9 @@ struct idz_io_backend { void (*jvs_read_buttons)(uint8_t *gamebtn); void (*jvs_read_shifter)(uint8_t *gear); void (*jvs_read_analogs)(struct idz_io_analog_state *state); + HRESULT (*ffb_init)(void); + void (*ffb_toggle)(bool active); + void (*ffb_constant_force)(uint8_t direction, uint8_t force); + void (*ffb_rumble)(uint8_t period, uint8_t force); + void (*ffb_damper)(uint8_t force); }; diff --git a/idzio/config.c b/idzio/config.c index aec8261..369a4c5 100644 --- a/idzio/config.c +++ b/idzio/config.c @@ -78,12 +78,29 @@ void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename) } // FFB configuration + cfg->ffb_constant_force_strength = GetPrivateProfileIntW( + L"dinput", + L"constantForceStrength", + 100, + filename); - cfg->center_spring_strength = GetPrivateProfileIntW( - L"dinput", - L"centerSpringStrength", - 30, - filename); + cfg->ffb_rumble_strength = GetPrivateProfileIntW( + L"dinput", + L"rumbleStrength", + 100, + filename); + + cfg->ffb_damper_strength = GetPrivateProfileIntW( + L"dinput", + L"damperStrength", + 100, + filename); + + cfg->ffb_rumble_duration = GetPrivateProfileIntW( + L"dinput", + L"rumbleDuration", + 1000, + filename); } void idz_xi_config_load(struct idz_xi_config *cfg, const wchar_t *filename) diff --git a/idzio/config.h b/idzio/config.h index 155f797..7a17f00 100644 --- a/idzio/config.h +++ b/idzio/config.h @@ -23,7 +23,11 @@ struct idz_di_config { bool reverse_accel_axis; // FFB configuration - uint16_t center_spring_strength; + uint8_t ffb_constant_force_strength; + uint8_t ffb_rumble_strength; + uint8_t ffb_damper_strength; + + uint32_t ffb_rumble_duration; }; struct idz_xi_config { diff --git a/idzio/di-dev.c b/idzio/di-dev.c index 26cf6e3..053eb9c 100644 --- a/idzio/di-dev.c +++ b/idzio/di-dev.c @@ -1,134 +1,39 @@ #include #include - +#include #include #include "idzio/di-dev.h" #include "util/dprintf.h" -HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) +const struct idz_di_config *idz_di_cfg; +static HWND idz_di_wnd; +static IDirectInputDevice8W *idz_di_dev; + +/* Individual DI Effects */ +static IDirectInputEffect *idz_di_fx; +static IDirectInputEffect *idz_di_fx_rumble; +static IDirectInputEffect *idz_di_fx_damper; + +/* Max FFB Board value is 127 */ +static const double idz_di_ffb_scale = 127.0; + +HRESULT idz_di_dev_init( + const struct idz_di_config *cfg, + IDirectInputDevice8W *dev, + HWND wnd) { HRESULT hr; assert(dev != NULL); assert(wnd != NULL); - hr = IDirectInputDevice8_SetCooperativeLevel( - dev, - wnd, - DISCL_BACKGROUND | DISCL_EXCLUSIVE); + idz_di_cfg = cfg; + idz_di_dev = dev; + idz_di_wnd = wnd; - if (FAILED(hr)) { - dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int) hr); - - return hr; - } - - hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); - - if (FAILED(hr)) { - dprintf("DirectInput: SetDataFormat failed: %08x\n", (int) hr); - - return hr; - } - - hr = IDirectInputDevice8_Acquire(dev); - - if (FAILED(hr)) { - dprintf("DirectInput: Acquire failed: %08x\n", (int) hr); - - return hr; - } - - return hr; -} - -void idz_di_dev_start_fx( - IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength) -{ - /* Set up force-feedback on devices that support it. This is just a stub - for the time being, since we don't yet know how the serial port force - feedback protocol works. - - I'm currently developing with an Xbox One Thrustmaster TMX wheel, if - we don't perform at least some perfunctory FFB initialization of this - nature (or indeed if no DirectInput application is running) then the - wheel exhibits considerable resistance, similar to that of a stationary - car. Changing cf.lMagnitude to a nonzero value does cause the wheel to - continuously turn in the given direction with the given force as one - would expect (max magnitude per DirectInput docs is +/- 10000). - - Failure here is non-fatal, we log any errors and move on. - - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416353(v=vs.85) - */ - - IDirectInputEffect *obj; - DWORD axis; - LONG direction; - DIEFFECT fx; - DICONDITION cond; - HRESULT hr; - - assert(dev != NULL); - assert(out != NULL); - - *out = NULL; - - dprintf("DirectInput: Starting force feedback (may take a sec)\n"); - - // Auto-centering effect - axis = DIJOFS_X; - direction = 0; - - memset(&cond, 0, sizeof(cond)); - cond.lOffset = 0; - cond.lPositiveCoefficient = strength; - cond.lNegativeCoefficient = strength; - cond.dwPositiveSaturation = strength; // For FG920? - cond.dwNegativeSaturation = strength; // For FG920? - cond.lDeadBand = 0; - - memset(&fx, 0, sizeof(fx)); - fx.dwSize = sizeof(fx); - fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; - fx.dwDuration = INFINITE; - fx.dwGain = DI_FFNOMINALMAX; - fx.dwTriggerButton = DIEB_NOTRIGGER; - fx.dwTriggerRepeatInterval = INFINITE; - fx.cAxes = 1; - fx.rgdwAxes = &axis; - fx.rglDirection = &direction; - fx.cbTypeSpecificParams = sizeof(cond); - fx.lpvTypeSpecificParams = &cond; - - hr = IDirectInputDevice8_CreateEffect( - dev, - &GUID_Spring, - &fx, - &obj, - NULL); - - if (FAILED(hr)) { - dprintf("DirectInput: Centering spring force feedback unavailable: %08x\n", - (int) hr); - return; - } - - hr = IDirectInputEffect_Start(obj, INFINITE, 0); - - if (FAILED(hr)) { - IDirectInputEffect_Release(obj); - dprintf("DirectInput: Centering spring force feedback start failed: %08x\n", - (int) hr); - return; - } - - *out = obj; - - dprintf("DirectInput: Centering spring effects initialized with strength %d%%\n", - strength / 100); + return S_OK; } HRESULT idz_di_dev_poll( @@ -167,3 +72,312 @@ HRESULT idz_di_dev_poll( return hr; } + +HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) { + HRESULT hr; + + assert(dev != NULL); + assert(wnd != NULL); + + hr = IDirectInputDevice8_SetCooperativeLevel( + dev, + wnd, + DISCL_BACKGROUND | DISCL_EXCLUSIVE); + + if (FAILED(hr)) { + dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int) hr); + + return hr; + } + + hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); + + if (FAILED(hr)) { + dprintf("DirectInput: SetDataFormat failed: %08x\n", (int) hr); + + return hr; + } + + hr = IDirectInputDevice8_Acquire(dev); + + if (FAILED(hr)) { + dprintf("DirectInput: Acquire failed: %08x\n", (int) hr); + + return hr; + } + + return hr; +} + +HRESULT idz_di_ffb_init(void) +{ + HRESULT hr; + + hr = idz_di_dev_start(idz_di_dev, idz_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +void idz_di_ffb_toggle(bool active) +{ + if (active) { + return; + } + + /* Stop and release all effects */ + /* I never programmed DirectInput Effects, so this might be bad practice. */ + if (idz_di_fx != NULL) { + IDirectInputEffect_Stop(idz_di_fx); + IDirectInputEffect_Release(idz_di_fx); + idz_di_fx = NULL; + } + + if (idz_di_fx_rumble != NULL) { + IDirectInputEffect_Stop(idz_di_fx_rumble); + IDirectInputEffect_Release(idz_di_fx_rumble); + idz_di_fx_rumble = NULL; + } + + if (idz_di_fx_damper != NULL) { + IDirectInputEffect_Stop(idz_di_fx_damper); + IDirectInputEffect_Release(idz_di_fx_damper); + idz_di_fx_damper = NULL; + } +} + +void idz_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) +{ + /* DI expects a magnitude in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = idz_di_cfg->ffb_constant_force_strength * 100; + if (ffb_strength == 0) { + return; + } + + DWORD axis; + LONG direction; + DIEFFECT fx; + DICONSTANTFORCE cf; + HRESULT hr; + + /* Direction 0: move to the right, 1: move to the left */ + LONG magnitude = (LONG)(((double)force / idz_di_ffb_scale) * ffb_strength); + cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude; + + axis = DIJOFS_X; + /* Irrelevant as magnitude descripbes the direction */ + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cf); + fx.lpvTypeSpecificParams = &cf; + + if (idz_di_fx != NULL) { + // Try to update the existing effect + hr = IDirectInputEffect_SetParameters(idz_di_fx, &fx, DIEP_TYPESPECIFICPARAMS); + + if (SUCCEEDED(hr)) { + return; + } else { + dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr); + // Stop and release the current effect if updating fails + IDirectInputEffect_Stop(idz_di_fx); + IDirectInputEffect_Release(idz_di_fx); + idz_di_fx = NULL; + } + } + + // Create a new constant force effect + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + idz_di_dev, + &GUID_ConstantForce, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int) hr); + return; + } + + hr = IDirectInputEffect_Start(obj, INFINITE, 0); + if (FAILED(hr)) { + dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int) hr); + IDirectInputEffect_Release(obj); + return; + } + + idz_di_fx = obj; +} + +void idz_di_ffb_rumble(uint8_t force, uint8_t period) +{ + /* DI expects a magnitude in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = idz_di_cfg->ffb_rumble_strength * 100; + if (ffb_strength == 0) { + return; + } + + uint32_t ffb_duration = idz_di_cfg->ffb_rumble_duration; + + DWORD axis; + LONG direction; + DIEFFECT fx; + DIPERIODIC pe; + HRESULT hr; + + /* Duration in microseconds, + Might be totally wrong as especially on FANATEC wheels as this code will + crash the game. TODO: Figure out why this effect will crash on FANATEC! */ + DWORD duration = (DWORD)((double)force * ffb_duration); + + memset(&pe, 0, sizeof(pe)); + pe.dwMagnitude = (DWORD)(((double)force / idz_di_ffb_scale) * ffb_strength); + pe.lOffset = 0; + pe.dwPhase = 0; + pe.dwPeriod = duration; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = duration; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(pe); + fx.lpvTypeSpecificParams = &pe; + + if (idz_di_fx_rumble != NULL) { + // Try to update the existing effect + hr = IDirectInputEffect_SetParameters(idz_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS); + + if (SUCCEEDED(hr)) { + return; + } else { + dprintf("DirectInput: Failed to update periodic force feedback, recreating effect: %08x\n", (int)hr); + // Stop and release the current effect if updating fails + IDirectInputEffect_Stop(idz_di_fx_rumble); + IDirectInputEffect_Release(idz_di_fx_rumble); + idz_di_fx_rumble = NULL; + } + } + + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + idz_di_dev, + &GUID_Sine, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: Periodic force feedback creation failed: %08x\n", (int) hr); + return; + } + + hr = IDirectInputEffect_Start(obj, INFINITE, 0); + if (FAILED(hr)) { + dprintf("DirectInput: Periodic force feedback start failed: %08x\n", (int) hr); + IDirectInputEffect_Release(obj); + return; + } + + idz_di_fx_rumble = obj; +} + +void idz_di_ffb_damper(uint8_t force) +{ + /* DI expects a coefficient in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = idz_di_cfg->ffb_damper_strength * 100; + if (ffb_strength == 0) { + return; + } + + DWORD axis; + LONG direction; + DIEFFECT fx; + DICONDITION cond; + HRESULT hr; + + memset(&cond, 0, sizeof(cond)); + cond.lOffset = 0; + cond.lPositiveCoefficient = (LONG)(((double)force / idz_di_ffb_scale) * ffb_strength); + cond.lNegativeCoefficient = (LONG)(((double)force / idz_di_ffb_scale) * ffb_strength); + /* Not sure on this one */ + cond.dwPositiveSaturation = DI_FFNOMINALMAX; + cond.dwNegativeSaturation = DI_FFNOMINALMAX; + cond.lDeadBand = 0; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cond); + fx.lpvTypeSpecificParams = &cond; + + if (idz_di_fx_damper != NULL) { + // Try to update the existing effect + hr = IDirectInputEffect_SetParameters(idz_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS); + + if (SUCCEEDED(hr)) { + return; + } else { + dprintf("DirectInput: Failed to update damper force feedback, recreating effect: %08x\n", (int)hr); + // Stop and release the current effect if updating fails + IDirectInputEffect_Stop(idz_di_fx_damper); + IDirectInputEffect_Release(idz_di_fx_damper); + idz_di_fx_damper = NULL; + } + } + + // Create a new damper force effect + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + idz_di_dev, + &GUID_Damper, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: Damper force feedback creation failed: %08x\n", (int) hr); + return; + } + + hr = IDirectInputEffect_Start(obj, INFINITE, 0); + if (FAILED(hr)) { + dprintf("DirectInput: Damper force feedback start failed: %08x\n", (int) hr); + IDirectInputEffect_Release(obj); + return; + } + + idz_di_fx_damper = obj; +} diff --git a/idzio/di-dev.h b/idzio/di-dev.h index 9b3d03f..783ec66 100644 --- a/idzio/di-dev.h +++ b/idzio/di-dev.h @@ -5,15 +5,26 @@ #include +#include "idzio/config.h" + union idz_di_state { DIJOYSTATE st; uint8_t bytes[sizeof(DIJOYSTATE)]; }; +HRESULT idz_di_dev_init( + const struct idz_di_config *cfg, + IDirectInputDevice8W *dev, + HWND wnd); + HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd); -void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength); HRESULT idz_di_dev_poll( IDirectInputDevice8W *dev, HWND wnd, union idz_di_state *out); +HRESULT idz_di_ffb_init(void); +void idz_di_ffb_toggle(bool active); +void idz_di_ffb_constant_force(uint8_t direction, uint8_t force); +void idz_di_ffb_rumble(uint8_t force, uint8_t period); +void idz_di_ffb_damper(uint8_t force); diff --git a/idzio/di.c b/idzio/di.c index e0cb4c6..8d8be41 100644 --- a/idzio/di.c +++ b/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_shifter = idz_di_jvs_read_shifter, .jvs_read_analogs = idz_di_jvs_read_analogs, + .ffb_init = idz_di_ffb_init, + .ffb_toggle = idz_di_ffb_toggle, + .ffb_constant_force = idz_di_ffb_constant_force, + .ffb_rumble = idz_di_ffb_rumble, + .ffb_damper = idz_di_ffb_damper }; static HWND idz_di_wnd; @@ -73,7 +78,6 @@ static uint8_t idz_di_gear[6]; static bool idz_di_use_pedals; static bool idz_di_reverse_brake_axis; static bool idz_di_reverse_accel_axis; -static uint16_t idz_di_center_spring_strength; HRESULT idz_di_init( const struct idz_di_config *cfg, @@ -166,16 +170,12 @@ HRESULT idz_di_init( return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } - hr = idz_di_dev_start(idz_di_dev, idz_di_wnd); + hr = idz_di_dev_init(cfg, idz_di_dev, idz_di_wnd); if (FAILED(hr)) { return hr; } - // Convert the strength from 0-100 to 0-10000 for DirectInput - idz_di_dev_start_fx(idz_di_dev, &idz_di_fx, - idz_di_center_spring_strength * 100); - if (cfg->pedals_name[0] != L'\0') { hr = IDirectInput8_EnumDevices( idz_di_api, @@ -349,15 +349,24 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg) idz_di_gear[i] = cfg->gear[i]; } - // FFB configuration + /* FFB configuration */ + if (cfg->ffb_constant_force_strength < 0 || cfg->ffb_constant_force_strength > 100) { + dprintf("Wheel: Invalid constant force strength: %i\n", cfg->ffb_constant_force_strength); - if (cfg->center_spring_strength < 0 || cfg->center_spring_strength > 100) { - dprintf("Wheel: Invalid center spring strength: %i\n", cfg->center_spring_strength); + return E_INVALIDARG; + } + + if (cfg->ffb_rumble_strength < 0 || cfg->ffb_rumble_strength > 100) { + dprintf("Wheel: Invalid rumble strength: %i\n", cfg->ffb_rumble_strength); return E_INVALIDARG; } - idz_di_center_spring_strength = cfg->center_spring_strength; + if (cfg->ffb_damper_strength < 0 || cfg->ffb_damper_strength > 100) { + dprintf("Wheel: Invalid damper strength: %i\n", cfg->ffb_damper_strength); + + return E_INVALIDARG; + } return S_OK; } diff --git a/idzio/dllmain.c b/idzio/dllmain.c index ad94747..5c8592d 100644 --- a/idzio/dllmain.c +++ b/idzio/dllmain.c @@ -20,7 +20,7 @@ static uint16_t idz_io_coins; uint16_t idz_io_get_api_version(void) { - return 0x0100; + return 0x0102; } HRESULT idz_io_jvs_init(void) @@ -123,3 +123,79 @@ void idz_io_jvs_read_coin_counter(uint16_t *out) *out = idz_io_coins; } + +HRESULT idz_io_led_init(void) +{ + return S_OK; +} + +void idz_io_led_set_fet_output(const uint8_t *rgb) +{ +#if 0 + dprintf("IDZ LED: LEFT SEAT LED: %02X\n", rgb[0]); + dprintf("IDZ LED: RIGHT SEAT LED: %02X\n", rgb[1]); +#endif + + return; +} + +void idz_io_led_gs_update(const uint8_t *rgb) +{ +#if 0 + for (int i = 0; i < 9; i++) { + dprintf("IDZ LED: LED %d: %02X %02X %02X Speed: %02X\n", + i, rgb[i * 4], rgb[i * 4 + 1], rgb[i * 4 + 2], rgb[i * 4 + 3]); + } +#endif + + return; +} + +void idz_io_led_set_leds(const uint8_t *rgb) +{ +#if 0 + dprintf("IDZ LED: START: %02X\n", rgb[0]); + dprintf("IDZ LED: VIEW CHANGE: %02X\n", rgb[1]); + dprintf("IDZ LED: UP: %02X\n", rgb[2]); + dprintf("IDZ LED: DOWN: %02X\n", rgb[3]); + dprintf("IDZ LED: RIGHT: %02X\n", rgb[4]); + dprintf("IDZ LED: LEFT: %02X\n", rgb[5]); +#endif + + return; +} + +HRESULT idz_io_ffb_init(void) +{ + assert(idz_io_backend != NULL); + + return idz_io_backend->ffb_init(); +} + +void idz_io_ffb_toggle(bool active) +{ + assert(idz_io_backend != NULL); + + idz_io_backend->ffb_toggle(active); +} + +void idz_io_ffb_constant_force(uint8_t direction, uint8_t force) +{ + assert(idz_io_backend != NULL); + + idz_io_backend->ffb_constant_force(direction, force); +} + +void idz_io_ffb_rumble(uint8_t period, uint8_t force) +{ + assert(idz_io_backend != NULL); + + idz_io_backend->ffb_rumble(period, force); +} + +void idz_io_ffb_damper(uint8_t force) +{ + assert(idz_io_backend != NULL); + + idz_io_backend->ffb_damper(force); +} diff --git a/idzio/idzio.def b/idzio/idzio.def index b177000..4d66148 100644 --- a/idzio/idzio.def +++ b/idzio/idzio.def @@ -6,3 +6,12 @@ EXPORTS idz_io_jvs_read_buttons idz_io_jvs_read_coin_counter idz_io_jvs_read_shifter + idz_io_led_init + idz_io_led_set_fet_output + idz_io_led_gs_update + idz_io_led_set_leds + idz_io_ffb_init + idz_io_ffb_toggle + idz_io_ffb_constant_force + idz_io_ffb_rumble + idz_io_ffb_damper diff --git a/idzio/idzio.h b/idzio/idzio.h index 395b012..8fd01d8 100644 --- a/idzio/idzio.h +++ b/idzio/idzio.h @@ -14,6 +14,7 @@ #include +#include #include enum { @@ -30,6 +31,17 @@ enum { IDZ_IO_GAMEBTN_VIEW_CHANGE = 0x20, }; +enum { + /* These are the bitmasks to use when checking which + lights are triggered on incoming IO4 GPIO writes. */ + IDZ_IO_LED_START = 1 << 7, + IDZ_IO_LED_VIEW_CHANGE = 1 << 6, + IDZ_IO_LED_UP = 1 << 1, + IDZ_IO_LED_DOWN = 1 << 0, + IDZ_IO_LED_RIGHT = 1 << 14, + IDZ_IO_LED_LEFT = 1 << 15 +}; + struct idz_io_analog_state { /* Current steering wheel position, where zero is the centered position. @@ -104,3 +116,107 @@ void idz_io_jvs_read_shifter(uint8_t *gear); Minimum API version: 0x0100 */ void idz_io_jvs_read_coin_counter(uint16_t *total); + +/* Initialize LED emulation. This function will be called before any + other idz_io_led_*() function calls. + + All subsequent calls may originate from arbitrary threads and some may + overlap with each other. Ensuring synchronization inside your IO DLL is + your responsibility. + + Minimum API version: 0x0101 */ + +HRESULT idz_io_led_init(void); + +/* Update the FET outputs. rgb is a pointer to an array up to 3 bytes. + + The following bits are used to control the FET outputs: + [0]: LEFT SEAT LED + [1]: RIGHT SEAT LED + + The LED is truned on when the byte is 255 and turned off when the byte is 0. + + Minimum API version: 0x0101 */ + +void idz_io_led_set_fet_output(const uint8_t *rgb); + +/* Update the RGB LEDs. rgb is a pointer to an array up to 32 * 4 = 128 bytes. + + The LEDs are laid out as follows: + [0]: LEFT UP LED + [1-2]: LEFT CENTER LED + [3]: LEFT DOWN LED + [5]: RIGHT UP LED + [6-7]: RIGHT CENTER LED + [8]: RIGHT DOWN LED + + Each rgb value is comprised for 4 bytes in the order of R, G, B, Speed. + Speed is a value from 0 to 255, where 0 is the fastest speed and 255 is the slowest. + + Minimum API version: 0x0101 */ + +void idz_io_led_gs_update(const uint8_t *rgb); + +/* Update the cabinet button LEDs. rgb is a pointer to an array up to 6 bytes. + + The LEDs are laid out as follows: + [0]: START LED + [1]: VIEW CHANGE LED + [2]: UP LED + [3]: DOWN LED + [4]: RIGHT LED + [5]: LEFT LED + + The LED is turned on when the byte is 255 and turned off when the byte is 0. + + Minimum API version: 0x0101 */ + +void idz_io_led_set_leds(const uint8_t *rgb); + +/* Initialize FFB emulation. This function will be called before any + other idz_io_ffb_*() function calls. + + This will always be called even if FFB board emulation is disabled to allow + the IO DLL to initialize any necessary resources. + + Minimum API version: 0x0102 */ + +HRESULT idz_io_ffb_init(void); + +/* Toggle FFB emulation. If active is true, FFB emulation should be enabled. + If active is false, FFB emulation should be disabled and all FFB effects + should be stopped and released. + + Minimum API version: 0x0102 */ + +void idz_io_ffb_toggle(bool active); + +/* Set a constant force FFB effect. + + Direction is 0 for right and 1 for left. + Force is the magnitude of the force, where 0 is no force and 127 is the + maximum force in a given direction. + + Minimum API version: 0x0102 */ + +void idz_io_ffb_constant_force(uint8_t direction, uint8_t force); + +/* Set a (sine) periodic force FFB effect. + + Period is the period of the effect in milliseconds (not sure). + Force is the magnitude of the force, where 0 is no force and 127 is the + maximum force. + + Minimum API version: 0x0102 */ + +void idz_io_ffb_rumble(uint8_t period, uint8_t force); + +/* Set a damper FFB effect. + + Force is the magnitude of the force, where 0 is no force and 40 is the + maximum force. Theoretically the maximum force is 127, but the game only + uses a maximum of 40. + + Minimum API version: 0x0102 */ + +void idz_io_ffb_damper(uint8_t force); diff --git a/idzio/xi.c b/idzio/xi.c index 4a8391d..db3a94d 100644 --- a/idzio/xi.c +++ b/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_analogs(struct idz_io_analog_state *out); +static HRESULT idz_xi_ffb_init(void); +static void idz_xi_ffb_toggle(bool active); +static void idz_xi_ffb_constant_force(uint8_t direction, uint8_t force); +static void idz_xi_ffb_rumble(uint8_t force, uint8_t period); +static void idz_xi_ffb_damper(uint8_t force); + static HRESULT idz_xi_config_apply(const struct idz_xi_config *cfg); static const struct idz_io_backend idz_xi_backend = { .jvs_read_buttons = idz_xi_jvs_read_buttons, .jvs_read_shifter = idz_xi_jvs_read_shifter, .jvs_read_analogs = idz_xi_jvs_read_analogs, + .ffb_init = idz_xi_ffb_init, + .ffb_toggle = idz_xi_ffb_toggle, + .ffb_constant_force = idz_xi_ffb_constant_force, + .ffb_rumble = idz_xi_ffb_rumble, + .ffb_damper = idz_xi_ffb_damper }; static bool idz_xi_single_stick_steering; @@ -210,3 +221,35 @@ static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out) out->accel = xi.Gamepad.bRightTrigger << 8; out->brake = xi.Gamepad.bLeftTrigger << 8; } + +static HRESULT idz_xi_ffb_init(void) { + return S_OK; +} + +static void idz_xi_ffb_toggle(bool active) { + XINPUT_VIBRATION vibration; + + memset(&vibration, 0, sizeof(vibration)); + + XInputSetState(0, &vibration); +} + +static void idz_xi_ffb_constant_force(uint8_t direction, uint8_t force) { + return; +} + +static void idz_xi_ffb_rumble(uint8_t force, uint8_t period) { + XINPUT_VIBRATION vibration; + /* XInput max strength is 65.535, so multiply the 127.0 by 516. */ + uint16_t strength = force * 516; + + memset(&vibration, 0, sizeof(vibration)); + vibration.wLeftMotorSpeed = strength; + vibration.wRightMotorSpeed = strength; + + XInputSetState(0, &vibration); +} + +static void idz_xi_ffb_damper(uint8_t force) { + return; +}