From a0664a40085787db0e0e513f8b345bfa8b157268 Mon Sep 17 00:00:00 2001 From: Tau Date: Thu, 9 May 2019 14:28:30 -0400 Subject: [PATCH] idzio/di.c: Add hardcoded DirectInput backend --- idzio/di.c | 336 ++++++++++++++++++++++++++++++++++++++++++++++ idzio/di.h | 5 + idzio/dllmain.c | 13 +- idzio/meson.build | 7 + meson.build | 2 + 5 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 idzio/di.c create mode 100644 idzio/di.h diff --git a/idzio/di.c b/idzio/di.c new file mode 100644 index 0000000..4fc2f4d --- /dev/null +++ b/idzio/di.c @@ -0,0 +1,336 @@ +#include +#include + +#include +#include +#include + +#include "idzio/backend.h" +#include "idzio/idzio.h" +#include "idzio/shifter.h" +#include "idzio/wnd.h" + +#include "util/dprintf.h" + +static BOOL idz_di_enum_callback(const DIDEVICEINSTANCEW *dev, void *ctx); +static void idz_di_try_fx(void); +static HRESULT idz_di_poll(DIJOYSTATE *out); +static void idz_di_jvs_read_buttons(uint8_t *gamebtn_out); +static uint8_t idz_di_decode_pov(DWORD pov); +static void idz_di_jvs_read_shifter(uint8_t *gear); +static void idz_di_jvs_read_analogs(struct idz_io_analog_state *out); + +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, +}; + +static HWND idz_di_wnd; +static IDirectInput8W *idz_di_api; +static IDirectInputDevice8W *idz_di_dev; +static IDirectInputEffect *idz_di_fx; + +HRESULT idz_di_init(HINSTANCE inst, const struct idz_io_backend **backend) +{ + HRESULT hr; + HMODULE dinput8; + HRESULT WINAPI (*api_entry)(HINSTANCE,DWORD,REFIID,LPVOID *,LPUNKNOWN); + wchar_t dll_path[MAX_PATH]; + UINT path_pos; + + assert(backend != NULL); + + *backend = NULL; + + hr = idz_io_wnd_create(inst, &idz_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + /* Initial D Zero has some built-in DirectInput support that is not + particularly useful. We short this out by dropping a no-op dinput8.dll + into the install directory. However, this DLL does need to talk to the + real operating system implementation of DirectInput without the stub DLL + interfering, so build a path to C:\Windows\System32\dinput.dll here. */ + + dll_path[0] = L'\0'; + path_pos = GetSystemDirectoryW(dll_path, _countof(dll_path)); + wcscat_s( + dll_path + path_pos, + _countof(dll_path) - path_pos, + L"\\dinput8.dll"); + + dinput8 = LoadLibraryW(dll_path); + + if (dinput8 == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Wheel: LoadLibrary failed: %08x\n", (int) hr); + + return hr; + } + + api_entry = (void *) GetProcAddress(dinput8, "DirectInput8Create"); + + if (api_entry == NULL) { + dprintf("Wheel: GetProcAddress failed\n"); + + return E_FAIL; + } + + hr = api_entry( + inst, + DIRECTINPUT_VERSION, + &IID_IDirectInput8W, + (void **) &idz_di_api, + NULL); + + if (FAILED(hr)) { + dprintf("Wheel: API create failed: %08x\n", (int) hr); + + return hr; + } + + hr = IDirectInput8_EnumDevices( + idz_di_api, + DI8DEVCLASS_GAMECTRL, + idz_di_enum_callback, + NULL, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr) || idz_di_dev == NULL) { + dprintf("Wheel: Could not find a controller: %08x\n", (int) hr); + + return hr; + } + + hr = IDirectInputDevice8_SetCooperativeLevel( + idz_di_dev, + idz_di_wnd, + DISCL_BACKGROUND | DISCL_EXCLUSIVE); + + if (FAILED(hr)) { + dprintf("Wheel: SetCooperativeLevel failed: %08x\n", (int) hr); + + return hr; + } + + hr = IDirectInputDevice8_SetDataFormat(idz_di_dev, &c_dfDIJoystick); + + if (FAILED(hr)) { + dprintf("Wheel: SetDataFormat failed: %08x\n", (int) hr); + + return hr; + } + + hr = IDirectInputDevice8_Acquire(idz_di_dev); + + if (FAILED(hr)) { + dprintf("Wheel: Acquire failed: %08x\n", (int) hr); + + return hr; + } + + dprintf("Wheel: DirectInput initialized\n"); + idz_di_try_fx(); + *backend = &idz_di_backend; + + return S_OK; +} + +static BOOL idz_di_enum_callback(const DIDEVICEINSTANCEW *dev, void *ctx) +{ + IDirectInput8_CreateDevice( + idz_di_api, + &dev->guidInstance, + &idz_di_dev, + NULL); + + return DIENUM_STOP; +} + +static void idz_di_try_fx(void) +{ + /* 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) + */ + + DWORD axis; + LONG direction; + DIEFFECT fx; + DICONSTANTFORCE cf; + HRESULT hr; + + dprintf("Wheel: Attempting to start force feedback (may take a sec)\n"); + + axis = DIJOFS_X; + direction = 0; + + memset(&cf, 0, sizeof(cf)); + cf.lMagnitude = 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; + + hr = IDirectInputDevice8_CreateEffect( + idz_di_dev, + &GUID_ConstantForce, + &fx, + &idz_di_fx, + NULL); + + if (FAILED(hr)) { + dprintf("Wheel: DirectInput force feedback unavailable: %08x\n", + (int) hr); + + return; + } + + hr = IDirectInputEffect_Start(idz_di_fx, INFINITE, 0); + + if (FAILED(hr)) { + dprintf("Wheel: DirectInput force feedback start failed: %08x\n", + (int) hr); + + return; + } + + dprintf("Wheel: Force feedback initialized and set to zero\n"); +} + +static HRESULT idz_di_poll(DIJOYSTATE *out) +{ + HRESULT hr; + MSG msg; + + memset(out, 0, sizeof(*out)); + + if (idz_di_dev == NULL) { + return E_UNEXPECTED; + } + + /* Pump our dummy window's message queue just in case DirectInput or an + IHV DirectInput driver somehow relies on it */ + + while (PeekMessageW(&msg, idz_di_wnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + hr = IDirectInputDevice8_GetDeviceState(idz_di_dev, sizeof(*out), out); + + if (FAILED(hr)) { + dprintf("Wheel: I/O error: %08x\n", (int) hr); + } + + /* JVS lacks a protocol for reporting hardware errors from poll command + responses, so this ends up returning zeroed input state instead. */ + + return hr; +} + +static void idz_di_jvs_read_buttons(uint8_t *gamebtn_out) +{ + uint8_t gamebtn; + DIJOYSTATE state; + HRESULT hr; + + assert(gamebtn_out != NULL); + + hr = idz_di_poll(&state); + + if (FAILED(hr)) { + return; + } + + gamebtn = idz_di_decode_pov(state.rgdwPOV[0]); + + if (state.rgbButtons[2]) { + gamebtn |= IDZ_IO_GAMEBTN_START; + } + + if (state.rgbButtons[9]) { + gamebtn |= IDZ_IO_GAMEBTN_VIEW_CHANGE; + } + + *gamebtn_out = gamebtn; +} + +static uint8_t idz_di_decode_pov(DWORD pov) +{ + switch (pov) { + case 0: return IDZ_IO_GAMEBTN_UP; + case 4500: return IDZ_IO_GAMEBTN_UP | IDZ_IO_GAMEBTN_RIGHT; + case 9000: return IDZ_IO_GAMEBTN_RIGHT; + case 13500: return IDZ_IO_GAMEBTN_RIGHT | IDZ_IO_GAMEBTN_DOWN; + case 18000: return IDZ_IO_GAMEBTN_DOWN; + case 22500: return IDZ_IO_GAMEBTN_DOWN | IDZ_IO_GAMEBTN_RIGHT; + case 27000: return IDZ_IO_GAMEBTN_LEFT; + case 31500: return IDZ_IO_GAMEBTN_LEFT | IDZ_IO_GAMEBTN_UP; + default: return 0; + } +} + +static void idz_di_jvs_read_shifter(uint8_t *gear) +{ + DIJOYSTATE state; + HRESULT hr; + + assert(gear != NULL); + + hr = idz_di_poll(&state); + + if (FAILED(hr)) { + return; + } + + if (state.rgbButtons[2]) { + idz_shifter_reset(); + } + + idz_shifter_update(state.rgbButtons[0], state.rgbButtons[1]); + + *gear = idz_shifter_current_gear(); +} + +static void idz_di_jvs_read_analogs(struct idz_io_analog_state *out) +{ + DIJOYSTATE state; + HRESULT hr; + + assert(out != NULL); + + hr = idz_di_poll(&state); + + if (FAILED(hr)) { + return; + } + + out->wheel = state.lX - 32768; + out->accel = 65535 - state.lRz; + out->brake = 65535 - state.lY; +} diff --git a/idzio/di.h b/idzio/di.h new file mode 100644 index 0000000..0470ef9 --- /dev/null +++ b/idzio/di.h @@ -0,0 +1,5 @@ +#pragma once + +#include "idzio/backend.h" + +HRESULT idz_di_init(HINSTANCE inst, const struct idz_io_backend **backend); diff --git a/idzio/dllmain.c b/idzio/dllmain.c index fd5240c..4256764 100644 --- a/idzio/dllmain.c +++ b/idzio/dllmain.c @@ -5,9 +5,11 @@ #include #include "idzio/backend.h" +#include "idzio/di.h" #include "idzio/idzio.h" #include "idzio/xi.h" +static HMODULE idz_io_hmodule; static const struct idz_io_backend *idz_io_backend; static bool idz_io_coin; static uint16_t idz_io_coins; @@ -16,7 +18,7 @@ HRESULT idz_io_init(void) { assert(idz_io_backend == NULL); - return idz_xi_init(&idz_io_backend); + return idz_di_init(idz_io_hmodule, &idz_io_backend); } void idz_io_jvs_read_buttons(uint8_t *opbtn_out, uint8_t *gamebtn_out) @@ -75,3 +77,12 @@ void idz_io_jvs_read_coin_counter(uint16_t *out) *out = idz_io_coins; } + +BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { + idz_io_hmodule = self; + } + + return TRUE; +} diff --git a/idzio/meson.build b/idzio/meson.build index d5976c9..91f2724 100644 --- a/idzio/meson.build +++ b/idzio/meson.build @@ -6,10 +6,17 @@ idzio_dll = shared_library( vs_module_defs : 'idzio.def', c_pch : '../precompiled.h', dependencies : [ + dinput8_lib, + dxguid_lib, xinput_lib, ], + link_with : [ + util_lib, + ], sources : [ 'backend.h', + 'di.c', + 'di.h', 'dllmain.c', 'idzio.h', 'shifter.c', diff --git a/meson.build b/meson.build index 47459d8..4178970 100644 --- a/meson.build +++ b/meson.build @@ -26,6 +26,8 @@ if meson.get_compiler('c').get_id() != 'msvc' endif cc = meson.get_compiler('c') +dinput8_lib = cc.find_library('dinput8') +dxguid_lib = cc.find_library('dxguid') xinput_lib = cc.find_library('xinput') inc = include_directories('.')