swdc: first segatools added

This commit is contained in:
2023-07-14 00:52:50 +02:00
parent 89195ed60b
commit ec072667b3
31 changed files with 2167 additions and 1 deletions

11
swdcio/backend.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <stdint.h>
#include "swdcio/swdcio.h"
struct swdc_io_backend {
void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint16_t *gamebtn);
void (*get_analogs)(struct swdc_io_analog_state *state);
};

111
swdcio/config.c Normal file
View File

@ -0,0 +1,111 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include "swdcio/config.h"
void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename)
{
wchar_t key[8];
int i;
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"dinput",
L"deviceName",
L"",
cfg->device_name,
_countof(cfg->device_name),
filename);
GetPrivateProfileStringW(
L"dinput",
L"brakeAxis",
L"RZ",
cfg->brake_axis,
_countof(cfg->brake_axis),
filename);
GetPrivateProfileStringW(
L"dinput",
L"accelAxis",
L"Y",
cfg->accel_axis,
_countof(cfg->accel_axis),
filename);
cfg->start = GetPrivateProfileIntW(L"dinput", L"start", 0, filename);
cfg->view_chg = GetPrivateProfileIntW(L"dinput", L"viewChg", 0, filename);
cfg->shift_dn = GetPrivateProfileIntW(L"dinput", L"shiftDn", 0, filename);
cfg->shift_up = GetPrivateProfileIntW(L"dinput", L"shiftUp", 0, filename);
cfg->wheel_green = GetPrivateProfileIntW(L"dinput", L"wheelGreen", 0, filename);
cfg->wheel_red = GetPrivateProfileIntW(L"dinput", L"wheelRed", 0, filename);
cfg->wheel_blue = GetPrivateProfileIntW(L"dinput", L"wheelBlue", 0, filename);
cfg->wheel_yellow = GetPrivateProfileIntW(L"dinput", L"wheelYellow", 0, filename);
cfg->reverse_brake_axis = GetPrivateProfileIntW(
L"dinput",
L"reverseBrakeAxis",
0,
filename);
cfg->reverse_accel_axis = GetPrivateProfileIntW(
L"dinput",
L"reverseAccelAxis",
0,
filename);
}
void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->single_stick_steering = GetPrivateProfileIntW(
L"io4",
L"singleStickSteering",
0,
filename);
}
void swdc_io_config_load(struct swdc_io_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", '1', filename);
cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", '2', filename);
cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", '3', filename);
cfg->restrict_ = GetPrivateProfileIntW(L"io4", L"restrict", 97, filename);
GetPrivateProfileStringW(
L"io4",
L"mode",
L"xinput",
cfg->mode,
_countof(cfg->mode),
filename);
swdc_shifter_config_load(&cfg->shifter, filename);
swdc_di_config_load(&cfg->di, filename);
swdc_xi_config_load(&cfg->xi, filename);
}
void swdc_shifter_config_load(
struct swdc_shifter_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->auto_neutral = GetPrivateProfileIntW(
L"io4",
L"autoNeutral",
0,
filename);
}

47
swdcio/config.h Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct swdc_shifter_config {
bool auto_neutral;
};
struct swdc_di_config {
wchar_t device_name[64];
wchar_t brake_axis[16];
wchar_t accel_axis[16];
uint8_t start;
uint8_t view_chg;
uint8_t shift_dn;
uint8_t shift_up;
uint8_t wheel_green;
uint8_t wheel_red;
uint8_t wheel_blue;
uint8_t wheel_yellow;
bool reverse_brake_axis;
bool reverse_accel_axis;
};
struct swdc_xi_config {
bool single_stick_steering;
};
struct swdc_io_config {
uint8_t vk_test;
uint8_t vk_service;
uint8_t vk_coin;
wchar_t mode[8];
int restrict_;
struct swdc_shifter_config shifter;
struct swdc_di_config di;
struct swdc_xi_config xi;
};
void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename);
void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename);
void swdc_io_config_load(struct swdc_io_config *cfg, const wchar_t *filename);
void swdc_shifter_config_load(
struct swdc_shifter_config *cfg,
const wchar_t *filename);

163
swdcio/di-dev.c Normal file
View File

@ -0,0 +1,163 @@
#include <windows.h>
#include <dinput.h>
#include <assert.h>
#include "swdcio/di-dev.h"
#include "util/dprintf.h"
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;
}
void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
{
/* 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;
DICONSTANTFORCE cf;
HRESULT hr;
assert(dev != NULL);
assert(out != NULL);
*out = NULL;
dprintf("DirectInput: Starting 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(
dev,
&GUID_ConstantForce,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: DirectInput force feedback unavailable: %08x\n",
(int) hr);
return;
}
hr = IDirectInputEffect_Start(obj, INFINITE, 0);
if (FAILED(hr)) {
IDirectInputEffect_Release(obj);
dprintf("DirectInput: DirectInput force feedback start failed: %08x\n",
(int) hr);
return;
}
*out = obj;
dprintf("DirectInput: Force feedback initialized and set to zero\n");
}
HRESULT swdc_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,
union swdc_di_state *out)
{
HRESULT hr;
MSG msg;
assert(dev != NULL);
assert(wnd != NULL);
assert(out != NULL);
memset(out, 0, sizeof(*out));
/* Pump our dummy window's message queue just in case DirectInput or an
IHV DirectInput driver somehow relies on it */
while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
hr = IDirectInputDevice8_GetDeviceState(
dev,
sizeof(out->st),
&out->st);
if (FAILED(hr)) {
dprintf("DirectInput: GetDeviceState 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;
}

19
swdcio/di-dev.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <windows.h>
#include <dinput.h>
#include <stdint.h>
union swdc_di_state {
DIJOYSTATE st;
uint8_t bytes[sizeof(DIJOYSTATE)];
};
HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out);
HRESULT swdc_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,
union swdc_di_state *out);

420
swdcio/di.c Normal file
View File

@ -0,0 +1,420 @@
#include <windows.h>
#include <dinput.h>
#include <stddef.h>
#include <stdint.h>
#include <wchar.h>
#include "swdcio/backend.h"
#include "swdcio/config.h"
#include "swdcio/di.h"
#include "swdcio/di-dev.h"
#include "swdcio/swdcio.h"
#include "swdcio/wnd.h"
#include "util/dprintf.h"
#include "util/str.h"
struct swdc_di_axis {
wchar_t name[4];
size_t off;
};
static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg);
static const struct swdc_di_axis *swdc_di_get_axis(const wchar_t *name);
static BOOL CALLBACK swdc_di_enum_callback(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static BOOL CALLBACK swdc_di_enum_callback_shifter(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static void swdc_di_get_buttons(uint16_t *gamebtn_out);
static uint8_t swdc_di_decode_pov(DWORD pov);
static void swdc_di_get_analogs(struct swdc_io_analog_state *out);
static const struct swdc_di_axis swdc_di_axes[] = {
/* Just map DIJOYSTATE for now, we can map DIJOYSTATE2 later if needed */
{ .name = L"X", .off = DIJOFS_X },
{ .name = L"Y", .off = DIJOFS_Y },
{ .name = L"Z", .off = DIJOFS_Z },
{ .name = L"RX", .off = DIJOFS_RX },
{ .name = L"RY", .off = DIJOFS_RY },
{ .name = L"RZ", .off = DIJOFS_RZ },
{ .name = L"U", .off = DIJOFS_SLIDER(0) },
{ .name = L"V", .off = DIJOFS_SLIDER(1) },
};
static const struct swdc_io_backend swdc_di_backend = {
.get_gamebtns = swdc_di_get_buttons,
.get_analogs = swdc_di_get_analogs,
};
static HWND swdc_di_wnd;
static IDirectInput8W *swdc_di_api;
static IDirectInputDevice8W *swdc_di_dev;
static IDirectInputEffect *swdc_di_fx;
static size_t swdc_di_off_brake;
static size_t swdc_di_off_accel;
static uint8_t swdc_di_shift_dn;
static uint8_t swdc_di_shift_up;
static uint8_t swdc_di_view_chg;
static uint8_t swdc_di_start;
static uint8_t swdc_di_wheel_green;
static uint8_t swdc_di_wheel_red;
static uint8_t swdc_di_wheel_blue;
static uint8_t swdc_di_wheel_yellow;
static bool swdc_di_reverse_brake_axis;
static bool swdc_di_reverse_accel_axis;
HRESULT swdc_di_init(
const struct swdc_di_config *cfg,
HINSTANCE inst,
const struct swdc_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(cfg != NULL);
assert(backend != NULL);
*backend = NULL;
hr = swdc_di_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
hr = swdc_io_wnd_create(inst, &swdc_di_wnd);
if (FAILED(hr)) {
return hr;
}
/* SWDC has some built-in DirectInput support that is not
particularly useful. swdchook shorts this out by redirecting dinput8.dll
to a no-op implementation of DirectInput. However, swdcio 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("DirectInput: LoadLibrary failed: %08x\n", (int) hr);
return hr;
}
api_entry = (void *) GetProcAddress(dinput8, "DirectInput8Create");
if (api_entry == NULL) {
dprintf("DirectInput: GetProcAddress failed\n");
return E_FAIL;
}
hr = api_entry(
inst,
DIRECTINPUT_VERSION,
&IID_IDirectInput8W,
(void **) &swdc_di_api,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: API create failed: %08x\n", (int) hr);
return hr;
}
hr = IDirectInput8_EnumDevices(
swdc_di_api,
DI8DEVCLASS_GAMECTRL,
swdc_di_enum_callback,
(void *) cfg,
DIEDFL_ATTACHEDONLY);
if (FAILED(hr)) {
dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr);
return hr;
}
if (swdc_di_dev == NULL) {
dprintf("Wheel: Controller not found\n");
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = swdc_di_dev_start(swdc_di_dev, swdc_di_wnd);
if (FAILED(hr)) {
return hr;
}
swdc_di_dev_start_fx(swdc_di_dev, &swdc_di_fx);
dprintf("DirectInput: Controller initialized\n");
*backend = &swdc_di_backend;
return S_OK;
}
static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg)
{
const struct swdc_di_axis *brake_axis;
const struct swdc_di_axis *accel_axis;
int i;
brake_axis = swdc_di_get_axis(cfg->brake_axis);
accel_axis = swdc_di_get_axis(cfg->accel_axis);
if (brake_axis == NULL) {
dprintf("Wheel: Invalid brake axis: %S\n", cfg->brake_axis);
return E_INVALIDARG;
}
if (accel_axis == NULL) {
dprintf("Wheel: Invalid accel axis: %S\n", cfg->accel_axis);
return E_INVALIDARG;
}
if (cfg->start > 32) {
dprintf("Wheel: Invalid start button: %i\n", cfg->start);
return E_INVALIDARG;
}
if (cfg->view_chg > 32) {
dprintf("Wheel: Invalid view change button: %i\n", cfg->view_chg);
return E_INVALIDARG;
}
if (cfg->shift_dn > 32) {
dprintf("Wheel: Invalid shift down button: %i\n", cfg->shift_dn);
return E_INVALIDARG;
}
if (cfg->shift_up > 32) {
dprintf("Wheel: Invalid shift up button: %i\n", cfg->shift_up);
return E_INVALIDARG;
}
if (cfg->wheel_green > 32) {
dprintf("Wheel: Invalid steering wheel green button: %i\n", cfg->wheel_green);
return E_INVALIDARG;
}
if (cfg->wheel_red > 32) {
dprintf("Wheel: Invalid steering wheel red button: %i\n", cfg->wheel_red);
return E_INVALIDARG;
}
if (cfg->wheel_blue > 32) {
dprintf("Wheel: Invalid steering wheel blue button: %i\n", cfg->wheel_blue);
return E_INVALIDARG;
}
if (cfg->wheel_yellow > 32) {
dprintf("Wheel: Invalid steering wheel yellow button: %i\n", cfg->wheel_yellow);
return E_INVALIDARG;
}
/* Print some debug output to make sure config works... */
dprintf("Wheel: --- Begin configuration ---\n");
dprintf("Wheel: Device name . . . . : Contains \"%S\"\n",
cfg->device_name);
dprintf("Wheel: Brake axis . . . . . . : %S\n", accel_axis->name);
dprintf("Wheel: Accelerator axis . . . : %S\n", brake_axis->name);
dprintf("Wheel: Start button . . . . . : %i\n", cfg->start);
dprintf("Wheel: View Change button . . : %i\n", cfg->view_chg);
dprintf("Wheel: Shift Down button . . : %i\n", cfg->shift_dn);
dprintf("Wheel: Shift Up button . . . : %i\n", cfg->shift_up);
dprintf("Wheel: Steering Green button : %i\n", cfg->wheel_green);
dprintf("Wheel: Steering Red button . : %i\n", cfg->wheel_red);
dprintf("Wheel: Steering Blue button . : %i\n", cfg->wheel_blue);
dprintf("Wheel: Steering Yellow button : %i\n", cfg->wheel_yellow);
dprintf("Wheel: Reverse Brake Axis . . : %i\n", cfg->reverse_brake_axis);
dprintf("Wheel: Reverse Accel Axis . . : %i\n", cfg->reverse_accel_axis);
dprintf("Wheel: --- End configuration ---\n");
swdc_di_off_brake = accel_axis->off;
swdc_di_off_accel = brake_axis->off;
swdc_di_start = cfg->start;
swdc_di_view_chg = cfg->view_chg;
swdc_di_shift_dn = cfg->shift_dn;
swdc_di_shift_up = cfg->shift_up;
swdc_di_wheel_green = cfg->wheel_green;
swdc_di_wheel_red = cfg->wheel_red;
swdc_di_wheel_blue = cfg->wheel_blue;
swdc_di_wheel_yellow = cfg->wheel_yellow;
swdc_di_reverse_brake_axis = cfg->reverse_brake_axis;
swdc_di_reverse_accel_axis = cfg->reverse_accel_axis;
return S_OK;
}
static const struct swdc_di_axis *swdc_di_get_axis(const wchar_t *name)
{
const struct swdc_di_axis *axis;
size_t i;
for (i = 0 ; i < _countof(swdc_di_axes) ; i++) {
axis = &swdc_di_axes[i];
if (wstr_ieq(name, axis->name)) {
return axis;
}
}
return NULL;
}
static BOOL CALLBACK swdc_di_enum_callback(
const DIDEVICEINSTANCEW *dev,
void *ctx)
{
const struct swdc_di_config *cfg;
HRESULT hr;
cfg = ctx;
if (wcsstr(dev->tszProductName, cfg->device_name) == NULL) {
return DIENUM_CONTINUE;
}
dprintf("Wheel: Using DirectInput device \"%S\"\n", dev->tszProductName);
hr = IDirectInput8_CreateDevice(
swdc_di_api,
&dev->guidInstance,
&swdc_di_dev,
NULL);
if (FAILED(hr)) {
dprintf("Wheel: CreateDevice failed: %08x\n", (int) hr);
}
return DIENUM_STOP;
}
static void swdc_di_get_buttons(uint16_t *gamebtn_out)
{
union swdc_di_state state;
uint8_t gamebtn;
HRESULT hr;
assert(gamebtn_out != NULL);
hr = swdc_di_dev_poll(swdc_di_dev, swdc_di_wnd, &state);
if (FAILED(hr)) {
return;
}
gamebtn = swdc_di_decode_pov(state.st.rgdwPOV[0]);
if (swdc_di_start && state.st.rgbButtons[swdc_di_start - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_START;
}
if (swdc_di_view_chg && state.st.rgbButtons[swdc_di_view_chg - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_VIEW_CHANGE;
}
if (swdc_di_shift_dn && state.st.rgbButtons[swdc_di_shift_dn - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT;
}
if (swdc_di_shift_up && state.st.rgbButtons[swdc_di_shift_up - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT;
}
if (swdc_di_wheel_green && state.st.rgbButtons[swdc_di_wheel_green - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_GREEN;
}
if (swdc_di_wheel_red && state.st.rgbButtons[swdc_di_wheel_red - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_RED;
}
if (swdc_di_wheel_blue && state.st.rgbButtons[swdc_di_wheel_blue - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_BLUE;
}
if (swdc_di_wheel_yellow && state.st.rgbButtons[swdc_di_wheel_yellow - 1]) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_YELLOW;
}
*gamebtn_out = gamebtn;
}
static uint8_t swdc_di_decode_pov(DWORD pov)
{
switch (pov) {
case 0: return SWDC_IO_GAMEBTN_UP;
case 4500: return SWDC_IO_GAMEBTN_UP | SWDC_IO_GAMEBTN_RIGHT;
case 9000: return SWDC_IO_GAMEBTN_RIGHT;
case 13500: return SWDC_IO_GAMEBTN_RIGHT | SWDC_IO_GAMEBTN_DOWN;
case 18000: return SWDC_IO_GAMEBTN_DOWN;
case 22500: return SWDC_IO_GAMEBTN_DOWN | SWDC_IO_GAMEBTN_RIGHT;
case 27000: return SWDC_IO_GAMEBTN_LEFT;
case 31500: return SWDC_IO_GAMEBTN_LEFT | SWDC_IO_GAMEBTN_UP;
default: return 0;
}
}
static void swdc_di_get_analogs(struct swdc_io_analog_state *out)
{
union swdc_di_state state;
const LONG *brake;
const LONG *accel;
HRESULT hr;
assert(out != NULL);
hr = swdc_di_dev_poll(swdc_di_dev, swdc_di_wnd, &state);
if (FAILED(hr)) {
return;
}
brake = (LONG *) &state.bytes[swdc_di_off_brake];
accel = (LONG *) &state.bytes[swdc_di_off_accel];
out->wheel = state.st.lX - 32768;
if (swdc_di_reverse_brake_axis) {
out->brake = *brake;
} else {
out->brake = 65535 - *brake;
}
if (swdc_di_reverse_accel_axis) {
out->accel = *accel;
} else {
out->accel = 65535 - *accel;
}
}

9
swdcio/di.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include "swdcio/backend.h"
#include "swdcio/config.h"
HRESULT swdc_di_init(
const struct swdc_di_config *cfg,
HINSTANCE inst,
const struct swdc_io_backend **backend);

112
swdcio/dllmain.c Normal file
View File

@ -0,0 +1,112 @@
#include <windows.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "swdcio/backend.h"
#include "swdcio/config.h"
#include "swdcio/di.h"
#include "swdcio/swdcio.h"
#include "swdcio/xi.h"
#include "util/dprintf.h"
#include "util/str.h"
static struct swdc_io_config swdc_io_cfg;
static const struct swdc_io_backend *swdc_io_backend;
static bool swdc_io_coin;
uint16_t swdc_io_get_api_version(void)
{
return 0x0100;
}
HRESULT swdc_io_init(void)
{
HINSTANCE inst;
HRESULT hr;
assert(swdc_io_backend == NULL);
inst = GetModuleHandleW(NULL);
if (inst == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("GetModuleHandleW failed: %lx\n", hr);
return hr;
}
swdc_io_config_load(&swdc_io_cfg, L".\\segatools.ini");
if (wstr_ieq(swdc_io_cfg.mode, L"dinput")) {
hr = swdc_di_init(&swdc_io_cfg.di, inst, &swdc_io_backend);
} else if (wstr_ieq(swdc_io_cfg.mode, L"xinput")) {
hr = swdc_xi_init(&swdc_io_cfg.xi, &swdc_io_backend);
} else {
hr = E_INVALIDARG;
dprintf("swdc IO: Invalid IO mode \"%S\", use dinput or xinput\n",
swdc_io_cfg.mode);
}
return hr;
}
void swdc_io_get_opbtns(uint8_t *opbtn_out)
{
uint8_t opbtn;
assert(swdc_io_backend != NULL);
assert(opbtn_out != NULL);
opbtn = 0;
if (GetAsyncKeyState(swdc_io_cfg.vk_test) & 0x8000) {
opbtn |= SWDC_IO_OPBTN_TEST;
}
if (GetAsyncKeyState(swdc_io_cfg.vk_service) & 0x8000) {
opbtn |= SWDC_IO_OPBTN_SERVICE;
}
if (GetAsyncKeyState(swdc_io_cfg.vk_coin) & 0x8000) {
if (!swdc_io_coin) {
swdc_io_coin = true;
opbtn |= SWDC_IO_OPBTN_COIN;
}
} else {
swdc_io_coin = false;
}
*opbtn_out = opbtn;
}
void swdc_io_get_gamebtns(uint16_t *gamebtn_out)
{
assert(swdc_io_backend != NULL);
assert(gamebtn_out != NULL);
swdc_io_backend->get_gamebtns(gamebtn_out);
}
void swdc_io_get_analogs(struct swdc_io_analog_state *out)
{
struct swdc_io_analog_state tmp;
assert(out != NULL);
assert(swdc_io_backend != NULL);
swdc_io_backend->get_analogs(&tmp);
/* Apply steering wheel restriction. Real cabs only report about 77% of
the IO-3's max ADC output value when the wheel is turned to either of
its maximum positions. To match this behavior we set the default value
for the wheel restriction config parameter to 97 (out of 128). This
scaling factor is applied using fixed-point arithmetic below. */
out->wheel = (tmp.wheel * swdc_io_cfg.restrict_) / 128;
out->accel = tmp.accel;
out->brake = tmp.brake;
}

30
swdcio/meson.build Normal file
View File

@ -0,0 +1,30 @@
swdcio_lib = static_library(
'swdccio',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
c_pch : '../precompiled.h',
dependencies : [
dinput8_lib,
dxguid_lib,
xinput_lib,
],
link_with : [
util_lib,
],
sources : [
'backend.h',
'config.c',
'config.h',
'di.c',
'di.h',
'di-dev.c',
'di-dev.h',
'dllmain.c',
'swdcio.h',
'wnd.c',
'wnd.h',
'xi.c',
'xi.h',
],
)

8
swdcio/swdcio.def Normal file
View File

@ -0,0 +1,8 @@
LIBRARY swdcio
EXPORTS
swdc_io_init
swdc_io_poll
swdc_io_get_opbtns
swdc_io_get_gamebtns
swdc_io_get_analogs

98
swdcio/swdcio.h Normal file
View File

@ -0,0 +1,98 @@
#pragma once
#include <windows.h>
#include <stdint.h>
enum {
SWDC_IO_OPBTN_TEST = 0x01,
SWDC_IO_OPBTN_SERVICE = 0x02,
SWDC_IO_OPBTN_COIN = 0x04,
};
enum {
SWDC_IO_GAMEBTN_UP = 0x01,
SWDC_IO_GAMEBTN_DOWN = 0x02,
SWDC_IO_GAMEBTN_LEFT = 0x04,
SWDC_IO_GAMEBTN_RIGHT = 0x08,
SWDC_IO_GAMEBTN_START = 0x10,
SWDC_IO_GAMEBTN_VIEW_CHANGE = 0x20,
SWDC_IO_GAMEBTN_STEERING_BLUE = 0x40,
SWDC_IO_GAMEBTN_STEERING_GREEN = 0x80,
SWDC_IO_GAMEBTN_STEERING_RED = 0x100,
SWDC_IO_GAMEBTN_STEERING_YELLOW = 0x200,
SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT = 0x400,
SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT = 0x800,
};
struct swdc_io_analog_state {
/* Current steering wheel position, where zero is the centered position.
The game will accept any signed 16-bit position value, however a real
cabinet will report a value of approximately +/- 25230 when the wheel
is at full lock. Steering wheel positions of a magnitude greater than
this value are not possible on a real cabinet. */
int16_t wheel;
/* Current position of the accelerator pedal, where 0 is released. */
uint16_t accel;
/* Current position of the brake pedal, where 0 is released. */
uint16_t brake;
};
/* Get the version of the IDAC IO API that this DLL supports. This
function should return a positive 16-bit integer, where the high byte is
the major version and the low byte is the minor version (as defined by the
Semantic Versioning standard).
The latest API version as of this writing is 0x0100. */
uint16_t swdc_io_get_api_version(void);
/* Initialize the IO DLL. This is the second function that will be called on
your DLL, after mu3_io_get_api_version.
All subsequent calls to this API may originate from arbitrary threads.
Minimum API version: 0x0100 */
HRESULT swdc_io_init(void);
/* Send any queued outputs (of which there are currently none, though this may
change in subsequent API versions) and retrieve any new inputs.
Minimum API version: 0x0100 */
HRESULT swdc_io_poll(void);
/* Get the state of the cabinet's operator buttons as of the last poll. See
MU3_IO_OPBTN enum above: this contains bit mask definitions for button
states returned in *opbtn. All buttons are active-high.
Minimum API version: 0x0100 */
void swdc_io_get_opbtns(uint8_t *opbtn);
/* Get the state of the cabinet's gameplay buttons as of the last poll. See
MU3_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into
a left hand side set of inputs and a right hand side set of inputs: the bit
mappings are the same in both cases.
All buttons are active-high, even though some buttons' electrical signals
on a real cabinet are active-low.
Minimum API version: 0x0100 */
void swdc_io_get_gamebtns(uint16_t *gamebtn);
/* Poll the current state of the cabinet's JVS analog inputs. See structure
definition above for details.
Minimum API version: 0x0100 */
void swdc_io_get_analogs(struct swdc_io_analog_state *out);

86
swdcio/wnd.c Normal file
View File

@ -0,0 +1,86 @@
#include <windows.h>
#include <assert.h>
#include <string.h>
#include "util/dprintf.h"
/* DirectInput requires a window for correct initialization (and also force
feedback), so this source file provides some utilities for creating a
generic message-only window. */
static LRESULT WINAPI swdc_io_wnd_proc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam);
HRESULT swdc_io_wnd_create(HINSTANCE inst, HWND *out)
{
HRESULT hr;
WNDCLASSEXW wcx;
ATOM atom;
HWND hwnd;
assert(inst != NULL); /* We are not an EXE */
assert(out != NULL);
*out = NULL;
memset(&wcx, 0, sizeof(wcx));
wcx.cbSize = sizeof(wcx);
wcx.lpfnWndProc = swdc_io_wnd_proc;
wcx.hInstance = inst;
wcx.lpszClassName = L"SWDCIO";
atom = RegisterClassExW(&wcx);
if (atom == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("SWDCIO: RegisterClassExW failed: %08x\n", (int) hr);
goto fail;
}
hwnd = CreateWindowExW(
0,
(wchar_t *) (intptr_t) atom,
L"",
0,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
HWND_MESSAGE,
NULL,
inst,
NULL);
if (hwnd == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("SWDCIO: CreateWindowExW failed: %08x\n", (int) hr);
goto fail;
}
*out = hwnd;
return S_OK;
fail:
UnregisterClassW((wchar_t *) (intptr_t) atom, inst);
return hr;
}
static LRESULT WINAPI swdc_io_wnd_proc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg) {
default:
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
}

5
swdcio/wnd.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include <windows.h>
HRESULT swdc_io_wnd_create(HINSTANCE inst, HWND *out);

165
swdcio/xi.c Normal file
View File

@ -0,0 +1,165 @@
#include <windows.h>
#include <xinput.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "swdcio/backend.h"
#include "swdcio/config.h"
#include "swdcio/swdcio.h"
#include "swdcio/xi.h"
#include "util/dprintf.h"
static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out);
static void swdc_xi_get_analogs(struct swdc_io_analog_state *out);
static HRESULT swdc_xi_config_apply(const struct swdc_xi_config *cfg);
static const struct swdc_io_backend swdc_xi_backend = {
.get_gamebtns = swdc_xi_get_gamebtns,
.get_analogs = swdc_xi_get_analogs,
};
static bool swdc_xi_single_stick_steering;
HRESULT swdc_xi_init(const struct swdc_xi_config *cfg, const struct swdc_io_backend **backend)
{
HRESULT hr;
assert(cfg != NULL);
assert(backend != NULL);
hr = swdc_xi_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
dprintf("XInput: Using XInput controller\n");
*backend = &swdc_xi_backend;
return S_OK;
}
HRESULT swdc_io_poll(void)
{
return S_OK;
}
static HRESULT swdc_xi_config_apply(const struct swdc_xi_config *cfg)
{
dprintf("XInput: --- Begin configuration ---\n");
dprintf("XInput: Single Stick Steering : %i\n", cfg->single_stick_steering);
dprintf("XInput: --- End configuration ---\n");
swdc_xi_single_stick_steering = cfg->single_stick_steering;
return S_OK;
}
static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out)
{
uint16_t gamebtn;
XINPUT_STATE xi;
WORD xb;
assert(gamebtn_out != NULL);
gamebtn = 0;
memset(&xi, 0, sizeof(xi));
XInputGetState(0, &xi);
xb = xi.Gamepad.wButtons;
if (xb & XINPUT_GAMEPAD_DPAD_UP) {
gamebtn |= SWDC_IO_GAMEBTN_UP;
}
if (xb & XINPUT_GAMEPAD_DPAD_DOWN) {
gamebtn |= SWDC_IO_GAMEBTN_DOWN;
}
if (xb & XINPUT_GAMEPAD_DPAD_LEFT) {
gamebtn |= SWDC_IO_GAMEBTN_LEFT;
}
if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) {
gamebtn |= SWDC_IO_GAMEBTN_RIGHT;
}
if (xb & XINPUT_GAMEPAD_START) {
gamebtn |= SWDC_IO_GAMEBTN_START;
}
if (xb & XINPUT_GAMEPAD_BACK) {
gamebtn |= SWDC_IO_GAMEBTN_VIEW_CHANGE;
}
if (xb & XINPUT_GAMEPAD_A) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_GREEN;
}
if (xb & XINPUT_GAMEPAD_B) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_RED;
}
if (xb & XINPUT_GAMEPAD_X) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_BLUE;
}
if (xb & XINPUT_GAMEPAD_Y) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_YELLOW;
}
if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT;
}
if (xb & XINPUT_GAMEPAD_RIGHT_SHOULDER) {
gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT;
}
*gamebtn_out = gamebtn;
}
static void swdc_xi_get_analogs(struct swdc_io_analog_state *out)
{
XINPUT_STATE xi;
int left;
int right;
assert(out != NULL);
memset(&xi, 0, sizeof(xi));
XInputGetState(0, &xi);
left = xi.Gamepad.sThumbLX;
if (left < -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
left += XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
} else if (left > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
left -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
} else {
left = 0;
}
right = xi.Gamepad.sThumbRX;
if (right < -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
right += XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
} else if (right > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
right -= XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
} else {
right = 0;
}
if (swdc_xi_single_stick_steering) {
out->wheel = left;
} else {
out->wheel = (left + right) / 2;
}
out->accel = xi.Gamepad.bRightTrigger << 8;
out->brake = xi.Gamepad.bLeftTrigger << 8;
}

10
swdcio/xi.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
/* Can't call this xinput.h or it will conflict with <xinput.h> */
#include <windows.h>
#include "swdcio/backend.h"
#include "swdcio/config.h"
HRESULT swdc_xi_init(const struct swdc_xi_config *cfg, const struct swdc_io_backend **backend);