idac: first segatools support

This commit is contained in:
Dniel97 2023-06-29 11:24:34 +02:00
parent ee6675dd73
commit da97d23b51
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
25 changed files with 351 additions and 441 deletions

View File

@ -13,14 +13,6 @@ appdata=
; Note that 127.0.0.1, localhost etc are specifically rejected. ; Note that 127.0.0.1, localhost etc are specifically rejected.
default=127.0.0.1 default=127.0.0.1
[ds]
; Region code on the emulated AMEX board DS EEPROM.
; 1: Japan
; 4: Export (some UI elements in English)
;
; NOTE: Changing this setting causes a factory reset.
region=1
[netenv] [netenv]
; Simulate an ideal LAN environment. This may interfere with head-to-head play. ; 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 ; SEGA games are somewhat picky about their LAN environment, so leaving this
@ -33,25 +25,24 @@ enable=1
; that subnet must start with 192.168. ; that subnet must start with 192.168.
subnet=192.168.100.0 subnet=192.168.100.0
[gpio]
; Emulated Nu DIP switch for Distribution Server setting.
;
; If multiple machines are present on the same LAN then set this to 1 on
; exactly one machine and set this to 0 on all others.
dipsw1=1
[aimeio] [aimeio]
; To use a custom card reader IO DLL enter its path here. ; To use a custom card reader IO DLL enter its path here.
; Leave empty if you want to use Segatools built-in keyboard input. ; Leave empty if you want to use Segatools built-in keyboard input.
path= path=
[idzio] [idacio]
; To use a custom Initial D Zero IO DLL enter its path here. ; To use a custom Initial D The Arcade IO DLL enter its path here.
; Leave empty if you want to use Segatools built-in gamepad/wheel input. ; Leave empty if you want to use Segatools built-in gamepad/wheel input.
path= path=
[io3] [io4]
; Input API selection for JVS input emulator. ; 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. ; Set "xinput" to use a gamepad and "dinput" to use a steering wheel.
mode=xinput mode=xinput
; Automatically reset the simulated shifter to Neutral when XInput Start is ; Automatically reset the simulated shifter to Neutral when XInput Start is
@ -59,7 +50,7 @@ mode=xinput
autoNeutral=1 autoNeutral=1
; Use the left thumbstick for steering instead of both on XInput Controllers. ; 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 ; Not recommended as it will not give you the precision needed for this game
singleStickSteering=0 singleStickSteering=1
; Adjust scaling for steering wheel input. ; Adjust scaling for steering wheel input.
; ;
; This setting scales the steering wheel input so that the maximum positive ; This setting scales the steering wheel input so that the maximum positive

6
dist/idac/start.bat vendored
View File

@ -2,8 +2,10 @@
pushd %~dp0 pushd %~dp0
start inject.exe -d -k idachook.dll amdaemon.exe -c config_common.json config_jp.json config_seat_1_jp.json config_seat_2_jp.json config_ex.json config_seat_1_ex.json config_seat_2_ex.json REM start /min inject.exe -d -k idachook.dll amdaemon.exe -f -c config_aime_high_ex.json config_aime_high_jp.json config_aime_normal_ex.json config_aime_normal_jp.json config_common.json config_ex.json config_jp.json config_laninstall_client_ex.json config_laninstall_client_jp.json config_laninstall_server_ex.json config_laninstall_server_jp.json config_seat_1_ex.json config_seat_1_jp.json config_seat_2_ex.json config_seat_2_jp.json config_seat_3_ex.json config_seat_3_jp.json config_seat_4_ex.json config_seat_4_jp.json config_seat_single_ex.json config_seat_single_jp.json
inject -d -k idachook.dll ../WindowsNoEditor/GameProject/Binaries/Win64/GameProject-Win64-Shipping.exe start /min inject -d -k idachook.dll amdaemon.exe -f -c config_aime_normal_jp.json config_common.json config_jp.json config_laninstall_server_jp.json config_seat_1_jp.json
inject -d -k idachook.dll ..\WindowsNoEditor\GameProject.exe -culture=ja launch=Cabinet ABSLOG="..\..\..\..\..\Userdata\GameProject.log" -Master -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED
taskkill /f /im amdaemon.exe > nul 2>&1
echo. echo.
echo Game processes have terminated echo Game processes have terminated

View File

@ -1,14 +1,9 @@
#include <assert.h> #include <assert.h>
#include <stddef.h> #include <stddef.h>
#include "amex/amex.h"
#include "amex/config.h"
#include "board/config.h" #include "board/config.h"
#include "board/sg-reader.h" #include "board/sg-reader.h"
#include "gfxhook/config.h"
#include "hooklib/config.h" #include "hooklib/config.h"
#include "hooklib/dvd.h" #include "hooklib/dvd.h"
@ -26,7 +21,7 @@ void idac_dll_config_load(
assert(filename != NULL); assert(filename != NULL);
GetPrivateProfileStringW( GetPrivateProfileStringW(
L"idzio", L"idacio",
L"path", L"path",
L"", L"",
cfg->path, cfg->path,
@ -42,12 +37,11 @@ void idac_hook_config_load(
assert(filename != NULL); assert(filename != NULL);
platform_config_load(&cfg->platform, filename); platform_config_load(&cfg->platform, filename);
amex_config_load(&cfg->amex, filename);
aime_config_load(&cfg->aime, filename); aime_config_load(&cfg->aime, filename);
dvd_config_load(&cfg->dvd, filename);
// gfx_config_load(&cfg->gfx, filename);
idac_dll_config_load(&cfg->dll, filename); idac_dll_config_load(&cfg->dll, filename);
zinput_config_load(&cfg->zinput, 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) void zinput_config_load(struct zinput_config *cfg, const wchar_t *filename)

View File

@ -3,11 +3,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "amex/amex.h" #include "board/config.h"
#include "board/sg-reader.h"
#include "gfxhook/gfx.h"
#include "hooklib/dvd.h" #include "hooklib/dvd.h"
@ -18,9 +14,9 @@
struct idac_hook_config { struct idac_hook_config {
struct platform_config platform; struct platform_config platform;
struct amex_config amex;
struct aime_config aime; struct aime_config aime;
struct dvd_config dvd; struct dvd_config dvd;
struct io4_config io4;
struct idac_dll_config dll; struct idac_dll_config dll;
struct zinput_config zinput; struct zinput_config zinput;
}; };

View File

@ -1,17 +1,11 @@
#include <windows.h> #include <windows.h>
#include <shlwapi.h> #include <shlwapi.h>
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <wchar.h>
#include "amex/amex.h"
#include "board/sg-reader.h" #include "board/sg-reader.h"
#include "board/io4.h"
// #include "gfxhook/d3d11.h" #include "board/vfd.h"
// #include "gfxhook/dxgi.h"
// #include "gfxhook/gfx.h"
#include "hook/process.h" #include "hook/process.h"
@ -21,13 +15,12 @@
#include "idachook/config.h" #include "idachook/config.h"
#include "idachook/idac-dll.h" #include "idachook/idac-dll.h"
#include "idachook/jvs.h" #include "idachook/io4.h"
#include "idachook/zinput.h" #include "idachook/zinput.h"
#include "platform/platform.h" #include "platform/platform.h"
#include "util/dprintf.h" #include "util/dprintf.h"
#include "util/lib.h"
static HMODULE idac_hook_mod; static HMODULE idac_hook_mod;
static process_entry_t idac_startup; static process_entry_t idac_startup;
@ -35,8 +28,6 @@ static struct idac_hook_config idac_hook_cfg;
static DWORD CALLBACK idac_pre_startup(void) static DWORD CALLBACK idac_pre_startup(void)
{ {
wchar_t *module_path;
wchar_t *file_name;
HRESULT hr; HRESULT hr;
dprintf("--- Begin idac_pre_startup ---\n"); dprintf("--- Begin idac_pre_startup ---\n");
@ -45,33 +36,9 @@ static DWORD CALLBACK idac_pre_startup(void)
idac_hook_config_load(&idac_hook_cfg, L".\\segatools.ini"); idac_hook_config_load(&idac_hook_cfg, L".\\segatools.ini");
/*
module_path = module_file_name(NULL);
if (module_path != NULL) {
file_name = PathFindFileNameW(module_path);
_wcslwr(file_name);
if (wcsstr(file_name, L"serverbox") != NULL) {
dprintf("Executable filename contains 'ServerBox', disabling full-screen mode\n");
idac_hook_cfg.gfx.windowed = true;
idac_hook_cfg.gfx.framed = true;
}
free(module_path);
module_path = NULL;
}
*/
/* Hook Win32 APIs */ /* Hook Win32 APIs */
serial_hook_init(); serial_hook_init();
// gfx_hook_init(&idac_hook_cfg.gfx);
// gfx_d3d11_hook_init(&idac_hook_cfg.gfx, idac_hook_mod);
// gfx_dxgi_hook_init(&idac_hook_cfg.gfx, idac_hook_mod);
zinput_hook_init(&idac_hook_cfg.zinput); zinput_hook_init(&idac_hook_cfg.zinput);
dvd_hook_init(&idac_hook_cfg.dvd, idac_hook_mod); dvd_hook_init(&idac_hook_cfg.dvd, idac_hook_mod);
@ -80,26 +47,33 @@ static DWORD CALLBACK idac_pre_startup(void)
hr = platform_hook_init( hr = platform_hook_init(
&idac_hook_cfg.platform, &idac_hook_cfg.platform,
"SDGT", "SDGT",
"ACA2", "ACA4",
idac_hook_mod); idac_hook_mod);
if (FAILED(hr)) { if (FAILED(hr)) {
goto fail; goto fail;
} }
hr = sg_reader_hook_init(&idac_hook_cfg.aime, 3, idac_hook_mod);
if (FAILED(hr)) {
goto fail;
}
// Not needed?
hr = vfd_hook_init(4);
if (FAILED(hr)) {
return hr;
}
hr = idac_dll_init(&idac_hook_cfg.dll, idac_hook_mod); hr = idac_dll_init(&idac_hook_cfg.dll, idac_hook_mod);
if (FAILED(hr)) { if (FAILED(hr)) {
goto fail; goto fail;
} }
hr = amex_hook_init(&idac_hook_cfg.amex, idac_jvs_init); hr = idac_io4_hook_init(&idac_hook_cfg.io4);
if (FAILED(hr)) {
goto fail;
}
hr = sg_reader_hook_init(&idac_hook_cfg.aime, 10, idac_hook_mod);
if (FAILED(hr)) { if (FAILED(hr)) {
goto fail; goto fail;

View File

@ -10,20 +10,23 @@
const struct dll_bind_sym idac_dll_syms[] = { const struct dll_bind_sym idac_dll_syms[] = {
{ {
.sym = "idac_io_jvs_init", .sym = "idac_io_init",
.off = offsetof(struct idac_dll, jvs_init), .off = offsetof(struct idac_dll, init),
}, { }, {
.sym = "idac_io_jvs_read_analogs", .sym = "idac_io_poll",
.off = offsetof(struct idac_dll, jvs_read_analogs), .off = offsetof(struct idac_dll, poll),
}, { }, {
.sym = "idac_io_jvs_read_buttons", .sym = "idac_io_get_opbtns",
.off = offsetof(struct idac_dll, jvs_read_buttons), .off = offsetof(struct idac_dll, get_opbtns),
}, { }, {
.sym = "idac_io_jvs_read_shifter", .sym = "idac_io_get_gamebtns",
.off = offsetof(struct idac_dll, jvs_read_shifter), .off = offsetof(struct idac_dll, get_gamebtns),
}, { }, {
.sym = "idac_io_jvs_read_coin_counter", .sym = "idac_io_get_shifter",
.off = offsetof(struct idac_dll, jvs_read_coin_counter), .off = offsetof(struct idac_dll, get_shifter),
}, {
.sym = "idac_io_get_analogs",
.off = offsetof(struct idac_dll, get_analogs),
} }
}; };
@ -51,14 +54,14 @@ HRESULT idac_dll_init(const struct idac_dll_config *cfg, HINSTANCE self)
if (owned == NULL) { if (owned == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError()); hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("IDZ IO: Failed to load IO DLL: %lx: %S\n", dprintf("IDAC IO: Failed to load IO DLL: %lx: %S\n",
hr, hr,
cfg->path); cfg->path);
goto end; goto end;
} }
dprintf("IDZ IO: Using custom IO DLL: %S\n", cfg->path); dprintf("IDAC IO: Using custom IO DLL: %S\n", cfg->path);
src = owned; src = owned;
} else { } else {
owned = NULL; owned = NULL;
@ -78,7 +81,7 @@ HRESULT idac_dll_init(const struct idac_dll_config *cfg, HINSTANCE self)
if (idac_dll.api_version >= 0x0200) { if (idac_dll.api_version >= 0x0200) {
hr = E_NOTIMPL; hr = E_NOTIMPL;
dprintf("IDZ IO: Custom IO DLL implements an unsupported " dprintf("IDAC IO: Custom IO DLL implements an unsupported "
"API version (%#04x). Please update Segatools.\n", "API version (%#04x). Please update Segatools.\n",
idac_dll.api_version); idac_dll.api_version);
@ -90,7 +93,7 @@ HRESULT idac_dll_init(const struct idac_dll_config *cfg, HINSTANCE self)
if (FAILED(hr)) { if (FAILED(hr)) {
if (src != self) { if (src != self) {
dprintf("IDZ IO: Custom IO DLL does not provide function " dprintf("IDAC IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for " "\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n", "further assistance.\n",
sym->sym); sym->sym);

View File

@ -6,11 +6,12 @@
struct idac_dll { struct idac_dll {
uint16_t api_version; uint16_t api_version;
HRESULT (*jvs_init)(void); HRESULT (*init)(void);
void (*jvs_read_analogs)(struct idac_io_analog_state *out); HRESULT (*poll)(void);
void (*jvs_read_buttons)(uint8_t *opbtn, uint8_t *gamebtn); void (*get_opbtns)(uint8_t *opbtn);
void (*jvs_read_shifter)(uint8_t *gear); void (*get_gamebtns)(uint8_t *gamebtn);
void (*jvs_read_coin_counter)(uint16_t *total); void (*get_shifter)(uint8_t *gear);
void (*get_analogs)(struct idac_io_analog_state *out);
}; };
struct idac_dll_config { struct idac_dll_config {

View File

@ -1,24 +0,0 @@
LIBRARY idachook
EXPORTS
CreateDXGIFactory
CreateDXGIFactory1
CreateDXGIFactory2
D3D11CreateDevice
D3D11CreateDeviceAndSwapChain
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
idac_io_get_api_version
idac_io_jvs_init
idac_io_jvs_read_analogs
idac_io_jvs_read_buttons
idac_io_jvs_read_coin_counter
idac_io_jvs_read_shifter

View File

@ -12,8 +12,9 @@ EXPORTS
amDllVideoOpen @1 amDllVideoOpen @1
amDllVideoSetResolution @3 amDllVideoSetResolution @3
idac_io_get_api_version idac_io_get_api_version
idac_io_jvs_init idac_io_init
idac_io_jvs_read_analogs idac_io_poll
idac_io_jvs_read_buttons idac_io_get_opbtns
idac_io_jvs_read_coin_counter idac_io_get_gamebtns
idac_io_jvs_read_shifter idac_io_get_shifter
idac_io_get_analogs

138
idachook/io4.c Normal file
View File

@ -0,0 +1,138 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "board/io4.h"
#include "idachook/idac-dll.h"
#include "util/dprintf.h"
static HRESULT idac_io4_poll(void *ctx, struct io4_state *state);
static uint16_t coins;
static const struct io4_ops idac_io4_ops = {
.poll = idac_io4_poll,
};
static const uint16_t idac_gear_signals[] = {
/* Neutral */
0x0000,
/* 1: Left|Up */
0x0028,
/* 2: Left|Down */
0x0018,
/* 3: Up */
0x0020,
/* 4: Down */
0x0010,
/* 5: Right|Up */
0x0024,
/* 6: Right|Down */
0x0014,
};
HRESULT idac_io4_hook_init(const struct io4_config *cfg)
{
HRESULT hr;
assert(idac_dll.init != NULL);
hr = io4_hook_init(cfg, &idac_io4_ops, NULL);
if (FAILED(hr)) {
return hr;
}
return idac_dll.init();
}
static HRESULT idac_io4_poll(void *ctx, struct io4_state *state)
{
uint8_t opbtn;
uint8_t gamebtn;
uint8_t gear;
struct idac_io_analog_state analog_state;
HRESULT hr;
assert(idac_dll.poll != NULL);
assert(idac_dll.get_opbtns != NULL);
assert(idac_dll.get_gamebtns != NULL);
assert(idac_dll.get_analogs != NULL);
assert(idac_dll.get_shifter != NULL);
memset(state, 0, sizeof(*state));
memset(&analog_state, 0, sizeof(analog_state));
hr = idac_dll.poll();
if (FAILED(hr)) {
return hr;
}
opbtn = 0;
gamebtn = 0;
gear = 0;
idac_dll.get_opbtns(&opbtn);
idac_dll.get_gamebtns(&gamebtn);
idac_dll.get_shifter(&gear);
idac_dll.get_analogs(&analog_state);
if (opbtn & IDAC_IO_OPBTN_TEST) {
state->buttons[0] |= IO4_BUTTON_TEST;
}
if (opbtn & IDAC_IO_OPBTN_SERVICE) {
state->buttons[0] |= IO4_BUTTON_SERVICE;
}
if (opbtn & IDAC_IO_OPBTN_COIN) {
coins++;
}
state->chutes[0] = coins << 8;
if (gamebtn & IDAC_IO_GAMEBTN_START) {
state->buttons[0] |= 1 << 7;
}
if (gamebtn & IDAC_IO_GAMEBTN_VIEW_CHANGE) {
state->buttons[0] |= 1 << 1;
}
if (gamebtn & IDAC_IO_GAMEBTN_UP) {
state->buttons[0] |= 1 << 5;
}
if (gamebtn & IDAC_IO_GAMEBTN_DOWN) {
state->buttons[0] |= 1 << 4;
}
if (gamebtn & IDAC_IO_GAMEBTN_LEFT) {
state->buttons[0] |= 1 << 3;
}
if (gamebtn & IDAC_IO_GAMEBTN_RIGHT) {
state->buttons[0] |= 1 << 2;
}
/* Update simulated six-speed shifter */
if (gear > 6) {
gear = 6;
}
state->buttons[1] = idac_gear_signals[gear];
/* 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
idachook/io4.h Normal file
View File

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

View File

@ -1,177 +0,0 @@
#include <windows.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "amex/jvs.h"
#include "board/io3.h"
#include "idachook/idac-dll.h"
#include "idachook/jvs.h"
#include "jvs/jvs-bus.h"
#include "util/dprintf.h"
static void idac_jvs_read_analogs(
void *ctx,
uint16_t *analogs,
uint8_t nanalogs);
static void idac_jvs_read_switches(void *ctx, struct io3_switch_state *out);
static void idac_jvs_read_coin_counter(
void *ctx,
uint8_t slot_no,
uint16_t *out);
static const struct io3_ops idac_jvs_io3_ops = {
.read_switches = idac_jvs_read_switches,
.read_analogs = idac_jvs_read_analogs,
.read_coin_counter = idac_jvs_read_coin_counter,
};
static const uint16_t idac_jvs_gear_signals[] = {
/* Neutral */
0x0000,
/* 1: Left|Up */
0x2800,
/* 2: Left|Down */
0x1800,
/* 3: Up */
0x2000,
/* 4: Down */
0x1000,
/* 5: Right|Up */
0x2400,
/* 6: Right|Down */
0x1400,
};
static struct io3 idac_jvs_io3;
HRESULT idac_jvs_init(struct jvs_node **out)
{
HRESULT hr;
assert(out != NULL);
assert(idac_dll.jvs_init != NULL);
dprintf("JVS I/O: Starting Initial D Zero backend DLL\n");
hr = idac_dll.jvs_init();
if (FAILED(hr)) {
dprintf("JVS I/O: Backend error, I/O disconnected; %x\n", (int) hr);
return hr;
}
io3_init(&idac_jvs_io3, NULL, &idac_jvs_io3_ops, NULL);
*out = io3_to_jvs_node(&idac_jvs_io3);
return S_OK;
}
static void idac_jvs_read_switches(void *ctx, struct io3_switch_state *out)
{
uint8_t opbtn;
uint8_t gamebtn;
uint8_t gear;
assert(out != NULL);
assert(idac_dll.jvs_read_buttons != NULL);
assert(idac_dll.jvs_read_shifter != NULL);
opbtn = 0;
gamebtn = 0;
gear = 0;
idac_dll.jvs_read_buttons(&opbtn, &gamebtn);
idac_dll.jvs_read_shifter(&gear);
/* Update gameplay buttons */
if (gamebtn & IDAC_IO_GAMEBTN_UP) {
out->p1 |= 1 << 13;
}
if (gamebtn & IDAC_IO_GAMEBTN_DOWN) {
out->p1 |= 1 << 12;
}
if (gamebtn & IDAC_IO_GAMEBTN_LEFT) {
out->p1 |= 1 << 11;
}
if (gamebtn & IDAC_IO_GAMEBTN_RIGHT) {
out->p1 |= 1 << 10;
}
if (gamebtn & IDAC_IO_GAMEBTN_START) {
out->p1 |= 1 << 15;
}
if (gamebtn & IDAC_IO_GAMEBTN_VIEW_CHANGE) {
out->p1 |= 1 << 9;
}
/* Update simulated six-speed shifter */
if (gear > 6) {
gear = 6;
}
out->p2 = idac_jvs_gear_signals[gear];
/* Update test/service buttons */
if (opbtn & IDAC_IO_OPBTN_TEST) {
out->system = 0x80;
} else {
out->system = 0;
}
if (opbtn & IDAC_IO_OPBTN_SERVICE) {
out->p1 |= 1 << 14;
}
}
static void idac_jvs_read_analogs(
void *ctx,
uint16_t *analogs,
uint8_t nanalogs)
{
struct idac_io_analog_state state;
assert(analogs != NULL);
assert(idac_dll.jvs_read_analogs != NULL);
memset(&state, 0, sizeof(state));
idac_dll.jvs_read_analogs(&state);
if (nanalogs > 0) {
analogs[0] = 0x8000 + state.wheel;
}
if (nanalogs > 1) {
analogs[1] = state.accel;
}
if (nanalogs > 2) {
analogs[2] = state.brake;
}
}
static void idac_jvs_read_coin_counter(
void *ctx,
uint8_t slot_no,
uint16_t *out)
{
assert(idac_dll.jvs_read_coin_counter != NULL);
if (slot_no > 0) {
return;
}
idac_dll.jvs_read_coin_counter(out);
}

View File

@ -1,7 +0,0 @@
#pragma once
#include <windows.h>
#include "jvs/jvs-bus.h"
HRESULT idac_jvs_init(struct jvs_node **root);

View File

@ -13,12 +13,9 @@ shared_library(
], ],
link_with : [ link_with : [
aimeio_lib, aimeio_lib,
amex_lib,
board_lib, board_lib,
# gfxhook_lib,
hooklib_lib, hooklib_lib,
idacio_lib, idacio_lib,
jvs_lib,
platform_lib, platform_lib,
util_lib, util_lib,
], ],
@ -28,8 +25,8 @@ shared_library(
'dllmain.c', 'dllmain.c',
'idac-dll.c', 'idac-dll.c',
'idac-dll.h', 'idac-dll.h',
'jvs.c', 'io4.c',
'jvs.h', 'io4.h',
'zinput.c', 'zinput.c',
'zinput.h', 'zinput.h',
], ],

View File

@ -5,7 +5,8 @@
#include "idacio/idacio.h" #include "idacio/idacio.h"
struct idac_io_backend { struct idac_io_backend {
void (*jvs_read_buttons)(uint8_t *gamebtn); void (*get_opbtns)(uint8_t *opbtn);
void (*jvs_read_shifter)(uint8_t *gear); void (*get_gamebtns)(uint8_t *gamebtn);
void (*jvs_read_analogs)(struct idac_io_analog_state *state); void (*get_shifter)(uint8_t *gear);
void (*get_analogs)(struct idac_io_analog_state *state);
}; };

View File

@ -77,7 +77,7 @@ void idac_xi_config_load(struct idac_xi_config *cfg, const wchar_t *filename)
assert(filename != NULL); assert(filename != NULL);
cfg->single_stick_steering = GetPrivateProfileIntW( cfg->single_stick_steering = GetPrivateProfileIntW(
L"io3", L"io4",
L"singleStickSteering", L"singleStickSteering",
0, 0,
filename); filename);
@ -88,13 +88,13 @@ void idac_io_config_load(struct idac_io_config *cfg, const wchar_t *filename)
assert(cfg != NULL); assert(cfg != NULL);
assert(filename != NULL); assert(filename != NULL);
cfg->vk_test = GetPrivateProfileIntW(L"io3", L"test", '1', filename); cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", '1', filename);
cfg->vk_service = GetPrivateProfileIntW(L"io3", L"service", '2', filename); cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", '2', filename);
cfg->vk_coin = GetPrivateProfileIntW(L"io3", L"coin", '3', filename); cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", '3', filename);
cfg->restrict_ = GetPrivateProfileIntW(L"io3", L"restrict", 97, filename); cfg->restrict_ = GetPrivateProfileIntW(L"io4", L"restrict", 97, filename);
GetPrivateProfileStringW( GetPrivateProfileStringW(
L"io3", L"io4",
L"mode", L"mode",
L"xinput", L"xinput",
cfg->mode, cfg->mode,
@ -114,7 +114,7 @@ void idac_shifter_config_load(
assert(filename != NULL); assert(filename != NULL);
cfg->auto_neutral = GetPrivateProfileIntW( cfg->auto_neutral = GetPrivateProfileIntW(
L"io3", L"io4",
L"autoNeutral", L"autoNeutral",
0, 0,
filename); filename);

View File

@ -29,12 +29,12 @@ static BOOL CALLBACK idac_di_enum_callback(
static BOOL CALLBACK idac_di_enum_callback_shifter( static BOOL CALLBACK idac_di_enum_callback_shifter(
const DIDEVICEINSTANCEW *dev, const DIDEVICEINSTANCEW *dev,
void *ctx); void *ctx);
static void idac_di_jvs_read_buttons(uint8_t *gamebtn_out); static void idac_di_get_buttons(uint8_t *gamebtn_out);
static uint8_t idac_di_decode_pov(DWORD pov); static uint8_t idac_di_decode_pov(DWORD pov);
static void idac_di_jvs_read_shifter(uint8_t *gear); static void idac_di_get_shifter(uint8_t *gear);
static void idac_di_jvs_read_shifter_pos(uint8_t *gear); static void idac_di_get_shifter_pos(uint8_t *gear);
static void idac_di_jvs_read_shifter_virt(uint8_t *gear); static void idac_di_get_shifter_virt(uint8_t *gear);
static void idac_di_jvs_read_analogs(struct idac_io_analog_state *out); static void idac_di_get_analogs(struct idac_io_analog_state *out);
static const struct idac_di_axis idac_di_axes[] = { static const struct idac_di_axis idac_di_axes[] = {
/* Just map DIJOYSTATE for now, we can map DIJOYSTATE2 later if needed */ /* Just map DIJOYSTATE for now, we can map DIJOYSTATE2 later if needed */
@ -49,9 +49,9 @@ static const struct idac_di_axis idac_di_axes[] = {
}; };
static const struct idac_io_backend idac_di_backend = { static const struct idac_io_backend idac_di_backend = {
.jvs_read_buttons = idac_di_jvs_read_buttons, .get_gamebtns = idac_di_get_buttons,
.jvs_read_shifter = idac_di_jvs_read_shifter, .get_shifter = idac_di_get_shifter,
.jvs_read_analogs = idac_di_jvs_read_analogs, .get_analogs = idac_di_get_analogs,
}; };
static HWND idac_di_wnd; static HWND idac_di_wnd;
@ -98,8 +98,8 @@ HRESULT idac_di_init(
} }
/* Initial D Zero has some built-in DirectInput support that is not /* Initial D Zero has some built-in DirectInput support that is not
particularly useful. idzhook shorts this out by redirecting dinput8.dll particularly useful. idachook shorts this out by redirecting dinput8.dll
to a no-op implementation of DirectInput. However, idzio does need to to a no-op implementation of DirectInput. However, idacio does need to
talk to the real operating system implementation of DirectInput without talk to the real operating system implementation of DirectInput without
the stub DLL interfering, so build a path to the stub DLL interfering, so build a path to
C:\Windows\System32\dinput.dll here. */ C:\Windows\System32\dinput.dll here. */
@ -374,7 +374,7 @@ static BOOL CALLBACK idac_di_enum_callback_shifter(
return DIENUM_STOP; return DIENUM_STOP;
} }
static void idac_di_jvs_read_buttons(uint8_t *gamebtn_out) static void idac_di_get_buttons(uint8_t *gamebtn_out)
{ {
union idac_di_state state; union idac_di_state state;
uint8_t gamebtn; uint8_t gamebtn;
@ -416,18 +416,18 @@ static uint8_t idac_di_decode_pov(DWORD pov)
} }
} }
static void idac_di_jvs_read_shifter(uint8_t *gear) static void idac_di_get_shifter(uint8_t *gear)
{ {
assert(gear != NULL); assert(gear != NULL);
if (idac_di_shifter != NULL) { if (idac_di_shifter != NULL) {
idac_di_jvs_read_shifter_pos(gear); idac_di_get_shifter_pos(gear);
} else { } else {
idac_di_jvs_read_shifter_virt(gear); idac_di_get_shifter_virt(gear);
} }
} }
static void idac_di_jvs_read_shifter_pos(uint8_t *out) static void idac_di_get_shifter_pos(uint8_t *out)
{ {
union idac_di_state state; union idac_di_state state;
uint8_t btn_no; uint8_t btn_no;
@ -457,7 +457,7 @@ static void idac_di_jvs_read_shifter_pos(uint8_t *out)
*out = gear; *out = gear;
} }
static void idac_di_jvs_read_shifter_virt(uint8_t *gear) static void idac_di_get_shifter_virt(uint8_t *gear)
{ {
union idac_di_state state; union idac_di_state state;
bool shift_dn; bool shift_dn;
@ -489,7 +489,7 @@ static void idac_di_jvs_read_shifter_virt(uint8_t *gear)
*gear = idac_shifter_current_gear(); *gear = idac_shifter_current_gear();
} }
static void idac_di_jvs_read_analogs(struct idac_io_analog_state *out) static void idac_di_get_analogs(struct idac_io_analog_state *out)
{ {
union idac_di_state state; union idac_di_state state;
const LONG *brake; const LONG *brake;

View File

@ -16,14 +16,13 @@
static struct idac_io_config idac_io_cfg; static struct idac_io_config idac_io_cfg;
static const struct idac_io_backend *idac_io_backend; static const struct idac_io_backend *idac_io_backend;
static bool idac_io_coin; static bool idac_io_coin;
static uint16_t idac_io_coins;
uint16_t idac_io_get_api_version(void) uint16_t idac_io_get_api_version(void)
{ {
return 0x0100; return 0x0100;
} }
HRESULT idac_io_jvs_init(void) HRESULT idac_io_init(void)
{ {
HINSTANCE inst; HINSTANCE inst;
HRESULT hr; HRESULT hr;
@ -47,20 +46,19 @@ HRESULT idac_io_jvs_init(void)
hr = idac_xi_init(&idac_io_cfg.xi, &idac_io_backend); hr = idac_xi_init(&idac_io_cfg.xi, &idac_io_backend);
} else { } else {
hr = E_INVALIDARG; hr = E_INVALIDARG;
dprintf("IDZ IO: Invalid IO mode \"%S\", use dinput or xinput\n", dprintf("IDAC IO: Invalid IO mode \"%S\", use dinput or xinput\n",
idac_io_cfg.mode); idac_io_cfg.mode);
} }
return hr; return hr;
} }
void idac_io_jvs_read_buttons(uint8_t *opbtn_out, uint8_t *gamebtn_out) void idac_io_get_opbtns(uint8_t *opbtn_out)
{ {
uint8_t opbtn; uint8_t opbtn;
assert(idac_io_backend != NULL); assert(idac_io_backend != NULL);
assert(opbtn_out != NULL); assert(opbtn_out != NULL);
assert(gamebtn_out != NULL);
opbtn = 0; opbtn = 0;
@ -72,27 +70,43 @@ void idac_io_jvs_read_buttons(uint8_t *opbtn_out, uint8_t *gamebtn_out)
opbtn |= IDAC_IO_OPBTN_SERVICE; opbtn |= IDAC_IO_OPBTN_SERVICE;
} }
*opbtn_out = opbtn; if (GetAsyncKeyState(idac_io_cfg.vk_coin) & 0x8000) {
if (!idac_io_coin) {
idac_io_coin = true;
opbtn |= IDAC_IO_OPBTN_COIN;
}
} else {
idac_io_coin = false;
}
idac_io_backend->jvs_read_buttons(gamebtn_out); *opbtn_out = opbtn;
} }
void idac_io_jvs_read_shifter(uint8_t *gear)
void idac_io_get_gamebtns(uint8_t *gamebtn_out)
{
assert(idac_io_backend != NULL);
assert(gamebtn_out != NULL);
idac_io_backend->get_gamebtns(gamebtn_out);
}
void idac_io_get_shifter(uint8_t *gear)
{ {
assert(gear != NULL); assert(gear != NULL);
assert(idac_io_backend != NULL); assert(idac_io_backend != NULL);
idac_io_backend->jvs_read_shifter(gear); idac_io_backend->get_shifter(gear);
} }
void idac_io_jvs_read_analogs(struct idac_io_analog_state *out) void idac_io_get_analogs(struct idac_io_analog_state *out)
{ {
struct idac_io_analog_state tmp; struct idac_io_analog_state tmp;
assert(out != NULL); assert(out != NULL);
assert(idac_io_backend != NULL); assert(idac_io_backend != NULL);
idac_io_backend->jvs_read_analogs(&tmp); idac_io_backend->get_analogs(&tmp);
/* Apply steering wheel restriction. Real cabs only report about 77% of /* 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 the IO-3's max ADC output value when the wheel is turned to either of
@ -104,22 +118,3 @@ void idac_io_jvs_read_analogs(struct idac_io_analog_state *out)
out->accel = tmp.accel; out->accel = tmp.accel;
out->brake = tmp.brake; out->brake = tmp.brake;
} }
void idac_io_jvs_read_coin_counter(uint16_t *out)
{
assert(out != NULL);
/* Coin counter is not backend-specific */
if (idac_io_cfg.vk_coin &&
(GetAsyncKeyState(idac_io_cfg.vk_coin) & 0x8000)) {
if (!idac_io_coin) {
idac_io_coin = true;
idac_io_coins++;
}
} else {
idac_io_coin = false;
}
*out = idac_io_coins;
}

9
idacio/idacio.def Normal file
View File

@ -0,0 +1,9 @@
LIBRARY idacio
EXPORTS
idac_io_init
idac_io_poll
idac_io_get_opbtns
idac_io_get_gamebtns
idac_io_get_shifter
idac_io_get_analogs

View File

@ -1,17 +1,5 @@
#pragma once #pragma once
/* INITIAL D THE ARCADE CUSTOM IO API
This API definition allows custom driver DLLs to be defined for the
emulation of Initial D The Arcade cabinets. To be honest, there is very
little reason to want to do this, since driving game controllers are a
mostly-standardized PC peripheral which can be adequately controlled by the
built-in DirectInput and XInput support in idzhook. However, previous
versions of Segatools broke this functionality out into a separate DLL just
like all of the other supported games, so in the interests of maintaining
backwards compatibility we provide the option to load custom IDZIO
implementations as well. */
#include <windows.h> #include <windows.h>
#include <stdint.h> #include <stdint.h>
@ -19,6 +7,7 @@
enum { enum {
IDAC_IO_OPBTN_TEST = 0x01, IDAC_IO_OPBTN_TEST = 0x01,
IDAC_IO_OPBTN_SERVICE = 0x02, IDAC_IO_OPBTN_SERVICE = 0x02,
IDAC_IO_OPBTN_COIN = 0x04,
}; };
enum { enum {
@ -49,7 +38,7 @@ struct idac_io_analog_state {
uint16_t brake; uint16_t brake;
}; };
/* Get the version of the IDZ IO API that this DLL supports. This /* 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 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 the major version and the low byte is the minor version (as defined by the
Semantic Versioning standard). Semantic Versioning standard).
@ -58,33 +47,48 @@ struct idac_io_analog_state {
uint16_t idac_io_get_api_version(void); uint16_t idac_io_get_api_version(void);
/* Initialize JVS-based input. This function will be called before any other /* Initialize the IO DLL. This is the second function that will be called on
idac_io_jvs_*() function calls. Errors returned from this function will your DLL, after mu3_io_get_api_version.
manifest as a disconnected JVS bus.
All subsequent calls may originate from arbitrary threads and some may All subsequent calls to this API may originate from arbitrary threads.
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0100 */ Minimum API version: 0x0100 */
HRESULT idac_io_jvs_init(void); HRESULT idac_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 idac_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 idac_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 idac_io_get_gamebtns(uint8_t *gamebtn);
/* Poll the current state of the cabinet's JVS analog inputs. See structure /* Poll the current state of the cabinet's JVS analog inputs. See structure
definition above for details. definition above for details.
Minimum API version: 0x0100 */ Minimum API version: 0x0100 */
void idac_io_jvs_read_analogs(struct idac_io_analog_state *out); void idac_io_get_analogs(struct idac_io_analog_state *out);
/* Poll the current state of the cabinet's JVS input buttons and return them
through the opbtn and gamebtn out parameters. See enum definitions at the
top of this file for a list of bit masks to be used with these out
parameters.
Minimum API version: 0x0100 */
void idac_io_jvs_read_buttons(uint8_t *opbtn, uint8_t *gamebtn);
/* Poll the current position of the six-speed shifter and return it via the /* Poll the current position of the six-speed shifter and return it via the
gear out parameter. Valid values are 0 for neutral and 1-6 for gears 1-6. gear out parameter. Valid values are 0 for neutral and 1-6 for gears 1-6.
@ -95,12 +99,4 @@ void idac_io_jvs_read_buttons(uint8_t *opbtn, uint8_t *gamebtn);
Minimum API version: 0x0100 */ Minimum API version: 0x0100 */
void idac_io_jvs_read_shifter(uint8_t *gear); void idac_io_get_shifter(uint8_t *gear);
/* Read the current state of the coin counter. This value should be incremented
for every coin detected by the coin acceptor mechanism. This count does not
need to persist beyond the lifetime of the process.
Minimum API version: 0x0100 */
void idac_io_jvs_read_coin_counter(uint16_t *total);

View File

@ -1,8 +0,0 @@
LIBRARY idacio
EXPORTS
idac_io_jvs_init
idac_io_jvs_read_analogs
idac_io_jvs_read_buttons
idac_io_jvs_read_coin_counter
idac_io_jvs_read_shifter

View File

@ -1,14 +1,14 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "idzio/shifter.h" #include "idacio/shifter.h"
static bool idac_shifter_shifting; static bool idac_shifter_shifting;
static uint8_t idac_shifter_gear; static uint8_t idac_shifter_gear;
void idac_shifter_reset(void) void idac_shifter_set(uint8_t gear)
{ {
idac_shifter_gear = 0; idac_shifter_gear = gear;
} }
void idac_shifter_update(bool shift_dn, bool shift_up) void idac_shifter_update(bool shift_dn, bool shift_up)

View File

@ -3,6 +3,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
void idac_shifter_reset(void); void idac_shifter_set(uint8_t gear);
void idac_shifter_update(bool shift_dn, bool shift_up); void idac_shifter_update(bool shift_dn, bool shift_up);
uint8_t idac_shifter_current_gear(void); uint8_t idac_shifter_current_gear(void);

View File

@ -31,13 +31,13 @@ HRESULT idac_io_wnd_create(HINSTANCE inst, HWND *out)
wcx.cbSize = sizeof(wcx); wcx.cbSize = sizeof(wcx);
wcx.lpfnWndProc = idac_io_wnd_proc; wcx.lpfnWndProc = idac_io_wnd_proc;
wcx.hInstance = inst; wcx.hInstance = inst;
wcx.lpszClassName = L"IDZIO"; wcx.lpszClassName = L"IDACIO";
atom = RegisterClassExW(&wcx); atom = RegisterClassExW(&wcx);
if (atom == 0) { if (atom == 0) {
hr = HRESULT_FROM_WIN32(GetLastError()); hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("IDZIO: RegisterClassExW failed: %08x\n", (int) hr); dprintf("IDACIO: RegisterClassExW failed: %08x\n", (int) hr);
goto fail; goto fail;
} }
@ -58,7 +58,7 @@ HRESULT idac_io_wnd_create(HINSTANCE inst, HWND *out)
if (hwnd == NULL) { if (hwnd == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError()); hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("IDZIO: CreateWindowExW failed: %08x\n", (int) hr); dprintf("IDACIO: CreateWindowExW failed: %08x\n", (int) hr);
goto fail; goto fail;
} }

View File

@ -13,16 +13,16 @@
#include "util/dprintf.h" #include "util/dprintf.h"
static void idac_xi_jvs_read_buttons(uint8_t *gamebtn_out); static void idac_xi_get_gamebtns(uint8_t *gamebtn_out);
static void idac_xi_jvs_read_shifter(uint8_t *gear); static void idac_xi_get_shifter(uint8_t *gear);
static void idac_xi_jvs_read_analogs(struct idac_io_analog_state *out); static void idac_xi_get_analogs(struct idac_io_analog_state *out);
static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg); static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg);
static const struct idac_io_backend idac_xi_backend = { static const struct idac_io_backend idac_xi_backend = {
.jvs_read_buttons = idac_xi_jvs_read_buttons, .get_gamebtns = idac_xi_get_gamebtns,
.jvs_read_shifter = idac_xi_jvs_read_shifter, .get_shifter = idac_xi_get_shifter,
.jvs_read_analogs = idac_xi_jvs_read_analogs, .get_analogs = idac_xi_get_analogs,
}; };
static bool idac_xi_single_stick_steering; static bool idac_xi_single_stick_steering;
@ -45,6 +45,11 @@ HRESULT idac_xi_init(const struct idac_xi_config *cfg, const struct idac_io_back
return S_OK; return S_OK;
} }
HRESULT idac_io_poll(void)
{
return S_OK;
}
static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg) static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg)
{ {
dprintf("XInput: --- Begin configuration ---\n"); dprintf("XInput: --- Begin configuration ---\n");
@ -56,7 +61,7 @@ static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg)
return S_OK; return S_OK;
} }
static void idac_xi_jvs_read_buttons(uint8_t *gamebtn_out) static void idac_xi_get_gamebtns(uint8_t *gamebtn_out)
{ {
uint8_t gamebtn; uint8_t gamebtn;
XINPUT_STATE xi; XINPUT_STATE xi;
@ -97,7 +102,7 @@ static void idac_xi_jvs_read_buttons(uint8_t *gamebtn_out)
*gamebtn_out = gamebtn; *gamebtn_out = gamebtn;
} }
static void idac_xi_jvs_read_shifter(uint8_t *gear) static void idac_xi_get_shifter(uint8_t *gear)
{ {
bool shift_dn; bool shift_dn;
bool shift_up; bool shift_up;
@ -112,9 +117,25 @@ static void idac_xi_jvs_read_shifter(uint8_t *gear)
if (xb & XINPUT_GAMEPAD_START) { if (xb & XINPUT_GAMEPAD_START) {
/* Reset to Neutral when start is pressed */ /* Reset to Neutral when start is pressed */
idac_shifter_reset(); idac_shifter_set(0);
} }
/*
// Alternative shifting mode
if (xb & XINPUT_GAMEPAD_X) {
// Set to Gear 2 when X is pressed
idac_shifter_set(2);
}
if (xb & XINPUT_GAMEPAD_Y) {
// Set to Gear 3 when Y is pressed
idac_shifter_set(3);
}
shift_dn = xb & XINPUT_GAMEPAD_LEFT_SHOULDER;
shift_up = xb & XINPUT_GAMEPAD_RIGHT_SHOULDER;
*/
shift_dn = xb & (XINPUT_GAMEPAD_Y | XINPUT_GAMEPAD_LEFT_SHOULDER); shift_dn = xb & (XINPUT_GAMEPAD_Y | XINPUT_GAMEPAD_LEFT_SHOULDER);
shift_up = xb & (XINPUT_GAMEPAD_X | XINPUT_GAMEPAD_RIGHT_SHOULDER); shift_up = xb & (XINPUT_GAMEPAD_X | XINPUT_GAMEPAD_RIGHT_SHOULDER);
@ -123,7 +144,7 @@ static void idac_xi_jvs_read_shifter(uint8_t *gear)
*gear = idac_shifter_current_gear(); *gear = idac_shifter_current_gear();
} }
static void idac_xi_jvs_read_analogs(struct idac_io_analog_state *out) static void idac_xi_get_analogs(struct idac_io_analog_state *out)
{ {
XINPUT_STATE xi; XINPUT_STATE xi;
int left; int left;
@ -154,7 +175,7 @@ static void idac_xi_jvs_read_analogs(struct idac_io_analog_state *out)
right = 0; right = 0;
} }
if(idac_xi_single_stick_steering) { if (idac_xi_single_stick_steering) {
out->wheel = left; out->wheel = left;
} else { } else {
out->wheel = (left + right) / 2; out->wheel = (left + right) / 2;