forked from Hay1tsme/segatools
swdc: first segatools added
This commit is contained in:
parent
89195ed60b
commit
ec072667b3
16
Package.mk
16
Package.mk
@ -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 \
|
||||||
|
@ -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
99
dist/swdc/segatools.ini
vendored
Normal 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
29
dist/swdc/start.bat
vendored
Normal 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
|
@ -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
53
swdchook/config.c
Normal 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
32
swdchook/config.h
Normal 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
112
swdchook/dllmain.c
Normal 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
137
swdchook/io4.c
Normal 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
7
swdchook/io4.h
Normal 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
32
swdchook/meson.build
Normal 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
112
swdchook/swdc-dll.c
Normal 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
22
swdchook/swdc-dll.h
Normal 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
19
swdchook/swdchook.def
Normal 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
186
swdchook/zinput.c
Normal 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
11
swdchook/zinput.h
Normal 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
11
swdcio/backend.h
Normal 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
111
swdcio/config.c
Normal 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
47
swdcio/config.h
Normal 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
163
swdcio/di-dev.c
Normal 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
19
swdcio/di-dev.h
Normal 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
420
swdcio/di.c
Normal 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
9
swdcio/di.h
Normal 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
112
swdcio/dllmain.c
Normal 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
30
swdcio/meson.build
Normal 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
8
swdcio/swdcio.def
Normal 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
98
swdcio/swdcio.h
Normal 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
86
swdcio/wnd.c
Normal 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
5
swdcio/wnd.h
Normal 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
165
swdcio/xi.c
Normal 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
10
swdcio/xi.h
Normal 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);
|
Loading…
Reference in New Issue
Block a user