swdc: first segatools added

This commit is contained in:
Dniel97 2023-07-14 00:52:50 +02:00
parent 89195ed60b
commit ec072667b3
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
31 changed files with 2167 additions and 1 deletions

View File

@ -88,6 +88,21 @@ $(BUILD_DIR_ZIP)/idac.zip:
$(V)strip $(BUILD_DIR_ZIP)/idac/*.{exe,dll} $(V)strip $(BUILD_DIR_ZIP)/idac/*.{exe,dll}
$(V)cd $(BUILD_DIR_ZIP)/idac ; zip -r ../idac.zip * $(V)cd $(BUILD_DIR_ZIP)/idac ; zip -r ../idac.zip *
$(BUILD_DIR_ZIP)/swdc.zip:
$(V)echo ... $@
$(V)mkdir -p $(BUILD_DIR_ZIP)/swdc
$(V)mkdir -p $(BUILD_DIR_ZIP)/swdc/DEVICE
$(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \
$(BUILD_DIR_64)/swdchook/swdchook.dll \
$(DIST_DIR)/swdc/segatools.ini \
$(DIST_DIR)/swdc/start.bat \
$(BUILD_DIR_ZIP)/swdc
$(V)cp pki/billing.pub \
pki/ca.crt \
$(BUILD_DIR_ZIP)/swdc/DEVICE
$(V)strip $(BUILD_DIR_ZIP)/swdc/*.{exe,dll}
$(V)cd $(BUILD_DIR_ZIP)/swdc ; zip -r ../swdc.zip *
$(BUILD_DIR_ZIP)/mercury.zip: $(BUILD_DIR_ZIP)/mercury.zip:
$(V)echo ... $@ $(V)echo ... $@
$(V)mkdir -p $(BUILD_DIR_ZIP)/mercury $(V)mkdir -p $(BUILD_DIR_ZIP)/mercury
@ -135,6 +150,7 @@ $(BUILD_DIR_ZIP)/segatools.zip: \
$(BUILD_DIR_ZIP)/doc.zip \ $(BUILD_DIR_ZIP)/doc.zip \
$(BUILD_DIR_ZIP)/idz.zip \ $(BUILD_DIR_ZIP)/idz.zip \
$(BUILD_DIR_ZIP)/idac.zip \ $(BUILD_DIR_ZIP)/idac.zip \
$(BUILD_DIR_ZIP)/swdc.zip \
$(BUILD_DIR_ZIP)/mercury.zip \ $(BUILD_DIR_ZIP)/mercury.zip \
$(BUILD_DIR_ZIP)/mu3.zip \ $(BUILD_DIR_ZIP)/mu3.zip \
CHANGELOG.md \ CHANGELOG.md \

View File

@ -1,6 +1,6 @@
# Segatools # Segatools
Version: `v005` Version: `2023-07-14`
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.
@ -14,6 +14,9 @@ Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platfo
* [Chunithm Crystal (Plus)](doc/chunihook.md) * [Chunithm Crystal (Plus)](doc/chunihook.md)
* Initial D * Initial D
* [Initial D Arcade Stage Zero](doc/idzhook.md) * [Initial D Arcade Stage Zero](doc/idzhook.md)
* Initial D The Arcade
* SEGA World Drivers Championship
* SEGA World Drivers Championship 2019
* Wacca * Wacca
* Wacca Lilly R (WIP) * Wacca Lilly R (WIP)

99
dist/swdc/segatools.ini vendored Normal file
View File

@ -0,0 +1,99 @@
[vfs]
; Insert the path to the game AMFS directory here (contains ICF1 and ICF2)
amfs=
; Insert the path to the game Option directory here (contains OPxx directories)
option=
; Create an empty directory somewhere and insert the path here.
; This directory may be shared between multiple SEGA games.
; NOTE: This has nothing to do with Windows %APPDATA%.
appdata=
[aime]
; Controls emulation of the Aime card reader assembly.
enable=1
aimePath=DEVICE\aime.txt
felicaGen=0
[dns]
; Insert the hostname or IP address of the server you wish to use here.
; Note that 127.0.0.1, localhost etc are specifically rejected.
default=127.0.0.1
[netenv]
; Simulate an ideal LAN environment. This may interfere with head-to-head play.
; SEGA games are somewhat picky about their LAN environment, so leaving this
; setting enabled is recommended.
enable=1
[keychip]
; The /24 LAN subnet that the emulated keychip will tell the game to expect.
; You must set this to your LAN's IP subnet, and that subnet must start with 192.168.
subnet=192.168.100.0
[aimeio]
; To use a custom card reader IO DLL enter its path here.
; Leave empty if you want to use Segatools built-in keyboard input.
path=
[swdcio]
; To use a custom SEGA World Drivers Championship DLL enter its path here.
; Leave empty if you want to use Segatools built-in gamepad/wheel input.
path=
[io4]
; Test button virtual-key code. Default is the 1 key.
test=0x31
; Service button virtual-key code. Default is the 2 key.
service=0x32
; Keyboard button to increment coin counter. Default is the 3 key.
coin=0x33
; Input API selection for IO4 input emulator.
; Set "xinput" to use a gamepad and "dinput" to use a steering wheel.
mode=xinput
; Use the left thumbstick for steering instead of both on XInput Controllers.
; Not recommended as it will not give you the precision needed for this game
singleStickSteering=1
; Adjust scaling for steering wheel input.
;
; This setting scales the steering wheel input so that the maximum positive
; and minimum negative steering inputs reported in the operator menu's input
; test screen do not exceed the value below. The maximum possible value is 128,
; and the value that matches the input range of a real cabinet is 97.
;
; NOTE: This is not the same thing as DirectInput steering wheel movement
; range! Segatools cannot control the maximum angle of your physical steering
; wheel controller, this setting is vendor-specific and can only be adjusted
; in the Control Panel.
restrict=97
[dinput]
; Name of the DirectInput wheel to use (or any text that occurs in its name)
; Example: TMX
;
; If this is left blank then the first DirectInput device will be used.
deviceName=
; Pedal mappings. Valid axis names are:
;
; X, Y, Z, RX, RY, RZ, U, V
;
; (U and V are old names for Slider 1 and Slider 2).
; The examples below are valid for a Thrustmaster TMX.
brakeAxis=RZ
accelAxis=Y
; DirectInput button numbers to map to menu inputs. Note that buttons are
; numbered from 1; some software numbers buttons from 0.
start=3
viewChg=10
; Button mappings for the steering wheel paddles. Note shiftDn is the
; left paddle and shiftUp is the right paddle.
shiftDn=1
shiftUp=2
; Button mappings for the steering wheel buttons.
wheelGreen=4
wheelRed=5
wheelBlue=6
wheelYellow=7
; Invert the accelerator and or brake axis
; (Needed when using DirectInput for the Dualshock 4 for example)
reverseAccelAxis=0
reverseBrakeAxis=0

29
dist/swdc/start.bat vendored Normal file
View File

@ -0,0 +1,29 @@
@echo off
pushd %~dp0
REM set the APP_DIR to the Y drive
set APP_DIR=Y:\SWDC
REM create the APP_DIR if it doesn't exist and redirect it to the TEMP folder
if not exist "%APP_DIR%" (
subst Y: %TEMP%
REM timeout /t 1
if not exist "%APP_DIR%" (
mkdir "%APP_DIR%"
)
)
echo Mounted the Y:\ drive to the %TEMP%\SWDC folder
REM start /min inject -d -k swdchook.dll amdaemon.exe -f -c config.json config_LanClient.json config_MiniCabinet.json
start /min inject -d -k swdchook.dll amdaemon.exe -f -c config.json config_LanServer.json config_MiniCabinet.json
inject -d -k swdchook.dll ..\Todoroki\Binaries\Win64\Todoroki-Win64-Shipping.exe -launch=MiniCabinet -ABSLOG="..\..\..\..\..\Userdata\GameProject.log" -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED
taskkill /f /im amdaemon.exe > nul 2>&1
REM unmount the APP_DIR
subst Y: /d > nul 2>&1
echo.
echo Game processes have terminated
pause

View File

@ -59,6 +59,7 @@ subdir('divaio')
subdir('carolio') subdir('carolio')
subdir('idzio') subdir('idzio')
subdir('idacio') subdir('idacio')
subdir('swdcio')
subdir('mu3io') subdir('mu3io')
subdir('mercuryio') subdir('mercuryio')
subdir('cxbio') subdir('cxbio')
@ -68,6 +69,7 @@ subdir('divahook')
subdir('carolhook') subdir('carolhook')
subdir('idzhook') subdir('idzhook')
subdir('idachook') subdir('idachook')
subdir('swdchook')
subdir('minihook') subdir('minihook')
subdir('mu3hook') subdir('mu3hook')
subdir('mercuryhook') subdir('mercuryhook')

53
swdchook/config.c Normal file
View File

@ -0,0 +1,53 @@
#include <assert.h>
#include <stddef.h>
#include "board/config.h"
#include "board/sg-reader.h"
#include "hooklib/config.h"
#include "hooklib/dvd.h"
#include "swdchook/config.h"
#include "swdchook/swdc-dll.h"
#include "platform/config.h"
#include "platform/platform.h"
void swdc_dll_config_load(
struct swdc_dll_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"swdcio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
}
void swdc_hook_config_load(
struct swdc_hook_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
platform_config_load(&cfg->platform, filename);
aime_config_load(&cfg->aime, filename);
swdc_dll_config_load(&cfg->dll, filename);
zinput_config_load(&cfg->zinput, filename);
dvd_config_load(&cfg->dvd, filename);
io4_config_load(&cfg->io4, 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);
}

32
swdchook/config.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include "board/config.h"
#include "hooklib/dvd.h"
#include "swdchook/swdc-dll.h"
#include "swdchook/zinput.h"
#include "platform/platform.h"
struct swdc_hook_config {
struct platform_config platform;
struct aime_config aime;
struct dvd_config dvd;
struct io4_config io4;
struct swdc_dll_config dll;
struct zinput_config zinput;
};
void swdc_dll_config_load(
struct swdc_dll_config *cfg,
const wchar_t *filename);
void swdc_hook_config_load(
struct swdc_hook_config *cfg,
const wchar_t *filename);
void zinput_config_load(struct zinput_config *cfg, const wchar_t *filename);

112
swdchook/dllmain.c Normal file
View File

@ -0,0 +1,112 @@
#include <windows.h>
#include <shlwapi.h>
#include <stdlib.h>
#include "board/sg-reader.h"
#include "board/io4.h"
#include "board/vfd.h"
#include "hook/process.h"
#include "hooklib/dvd.h"
#include "hooklib/serial.h"
#include "hooklib/spike.h"
#include "swdchook/config.h"
#include "swdchook/swdc-dll.h"
#include "swdchook/io4.h"
#include "swdchook/zinput.h"
#include "platform/platform.h"
#include "util/dprintf.h"
static HMODULE swdc_hook_mod;
static process_entry_t swdc_startup;
static struct swdc_hook_config swdc_hook_cfg;
static DWORD CALLBACK swdc_pre_startup(void)
{
HRESULT hr;
dprintf("--- Begin swdc_pre_startup ---\n");
/* Config load */
swdc_hook_config_load(&swdc_hook_cfg, L".\\segatools.ini");
/* Hook Win32 APIs */
serial_hook_init();
zinput_hook_init(&swdc_hook_cfg.zinput);
dvd_hook_init(&swdc_hook_cfg.dvd, swdc_hook_mod);
/* Initialize emulation hooks */
hr = platform_hook_init(
&swdc_hook_cfg.platform,
"SDDS",
"ACA4",
swdc_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = sg_reader_hook_init(&swdc_hook_cfg.aime, 3, swdc_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = vfd_hook_init(4);
if (FAILED(hr)) {
return hr;
}
hr = swdc_dll_init(&swdc_hook_cfg.dll, swdc_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = swdc_io4_hook_init(&swdc_hook_cfg.io4);
if (FAILED(hr)) {
goto fail;
}
/* Initialize debug helpers */
spike_hook_init(L".\\segatools.ini");
dprintf("--- End swdc_pre_startup ---\n");
/* Jump to EXE start address */
return swdc_startup();
fail:
ExitProcess(EXIT_FAILURE);
}
BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx)
{
HRESULT hr;
if (cause != DLL_PROCESS_ATTACH) {
return TRUE;
}
swdc_hook_mod = mod;
hr = process_hijack_startup(swdc_pre_startup, &swdc_startup);
if (!SUCCEEDED(hr)) {
dprintf("Failed to hijack process startup: %x\n", (int) hr);
}
return SUCCEEDED(hr);
}

137
swdchook/io4.c Normal file
View File

@ -0,0 +1,137 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "board/io4.h"
#include "swdchook/swdc-dll.h"
#include "util/dprintf.h"
static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state);
static uint16_t coins;
static const struct io4_ops swdc_io4_ops = {
.poll = swdc_io4_poll,
};
HRESULT swdc_io4_hook_init(const struct io4_config *cfg)
{
HRESULT hr;
assert(swdc_dll.init != NULL);
hr = io4_hook_init(cfg, &swdc_io4_ops, NULL);
if (FAILED(hr)) {
return hr;
}
return swdc_dll.init();
}
static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state)
{
uint8_t opbtn;
uint16_t gamebtn;
struct swdc_io_analog_state analog_state;
HRESULT hr;
assert(swdc_dll.poll != NULL);
assert(swdc_dll.get_opbtns != NULL);
assert(swdc_dll.get_gamebtns != NULL);
assert(swdc_dll.get_analogs != NULL);
memset(state, 0, sizeof(*state));
memset(&analog_state, 0, sizeof(analog_state));
hr = swdc_dll.poll();
if (FAILED(hr)) {
return hr;
}
opbtn = 0;
gamebtn = 0;
swdc_dll.get_opbtns(&opbtn);
swdc_dll.get_gamebtns(&gamebtn);
swdc_dll.get_analogs(&analog_state);
if (opbtn & SWDC_IO_OPBTN_TEST) {
state->buttons[0] |= IO4_BUTTON_TEST;
}
if (opbtn & SWDC_IO_OPBTN_SERVICE) {
state->buttons[0] |= IO4_BUTTON_SERVICE;
}
if (opbtn & SWDC_IO_OPBTN_COIN) {
coins++;
}
state->chutes[0] = coins << 8;
/* Update Cabinet buttons */
if (gamebtn & SWDC_IO_GAMEBTN_START) {
state->buttons[0] |= 1 << 7;
}
if (gamebtn & SWDC_IO_GAMEBTN_VIEW_CHANGE) {
state->buttons[0] |= 1 << 1;
}
if (gamebtn & SWDC_IO_GAMEBTN_UP) {
state->buttons[0] |= 1 << 5;
}
if (gamebtn & SWDC_IO_GAMEBTN_DOWN) {
state->buttons[0] |= 1 << 4;
}
if (gamebtn & SWDC_IO_GAMEBTN_LEFT) {
state->buttons[0] |= 1 << 3;
}
if (gamebtn & SWDC_IO_GAMEBTN_RIGHT) {
state->buttons[0] |= 1 << 2;
}
/* Update steering wheel buttons */
if (gamebtn & SWDC_IO_GAMEBTN_STEERING_BLUE) {
state->buttons[1] |= 1 << 15;
}
if (gamebtn & SWDC_IO_GAMEBTN_STEERING_GREEN) {
state->buttons[1] |= 1 << 14;
}
if (gamebtn & SWDC_IO_GAMEBTN_STEERING_RED) {
state->buttons[1] |= 1 << 13;
}
if (gamebtn & SWDC_IO_GAMEBTN_STEERING_YELLOW) {
state->buttons[1] |= 1 << 12;
}
if (gamebtn & SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT) {
state->buttons[1] |= 1 << 1;
}
if (gamebtn & SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT) {
state->buttons[1] |= 1 << 0;
}
/* Steering wheel increases left-to-right.
Use 0x8000 as the center point. */
state->adcs[0] = 0x8000 + analog_state.wheel;
state->adcs[1] = analog_state.accel;
state->adcs[2] = analog_state.brake;
return S_OK;
}

7
swdchook/io4.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "board/io4.h"
HRESULT swdc_io4_hook_init(const struct io4_config *cfg);

32
swdchook/meson.build Normal file
View File

@ -0,0 +1,32 @@
shared_library(
'swdchook',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
vs_module_defs : 'swdchook.def',
c_pch : '../precompiled.h',
dependencies : [
capnhook.get_variable('hook_dep'),
capnhook.get_variable('hooklib_dep'),
xinput_lib,
],
link_with : [
aimeio_lib,
board_lib,
hooklib_lib,
swdcio_lib,
platform_lib,
util_lib,
],
sources : [
'config.c',
'config.h',
'dllmain.c',
'swdc-dll.c',
'swdc-dll.h',
'io4.c',
'io4.h',
'zinput.c',
'zinput.h',
],
)

112
swdchook/swdc-dll.c Normal file
View File

@ -0,0 +1,112 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include "swdchook/swdc-dll.h"
#include "util/dll-bind.h"
#include "util/dprintf.h"
const struct dll_bind_sym swdc_dll_syms[] = {
{
.sym = "swdc_io_init",
.off = offsetof(struct swdc_dll, init),
}, {
.sym = "swdc_io_poll",
.off = offsetof(struct swdc_dll, poll),
}, {
.sym = "swdc_io_get_opbtns",
.off = offsetof(struct swdc_dll, get_opbtns),
}, {
.sym = "swdc_io_get_gamebtns",
.off = offsetof(struct swdc_dll, get_gamebtns),
}, {
.sym = "swdc_io_get_analogs",
.off = offsetof(struct swdc_dll, get_analogs),
}
};
struct swdc_dll swdc_dll;
// Copypasta DLL binding and diagnostic message boilerplate.
// Not much of this lends itself to being easily factored out. Also there
// will be a lot of API-specific branching code here eventually as new API
// versions get defined, so even though these functions all look the same
// now this won't remain the case forever.
HRESULT swdc_dll_init(const struct swdc_dll_config *cfg, HINSTANCE self)
{
uint16_t (*get_api_version)(void);
const struct dll_bind_sym *sym;
HINSTANCE owned;
HINSTANCE src;
HRESULT hr;
assert(cfg != NULL);
assert(self != NULL);
if (cfg->path[0] != L'\0') {
owned = LoadLibraryW(cfg->path);
if (owned == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("SWDC IO: Failed to load IO DLL: %lx: %S\n",
hr,
cfg->path);
goto end;
}
dprintf("SWDC IO: Using custom IO DLL: %S\n", cfg->path);
src = owned;
} else {
owned = NULL;
src = self;
}
get_api_version = (void *) GetProcAddress(src, "swdc_io_get_api_version");
if (get_api_version != NULL) {
swdc_dll.api_version = get_api_version();
} else {
swdc_dll.api_version = 0x0100;
dprintf("Custom IO DLL does not expose swdc_io_get_api_version, "
"assuming API version 1.0.\n"
"Please ask the developer to update their DLL.\n");
}
if (swdc_dll.api_version >= 0x0200) {
hr = E_NOTIMPL;
dprintf("SWDC IO: Custom IO DLL implements an unsupported "
"API version (%#04x). Please update Segatools.\n",
swdc_dll.api_version);
goto end;
}
sym = swdc_dll_syms;
hr = dll_bind(&swdc_dll, src, &sym, _countof(swdc_dll_syms));
if (FAILED(hr)) {
if (src != self) {
dprintf("SWDC IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
goto end;
} else {
dprintf("Internal error: could not reflect \"%s\"\n", sym->sym);
}
}
owned = NULL;
end:
if (owned != NULL) {
FreeLibrary(owned);
}
return hr;
}

22
swdchook/swdc-dll.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <windows.h>
#include "swdcio/swdcio.h"
struct swdc_dll {
uint16_t api_version;
HRESULT (*init)(void);
HRESULT (*poll)(void);
void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint16_t *gamebtn);
void (*get_analogs)(struct swdc_io_analog_state *out);
};
struct swdc_dll_config {
wchar_t path[MAX_PATH];
};
extern struct swdc_dll swdc_dll;
HRESULT swdc_dll_init(const struct swdc_dll_config *cfg, HINSTANCE self);

19
swdchook/swdchook.def Normal file
View File

@ -0,0 +1,19 @@
LIBRARY swdchook
EXPORTS
aime_io_get_api_version
aime_io_init
aime_io_led_set_color
aime_io_nfc_get_aime_id
aime_io_nfc_get_felica_id
aime_io_nfc_poll
amDllVideoClose @2
amDllVideoGetVBiosVersion @4
amDllVideoOpen @1
amDllVideoSetResolution @3
swdc_io_get_api_version
swdc_io_init
swdc_io_poll
swdc_io_get_opbtns
swdc_io_get_gamebtns
swdc_io_get_analogs

186
swdchook/zinput.c Normal file
View File

@ -0,0 +1,186 @@
#include <windows.h>
#include <dinput.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "swdchook/config.h"
#include "swdchook/zinput.h"
#include "hook/table.h"
#include "util/dprintf.h"
HRESULT WINAPI hook_DirectInput8Create(
HINSTANCE hinst,
DWORD dwVersion,
REFIID riidltf,
LPVOID *ppvOut,
LPUNKNOWN punkOuter);
static unsigned long WINAPI hook_AddRef(IUnknown *self);
static unsigned long WINAPI hook_Release(IUnknown *self);
static HRESULT WINAPI hook_CreateDevice(
IDirectInput8W *self,
REFGUID rguid,
LPDIRECTINPUTDEVICE8W * lplpDirectInputDevice,
LPUNKNOWN pUnkOuter);
static HRESULT WINAPI hook_EnumDevices(
IDirectInput8W *self,
DWORD dwDevType,
LPDIENUMDEVICESCALLBACKW lpCallback,
LPVOID pvRef,
DWORD dwFlags);
static HRESULT WINAPI hook_SetDataFormat(
IDirectInputDevice8W *self,
LPCDIDATAFORMAT lpdf);
static HRESULT WINAPI hook_SetCooperativeLevel(
IDirectInputDevice8W *self,
HWND hwnd,
DWORD flags);
static HRESULT WINAPI hook_Acquire(IDirectInputDevice8W *self);
static HRESULT WINAPI hook_Unacquire(IDirectInputDevice8W *self);
static HRESULT WINAPI hook_GetDeviceState(
IDirectInputDevice8W *self,
DWORD cbData,
LPVOID lpvData);
static const IDirectInput8WVtbl api_vtbl = {
.AddRef = (void *) hook_AddRef,
.Release = (void *) hook_Release,
.CreateDevice = hook_CreateDevice,
.EnumDevices = hook_EnumDevices,
};
static const IDirectInput8W api = { (void *) &api_vtbl };
static const IDirectInputDevice8WVtbl dev_vtbl = {
.AddRef = (void *) hook_AddRef,
.Release = (void *) hook_Release,
.SetDataFormat = hook_SetDataFormat,
.SetCooperativeLevel= hook_SetCooperativeLevel,
.Acquire = hook_Acquire,
.Unacquire = hook_Unacquire,
.GetDeviceState = hook_GetDeviceState,
};
static const IDirectInputDevice8W dev = { (void *) &dev_vtbl };
static const struct hook_symbol zinput_hook_syms[] = {
{
.name = "DirectInput8Create",
.patch = hook_DirectInput8Create,
}
};
HRESULT zinput_hook_init(struct zinput_config *cfg)
{
assert(cfg != NULL);
if (!cfg->enable) {
return S_FALSE;
}
hook_table_apply(
NULL,
"dinput8.dll",
zinput_hook_syms,
_countof(zinput_hook_syms));
return S_OK;
}
HRESULT WINAPI hook_DirectInput8Create(
HINSTANCE hinst,
DWORD dwVersion,
REFIID riidltf,
LPVOID *ppvOut,
LPUNKNOWN punkOuter)
{
dprintf("ZInput: Blocking built-in DirectInput support\n");
*ppvOut = (void *) &api;
return S_OK;
}
static unsigned long WINAPI hook_AddRef(IUnknown *self)
{
return 1;
}
static unsigned long WINAPI hook_Release(IUnknown *self)
{
return 1;
}
static HRESULT WINAPI hook_CreateDevice(
IDirectInput8W *self,
REFGUID rguid,
LPDIRECTINPUTDEVICE8W *lplpDirectInputDevice,
LPUNKNOWN pUnkOuter)
{
dprintf("ZInput: %s\n", __func__);
*lplpDirectInputDevice = (void *) &dev;
return S_OK;
}
static HRESULT WINAPI hook_EnumDevices(
IDirectInput8W *self,
DWORD dwDevType,
LPDIENUMDEVICESCALLBACKW lpCallback,
LPVOID pvRef,
DWORD dwFlags)
{
dprintf("ZInput: %s\n", __func__);
return S_OK;
}
static HRESULT WINAPI hook_SetDataFormat(
IDirectInputDevice8W *self,
LPCDIDATAFORMAT lpdf)
{
dprintf("ZInput: %s\n", __func__);
return S_OK;
}
static HRESULT WINAPI hook_SetCooperativeLevel(
IDirectInputDevice8W *self,
HWND hwnd,
DWORD flags)
{
dprintf("ZInput: %s\n", __func__);
return S_OK;
}
static HRESULT WINAPI hook_Acquire(IDirectInputDevice8W *self)
{
return S_OK;
}
static HRESULT WINAPI hook_Unacquire(IDirectInputDevice8W *self)
{
return S_OK;
}
static HRESULT WINAPI hook_GetDeviceState(
IDirectInputDevice8W *self,
DWORD cbData,
LPVOID lpvData)
{
memset(lpvData, 0, cbData);
return S_OK;
}

11
swdchook/zinput.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
struct zinput_config {
bool enable;
};
HRESULT zinput_hook_init(struct zinput_config *cfg);

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);