Merge branch 'idac_test' into develop

This commit is contained in:
Dniel97 2023-07-14 00:43:38 +02:00
commit 89195ed60b
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
43 changed files with 3057 additions and 4 deletions

View File

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

136
dist/idac/segatools.ini vendored Normal file
View File

@ -0,0 +1,136 @@
[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.
; If you disable netenv then you must set this to your LAN's IP subnet, and
; that subnet must start with 192.168. Set it to your LAN's subnet if you
; want to play head-to-head using netenv=1.
subnet=192.168.100.0
; Override the keychip's region code. Most games seem to pay attention to the
; DS EEPROM region code and not the keychip region code, and this seems to be
; a bit mask that controls which Nu PCB region codes this keychip is authorized
; for. So it probably only affects the system software and not the game software.
; 1: JPN: Japan, 4: EXP: Export (for Asian markets)
region=4
[gpio]
; ALLS DIP switches.
; 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
; 0 is the DZero CVT cab and 1 is the SWDC CVT cab.
dipsw2=0
; Enable the Single Seat mode, always requires dipsw1=1.
dipsw3=0
; The next two dip switches are the seat settings in bits, where
; 00 = Seat 1, 10 = Seat 2, 01 = Seat 3 and 11 = Seat 4
dipsw4=0
dipsw5=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=
[idacio]
; 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.
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
; Automatically reset the simulated shifter to Neutral when XInput Start is
; pressed (e.g. when navigating menus between races).
autoNeutral=1
; 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=
; Name of the positional shifter to use (or any subset thereof).
; Leave blank if you do not have a positional shifter; a positional shifter
; will be simulated using the configured Shift Down and Shift Up buttons
; in this case.
;
; Can be the same device as the wheel.
;
; Example: T500
shifterName=
; 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 simulated six-speed shifter.
shiftDn=1
shiftUp=2
; Button mappings for the positional shifter, if present.
gear1=1
gear2=2
gear3=3
gear4=4
gear5=5
gear6=6
; Invert the accelerator and or brake axis
; (Needed when using DirectInput for the Dualshock 4 for example)
reverseAccelAxis=0
reverseBrakeAxis=0

28
dist/idac/start.bat vendored Normal file
View File

@ -0,0 +1,28 @@
@echo off
pushd %~dp0
REM set the APP_DIR to the Y drive
set APP_DIR=Y:\SDGT
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%\SDGT folder
start /min inject -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.exe -culture=en launch=Cabinet ABSLOG="..\..\..\..\..\Userdata\GameProject.log" -Master -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED
taskkill /f /im amdaemon.exe > nul 2>&1
REM unmount the APP_DIR
subst Y: /d > nul 2>&1
echo.
echo Game processes have terminated
pause

View File

@ -18,7 +18,7 @@ if ERRORLEVEL 1 (
goto failure
)
docker image rm -f %IMAGE_NAME%
:: docker image rm -f %IMAGE_NAME%
goto success

53
idachook/config.c Normal file
View File

@ -0,0 +1,53 @@
#include <assert.h>
#include <stddef.h>
#include "board/config.h"
#include "board/sg-reader.h"
#include "hooklib/config.h"
#include "hooklib/dvd.h"
#include "idachook/config.h"
#include "idachook/idac-dll.h"
#include "platform/config.h"
#include "platform/platform.h"
void idac_dll_config_load(
struct idac_dll_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"idacio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
}
void idac_hook_config_load(
struct idac_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);
idac_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
idachook/config.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include "board/config.h"
#include "hooklib/dvd.h"
#include "idachook/idac-dll.h"
#include "idachook/zinput.h"
#include "platform/platform.h"
struct idac_hook_config {
struct platform_config platform;
struct aime_config aime;
struct dvd_config dvd;
struct io4_config io4;
struct idac_dll_config dll;
struct zinput_config zinput;
};
void idac_dll_config_load(
struct idac_dll_config *cfg,
const wchar_t *filename);
void idac_hook_config_load(
struct idac_hook_config *cfg,
const wchar_t *filename);
void zinput_config_load(struct zinput_config *cfg, const wchar_t *filename);

105
idachook/dllmain.c Normal file
View File

@ -0,0 +1,105 @@
#include <windows.h>
#include <shlwapi.h>
#include <stdlib.h>
#include "board/sg-reader.h"
#include "board/io4.h"
#include "hook/process.h"
#include "hooklib/dvd.h"
#include "hooklib/serial.h"
#include "hooklib/spike.h"
#include "idachook/config.h"
#include "idachook/idac-dll.h"
#include "idachook/io4.h"
#include "idachook/zinput.h"
#include "platform/platform.h"
#include "util/dprintf.h"
static HMODULE idac_hook_mod;
static process_entry_t idac_startup;
static struct idac_hook_config idac_hook_cfg;
static DWORD CALLBACK idac_pre_startup(void)
{
HRESULT hr;
dprintf("--- Begin idac_pre_startup ---\n");
/* Config load */
idac_hook_config_load(&idac_hook_cfg, L".\\segatools.ini");
/* Hook Win32 APIs */
serial_hook_init();
zinput_hook_init(&idac_hook_cfg.zinput);
dvd_hook_init(&idac_hook_cfg.dvd, idac_hook_mod);
/* Initialize emulation hooks */
hr = platform_hook_init(
&idac_hook_cfg.platform,
"SDGT",
"ACA4",
idac_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = sg_reader_hook_init(&idac_hook_cfg.aime, 3, idac_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = idac_dll_init(&idac_hook_cfg.dll, idac_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = idac_io4_hook_init(&idac_hook_cfg.io4);
if (FAILED(hr)) {
goto fail;
}
/* Initialize debug helpers */
spike_hook_init(L".\\segatools.ini");
dprintf("--- End idac_pre_startup ---\n");
/* Jump to EXE start address */
return idac_startup();
fail:
ExitProcess(EXIT_FAILURE);
}
BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx)
{
HRESULT hr;
if (cause != DLL_PROCESS_ATTACH) {
return TRUE;
}
idac_hook_mod = mod;
hr = process_hijack_startup(idac_pre_startup, &idac_startup);
if (!SUCCEEDED(hr)) {
dprintf("Failed to hijack process startup: %x\n", (int) hr);
}
return SUCCEEDED(hr);
}

115
idachook/idac-dll.c Normal file
View File

@ -0,0 +1,115 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include "idachook/idac-dll.h"
#include "util/dll-bind.h"
#include "util/dprintf.h"
const struct dll_bind_sym idac_dll_syms[] = {
{
.sym = "idac_io_init",
.off = offsetof(struct idac_dll, init),
}, {
.sym = "idac_io_poll",
.off = offsetof(struct idac_dll, poll),
}, {
.sym = "idac_io_get_opbtns",
.off = offsetof(struct idac_dll, get_opbtns),
}, {
.sym = "idac_io_get_gamebtns",
.off = offsetof(struct idac_dll, get_gamebtns),
}, {
.sym = "idac_io_get_shifter",
.off = offsetof(struct idac_dll, get_shifter),
}, {
.sym = "idac_io_get_analogs",
.off = offsetof(struct idac_dll, get_analogs),
}
};
struct idac_dll idac_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 idac_dll_init(const struct idac_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("IDAC IO: Failed to load IO DLL: %lx: %S\n",
hr,
cfg->path);
goto end;
}
dprintf("IDAC IO: Using custom IO DLL: %S\n", cfg->path);
src = owned;
} else {
owned = NULL;
src = self;
}
get_api_version = (void *) GetProcAddress(src, "idac_io_get_api_version");
if (get_api_version != NULL) {
idac_dll.api_version = get_api_version();
} else {
idac_dll.api_version = 0x0100;
dprintf("Custom IO DLL does not expose idac_io_get_api_version, "
"assuming API version 1.0.\n"
"Please ask the developer to update their DLL.\n");
}
if (idac_dll.api_version >= 0x0200) {
hr = E_NOTIMPL;
dprintf("IDAC IO: Custom IO DLL implements an unsupported "
"API version (%#04x). Please update Segatools.\n",
idac_dll.api_version);
goto end;
}
sym = idac_dll_syms;
hr = dll_bind(&idac_dll, src, &sym, _countof(idac_dll_syms));
if (FAILED(hr)) {
if (src != self) {
dprintf("IDAC 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;
}

23
idachook/idac-dll.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <windows.h>
#include "idacio/idacio.h"
struct idac_dll {
uint16_t api_version;
HRESULT (*init)(void);
HRESULT (*poll)(void);
void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint8_t *gamebtn);
void (*get_shifter)(uint8_t *gear);
void (*get_analogs)(struct idac_io_analog_state *out);
};
struct idac_dll_config {
wchar_t path[MAX_PATH];
};
extern struct idac_dll idac_dll;
HRESULT idac_dll_init(const struct idac_dll_config *cfg, HINSTANCE self);

20
idachook/idachook.def Normal file
View File

@ -0,0 +1,20 @@
LIBRARY idachook
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
idac_io_get_api_version
idac_io_init
idac_io_poll
idac_io_get_opbtns
idac_io_get_gamebtns
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);

32
idachook/meson.build Normal file
View File

@ -0,0 +1,32 @@
shared_library(
'idachook',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
vs_module_defs : 'idachook.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,
idacio_lib,
platform_lib,
util_lib,
],
sources : [
'config.c',
'config.h',
'dllmain.c',
'idac-dll.c',
'idac-dll.h',
'io4.c',
'io4.h',
'zinput.c',
'zinput.h',
],
)

186
idachook/zinput.c Normal file
View File

@ -0,0 +1,186 @@
#include <windows.h>
#include <dinput.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "idachook/config.h"
#include "idachook/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
idachook/zinput.h Normal file
View File

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

12
idacio/backend.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <stdint.h>
#include "idacio/idacio.h"
struct idac_io_backend {
void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint8_t *gamebtn);
void (*get_shifter)(uint8_t *gear);
void (*get_analogs)(struct idac_io_analog_state *state);
};

121
idacio/config.c Normal file
View File

@ -0,0 +1,121 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include "idacio/config.h"
void idac_di_config_load(struct idac_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"shifterName",
L"",
cfg->shifter_name,
_countof(cfg->shifter_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->reverse_brake_axis = GetPrivateProfileIntW(
L"dinput",
L"reverseBrakeAxis",
0,
filename);
cfg->reverse_accel_axis = GetPrivateProfileIntW(
L"dinput",
L"reverseAccelAxis",
0,
filename);
for (i = 0 ; i < 6 ; i++) {
swprintf_s(key, _countof(key), L"gear%i", i + 1);
cfg->gear[i] = GetPrivateProfileIntW(L"dinput", key, i + 1, filename);
}
}
void idac_xi_config_load(struct idac_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 idac_io_config_load(struct idac_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);
idac_shifter_config_load(&cfg->shifter, filename);
idac_di_config_load(&cfg->di, filename);
idac_xi_config_load(&cfg->xi, filename);
}
void idac_shifter_config_load(
struct idac_shifter_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->auto_neutral = GetPrivateProfileIntW(
L"io4",
L"autoNeutral",
0,
filename);
}

45
idacio/config.h Normal file
View File

@ -0,0 +1,45 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct idac_shifter_config {
bool auto_neutral;
};
struct idac_di_config {
wchar_t device_name[64];
wchar_t shifter_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 gear[6];
bool reverse_brake_axis;
bool reverse_accel_axis;
};
struct idac_xi_config {
bool single_stick_steering;
};
struct idac_io_config {
uint8_t vk_test;
uint8_t vk_service;
uint8_t vk_coin;
wchar_t mode[8];
int restrict_;
struct idac_shifter_config shifter;
struct idac_di_config di;
struct idac_xi_config xi;
};
void idac_di_config_load(struct idac_di_config *cfg, const wchar_t *filename);
void idac_xi_config_load(struct idac_xi_config *cfg, const wchar_t *filename);
void idac_io_config_load(struct idac_io_config *cfg, const wchar_t *filename);
void idac_shifter_config_load(
struct idac_shifter_config *cfg,
const wchar_t *filename);

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

@ -0,0 +1,163 @@
#include <windows.h>
#include <dinput.h>
#include <assert.h>
#include "idacio/di-dev.h"
#include "util/dprintf.h"
HRESULT idac_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 idac_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 idac_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,
union idac_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
idacio/di-dev.h Normal file
View File

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

523
idacio/di.c Normal file
View File

@ -0,0 +1,523 @@
#include <windows.h>
#include <dinput.h>
#include <stddef.h>
#include <stdint.h>
#include <wchar.h>
#include "idacio/backend.h"
#include "idacio/config.h"
#include "idacio/di.h"
#include "idacio/di-dev.h"
#include "idacio/idacio.h"
#include "idacio/shifter.h"
#include "idacio/wnd.h"
#include "util/dprintf.h"
#include "util/str.h"
struct idac_di_axis {
wchar_t name[4];
size_t off;
};
static HRESULT idac_di_config_apply(const struct idac_di_config *cfg);
static const struct idac_di_axis *idac_di_get_axis(const wchar_t *name);
static BOOL CALLBACK idac_di_enum_callback(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static BOOL CALLBACK idac_di_enum_callback_shifter(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static void idac_di_get_buttons(uint8_t *gamebtn_out);
static uint8_t idac_di_decode_pov(DWORD pov);
static void idac_di_get_shifter(uint8_t *gear);
static void idac_di_get_shifter_pos(uint8_t *gear);
static void idac_di_get_shifter_virt(uint8_t *gear);
static void idac_di_get_analogs(struct idac_io_analog_state *out);
static const struct idac_di_axis idac_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 idac_io_backend idac_di_backend = {
.get_gamebtns = idac_di_get_buttons,
.get_shifter = idac_di_get_shifter,
.get_analogs = idac_di_get_analogs,
};
static HWND idac_di_wnd;
static IDirectInput8W *idac_di_api;
static IDirectInputDevice8W *idac_di_dev;
static IDirectInputDevice8W *idac_di_shifter;
static IDirectInputEffect *idac_di_fx;
static size_t idac_di_off_brake;
static size_t idac_di_off_accel;
static uint8_t idac_di_shift_dn;
static uint8_t idac_di_shift_up;
static uint8_t idac_di_view_chg;
static uint8_t idac_di_start;
static uint8_t idac_di_gear[6];
static bool idac_di_reverse_brake_axis;
static bool idac_di_reverse_accel_axis;
HRESULT idac_di_init(
const struct idac_di_config *cfg,
HINSTANCE inst,
const struct idac_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 = idac_di_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
hr = idac_io_wnd_create(inst, &idac_di_wnd);
if (FAILED(hr)) {
return hr;
}
/* Initial D Zero has some built-in DirectInput support that is not
particularly useful. idachook shorts this out by redirecting dinput8.dll
to a no-op implementation of DirectInput. However, idacio 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 **) &idac_di_api,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: API create failed: %08x\n", (int) hr);
return hr;
}
hr = IDirectInput8_EnumDevices(
idac_di_api,
DI8DEVCLASS_GAMECTRL,
idac_di_enum_callback,
(void *) cfg,
DIEDFL_ATTACHEDONLY);
if (FAILED(hr)) {
dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr);
return hr;
}
if (idac_di_dev == NULL) {
dprintf("Wheel: Controller not found\n");
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = idac_di_dev_start(idac_di_dev, idac_di_wnd);
if (FAILED(hr)) {
return hr;
}
idac_di_dev_start_fx(idac_di_dev, &idac_di_fx);
if (cfg->shifter_name[0] != L'\0') {
hr = IDirectInput8_EnumDevices(
idac_di_api,
DI8DEVCLASS_GAMECTRL,
idac_di_enum_callback_shifter,
(void *) cfg,
DIEDFL_ATTACHEDONLY);
if (FAILED(hr)) {
dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr);
return hr;
}
if (idac_di_dev == NULL) {
dprintf("Shifter: Controller not found\n");
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = idac_di_dev_start(idac_di_shifter, idac_di_wnd);
if (FAILED(hr)) {
return hr;
}
}
dprintf("DirectInput: Controller initialized\n");
*backend = &idac_di_backend;
return S_OK;
}
static HRESULT idac_di_config_apply(const struct idac_di_config *cfg)
{
const struct idac_di_axis *brake_axis;
const struct idac_di_axis *accel_axis;
int i;
brake_axis = idac_di_get_axis(cfg->brake_axis);
accel_axis = idac_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;
}
for (i = 0 ; i < 6 ; i++) {
if (cfg->gear[i] > 32) {
dprintf("Shifter: Invalid gear %i button: %i\n",
i + 1,
cfg->gear[i]);
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: 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");
if (cfg->shifter_name[0] != L'\0') {
dprintf("Shifter: --- Begin configuration ---\n");
dprintf("Shifter: Device name . . . : Contains \"%S\"\n",
cfg->shifter_name);
dprintf("Shifter: Gear buttons . . : %i %i %i %i %i %i\n",
cfg->gear[0],
cfg->gear[1],
cfg->gear[2],
cfg->gear[3],
cfg->gear[4],
cfg->gear[5]);
dprintf("Shifter: --- End configuration ---\n");
}
idac_di_off_brake = accel_axis->off;
idac_di_off_accel = brake_axis->off;
idac_di_start = cfg->start;
idac_di_view_chg = cfg->view_chg;
idac_di_shift_dn = cfg->shift_dn;
idac_di_shift_up = cfg->shift_up;
idac_di_reverse_brake_axis = cfg->reverse_brake_axis;
idac_di_reverse_accel_axis = cfg->reverse_accel_axis;
for (i = 0 ; i < 6 ; i++) {
idac_di_gear[i] = cfg->gear[i];
}
return S_OK;
}
static const struct idac_di_axis *idac_di_get_axis(const wchar_t *name)
{
const struct idac_di_axis *axis;
size_t i;
for (i = 0 ; i < _countof(idac_di_axes) ; i++) {
axis = &idac_di_axes[i];
if (wstr_ieq(name, axis->name)) {
return axis;
}
}
return NULL;
}
static BOOL CALLBACK idac_di_enum_callback(
const DIDEVICEINSTANCEW *dev,
void *ctx)
{
const struct idac_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(
idac_di_api,
&dev->guidInstance,
&idac_di_dev,
NULL);
if (FAILED(hr)) {
dprintf("Wheel: CreateDevice failed: %08x\n", (int) hr);
}
return DIENUM_STOP;
}
static BOOL CALLBACK idac_di_enum_callback_shifter(
const DIDEVICEINSTANCEW *dev,
void *ctx)
{
const struct idac_di_config *cfg;
HRESULT hr;
cfg = ctx;
if (wcsstr(dev->tszProductName, cfg->shifter_name) == NULL) {
return DIENUM_CONTINUE;
}
dprintf("Shifter: Using DirectInput device \"%S\"\n", dev->tszProductName);
hr = IDirectInput8_CreateDevice(
idac_di_api,
&dev->guidInstance,
&idac_di_shifter,
NULL);
if (FAILED(hr)) {
dprintf("Shifter: CreateDevice failed: %08x\n", (int) hr);
}
return DIENUM_STOP;
}
static void idac_di_get_buttons(uint8_t *gamebtn_out)
{
union idac_di_state state;
uint8_t gamebtn;
HRESULT hr;
assert(gamebtn_out != NULL);
hr = idac_di_dev_poll(idac_di_dev, idac_di_wnd, &state);
if (FAILED(hr)) {
return;
}
gamebtn = idac_di_decode_pov(state.st.rgdwPOV[0]);
if (idac_di_start && state.st.rgbButtons[idac_di_start - 1]) {
gamebtn |= IDAC_IO_GAMEBTN_START;
}
if (idac_di_view_chg && state.st.rgbButtons[idac_di_view_chg - 1]) {
gamebtn |= IDAC_IO_GAMEBTN_VIEW_CHANGE;
}
*gamebtn_out = gamebtn;
}
static uint8_t idac_di_decode_pov(DWORD pov)
{
switch (pov) {
case 0: return IDAC_IO_GAMEBTN_UP;
case 4500: return IDAC_IO_GAMEBTN_UP | IDAC_IO_GAMEBTN_RIGHT;
case 9000: return IDAC_IO_GAMEBTN_RIGHT;
case 13500: return IDAC_IO_GAMEBTN_RIGHT | IDAC_IO_GAMEBTN_DOWN;
case 18000: return IDAC_IO_GAMEBTN_DOWN;
case 22500: return IDAC_IO_GAMEBTN_DOWN | IDAC_IO_GAMEBTN_RIGHT;
case 27000: return IDAC_IO_GAMEBTN_LEFT;
case 31500: return IDAC_IO_GAMEBTN_LEFT | IDAC_IO_GAMEBTN_UP;
default: return 0;
}
}
static void idac_di_get_shifter(uint8_t *gear)
{
assert(gear != NULL);
if (idac_di_shifter != NULL) {
idac_di_get_shifter_pos(gear);
} else {
idac_di_get_shifter_virt(gear);
}
}
static void idac_di_get_shifter_pos(uint8_t *out)
{
union idac_di_state state;
uint8_t btn_no;
uint8_t gear;
uint8_t i;
HRESULT hr;
assert(out != NULL);
assert(idac_di_shifter != NULL);
hr = idac_di_dev_poll(idac_di_shifter, idac_di_wnd, &state);
if (FAILED(hr)) {
return;
}
gear = 0;
for (i = 0 ; i < 6 ; i++) {
btn_no = idac_di_gear[i];
if (btn_no && state.st.rgbButtons[btn_no - 1]) {
gear = i + 1;
}
}
*out = gear;
}
static void idac_di_get_shifter_virt(uint8_t *gear)
{
union idac_di_state state;
bool shift_dn;
bool shift_up;
HRESULT hr;
assert(gear != NULL);
hr = idac_di_dev_poll(idac_di_dev, idac_di_wnd, &state);
if (FAILED(hr)) {
return;
}
if (idac_di_shift_dn) {
shift_dn = state.st.rgbButtons[idac_di_shift_dn - 1];
} else {
shift_dn = false;
}
if (idac_di_shift_up) {
shift_up = state.st.rgbButtons[idac_di_shift_up - 1];
} else {
shift_up = false;
}
idac_shifter_update(shift_dn, shift_up);
*gear = idac_shifter_current_gear();
}
static void idac_di_get_analogs(struct idac_io_analog_state *out)
{
union idac_di_state state;
const LONG *brake;
const LONG *accel;
HRESULT hr;
assert(out != NULL);
hr = idac_di_dev_poll(idac_di_dev, idac_di_wnd, &state);
if (FAILED(hr)) {
return;
}
brake = (LONG *) &state.bytes[idac_di_off_brake];
accel = (LONG *) &state.bytes[idac_di_off_accel];
out->wheel = state.st.lX - 32768;
if (idac_di_reverse_brake_axis) {
out->brake = *brake;
} else {
out->brake = 65535 - *brake;
}
if (idac_di_reverse_accel_axis) {
out->accel = *accel;
} else {
out->accel = 65535 - *accel;
}
}

9
idacio/di.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include "idacio/backend.h"
#include "idacio/config.h"
HRESULT idac_di_init(
const struct idac_di_config *cfg,
HINSTANCE inst,
const struct idac_io_backend **backend);

120
idacio/dllmain.c Normal file
View File

@ -0,0 +1,120 @@
#include <windows.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "idacio/backend.h"
#include "idacio/config.h"
#include "idacio/di.h"
#include "idacio/idacio.h"
#include "idacio/xi.h"
#include "util/dprintf.h"
#include "util/str.h"
static struct idac_io_config idac_io_cfg;
static const struct idac_io_backend *idac_io_backend;
static bool idac_io_coin;
uint16_t idac_io_get_api_version(void)
{
return 0x0100;
}
HRESULT idac_io_init(void)
{
HINSTANCE inst;
HRESULT hr;
assert(idac_io_backend == NULL);
inst = GetModuleHandleW(NULL);
if (inst == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("GetModuleHandleW failed: %lx\n", hr);
return hr;
}
idac_io_config_load(&idac_io_cfg, L".\\segatools.ini");
if (wstr_ieq(idac_io_cfg.mode, L"dinput")) {
hr = idac_di_init(&idac_io_cfg.di, inst, &idac_io_backend);
} else if (wstr_ieq(idac_io_cfg.mode, L"xinput")) {
hr = idac_xi_init(&idac_io_cfg.xi, &idac_io_backend);
} else {
hr = E_INVALIDARG;
dprintf("IDAC IO: Invalid IO mode \"%S\", use dinput or xinput\n",
idac_io_cfg.mode);
}
return hr;
}
void idac_io_get_opbtns(uint8_t *opbtn_out)
{
uint8_t opbtn;
assert(idac_io_backend != NULL);
assert(opbtn_out != NULL);
opbtn = 0;
if (GetAsyncKeyState(idac_io_cfg.vk_test) & 0x8000) {
opbtn |= IDAC_IO_OPBTN_TEST;
}
if (GetAsyncKeyState(idac_io_cfg.vk_service) & 0x8000) {
opbtn |= IDAC_IO_OPBTN_SERVICE;
}
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;
}
*opbtn_out = opbtn;
}
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(idac_io_backend != NULL);
idac_io_backend->get_shifter(gear);
}
void idac_io_get_analogs(struct idac_io_analog_state *out)
{
struct idac_io_analog_state tmp;
assert(out != NULL);
assert(idac_io_backend != NULL);
idac_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 * idac_io_cfg.restrict_) / 128;
out->accel = tmp.accel;
out->brake = tmp.brake;
}

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

102
idacio/idacio.h Normal file
View File

@ -0,0 +1,102 @@
#pragma once
#include <windows.h>
#include <stdint.h>
enum {
IDAC_IO_OPBTN_TEST = 0x01,
IDAC_IO_OPBTN_SERVICE = 0x02,
IDAC_IO_OPBTN_COIN = 0x04,
};
enum {
IDAC_IO_GAMEBTN_UP = 0x01,
IDAC_IO_GAMEBTN_DOWN = 0x02,
IDAC_IO_GAMEBTN_LEFT = 0x04,
IDAC_IO_GAMEBTN_RIGHT = 0x08,
IDAC_IO_GAMEBTN_START = 0x10,
IDAC_IO_GAMEBTN_VIEW_CHANGE = 0x20,
};
struct idac_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 idac_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 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
definition above for details.
Minimum API version: 0x0100 */
void idac_io_get_analogs(struct idac_io_analog_state *out);
/* 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.
idzhook internally translates this gear position value into the correct
combination of Gear Left, Gear Right, Gear Up, Gear Down buttons that the
game will then interpret as the current position of the gear lever.
Minimum API version: 0x0100 */
void idac_io_get_shifter(uint8_t *gear);

32
idacio/meson.build Normal file
View File

@ -0,0 +1,32 @@
idacio_lib = static_library(
'idacio',
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',
'idacio.h',
'shifter.c',
'shifter.h',
'wnd.c',
'wnd.h',
'xi.c',
'xi.h',
],
)

32
idacio/shifter.c Normal file
View File

@ -0,0 +1,32 @@
#include <stdbool.h>
#include <stdint.h>
#include "idacio/shifter.h"
static bool idac_shifter_shifting;
static uint8_t idac_shifter_gear;
void idac_shifter_set(uint8_t gear)
{
idac_shifter_gear = gear;
}
void idac_shifter_update(bool shift_dn, bool shift_up)
{
if (!idac_shifter_shifting) {
if (shift_dn && idac_shifter_gear > 0) {
idac_shifter_gear--;
}
if (shift_up && idac_shifter_gear < 6) {
idac_shifter_gear++;
}
}
idac_shifter_shifting = shift_dn || shift_up;
}
uint8_t idac_shifter_current_gear(void)
{
return idac_shifter_gear;
}

8
idacio/shifter.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
void idac_shifter_set(uint8_t gear);
void idac_shifter_update(bool shift_dn, bool shift_up);
uint8_t idac_shifter_current_gear(void);

86
idacio/wnd.c Normal file
View File

@ -0,0 +1,86 @@
#include <windows.h>
#include <assert.h>
#include <string.h>
#include "util/dprintf.h"
/* DirectInput requires a window for correct initialization (and also force
feedback), so this source file provides some utilities for creating a
generic message-only window. */
static LRESULT WINAPI idac_io_wnd_proc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam);
HRESULT idac_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 = idac_io_wnd_proc;
wcx.hInstance = inst;
wcx.lpszClassName = L"IDACIO";
atom = RegisterClassExW(&wcx);
if (atom == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("IDACIO: 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("IDACIO: 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 idac_io_wnd_proc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg) {
default:
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
}

5
idacio/wnd.h Normal file
View File

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

186
idacio/xi.c Normal file
View File

@ -0,0 +1,186 @@
#include <windows.h>
#include <xinput.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "idacio/backend.h"
#include "idacio/config.h"
#include "idacio/idacio.h"
#include "idacio/shifter.h"
#include "idacio/xi.h"
#include "util/dprintf.h"
static void idac_xi_get_gamebtns(uint8_t *gamebtn_out);
static void idac_xi_get_shifter(uint8_t *gear);
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 const struct idac_io_backend idac_xi_backend = {
.get_gamebtns = idac_xi_get_gamebtns,
.get_shifter = idac_xi_get_shifter,
.get_analogs = idac_xi_get_analogs,
};
static bool idac_xi_single_stick_steering;
HRESULT idac_xi_init(const struct idac_xi_config *cfg, const struct idac_io_backend **backend)
{
HRESULT hr;
assert(cfg != NULL);
assert(backend != NULL);
hr = idac_xi_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
dprintf("XInput: Using XInput controller\n");
*backend = &idac_xi_backend;
return S_OK;
}
HRESULT idac_io_poll(void)
{
return S_OK;
}
static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg)
{
dprintf("XInput: --- Begin configuration ---\n");
dprintf("XInput: Single Stick Steering : %i\n", cfg->single_stick_steering);
dprintf("XInput: --- End configuration ---\n");
idac_xi_single_stick_steering = cfg->single_stick_steering;
return S_OK;
}
static void idac_xi_get_gamebtns(uint8_t *gamebtn_out)
{
uint8_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 |= IDAC_IO_GAMEBTN_UP;
}
if (xb & XINPUT_GAMEPAD_DPAD_DOWN) {
gamebtn |= IDAC_IO_GAMEBTN_DOWN;
}
if (xb & XINPUT_GAMEPAD_DPAD_LEFT) {
gamebtn |= IDAC_IO_GAMEBTN_LEFT;
}
if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) {
gamebtn |= IDAC_IO_GAMEBTN_RIGHT;
}
if (xb & (XINPUT_GAMEPAD_START | XINPUT_GAMEPAD_A)) {
gamebtn |= IDAC_IO_GAMEBTN_START;
}
if (xb & (XINPUT_GAMEPAD_BACK | XINPUT_GAMEPAD_B)) {
gamebtn |= IDAC_IO_GAMEBTN_VIEW_CHANGE;
}
*gamebtn_out = gamebtn;
}
static void idac_xi_get_shifter(uint8_t *gear)
{
bool shift_dn;
bool shift_up;
XINPUT_STATE xi;
WORD xb;
assert(gear != NULL);
memset(&xi, 0, sizeof(xi));
XInputGetState(0, &xi);
xb = xi.Gamepad.wButtons;
if (xb & XINPUT_GAMEPAD_START) {
/* Reset to Neutral when start is pressed */
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_up = xb & (XINPUT_GAMEPAD_X | XINPUT_GAMEPAD_RIGHT_SHOULDER);
idac_shifter_update(shift_dn, shift_up);
*gear = idac_shifter_current_gear();
}
static void idac_xi_get_analogs(struct idac_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 (idac_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
idacio/xi.h Normal file
View File

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

View File

@ -58,6 +58,7 @@ subdir('chuniio')
subdir('divaio')
subdir('carolio')
subdir('idzio')
subdir('idacio')
subdir('mu3io')
subdir('mercuryio')
subdir('cxbio')
@ -66,6 +67,7 @@ subdir('chunihook')
subdir('divahook')
subdir('carolhook')
subdir('idzhook')
subdir('idachook')
subdir('minihook')
subdir('mu3hook')
subdir('mercuryhook')

View File

@ -21,6 +21,7 @@
#include "platform/pcbid.h"
#include "platform/platform.h"
#include "platform/vfs.h"
#include "platform/dipsw.h"
void platform_config_load(struct platform_config *cfg, const wchar_t *filename)
{
@ -37,6 +38,7 @@ void platform_config_load(struct platform_config *cfg, const wchar_t *filename)
netenv_config_load(&cfg->netenv, filename);
nusec_config_load(&cfg->nusec, filename);
vfs_config_load(&cfg->vfs, filename);
dipsw_config_load(&cfg->dipsw, filename);
}
void amvideo_config_load(struct amvideo_config *cfg, const wchar_t *filename)
@ -317,3 +319,21 @@ void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename)
filename);
}
void dipsw_config_load(struct dipsw_config *cfg, const wchar_t *filename)
{
wchar_t name[7];
size_t i;
assert(cfg != NULL);
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"gpio", L"enable", 1, filename);
wcscpy_s(name, _countof(name), L"dipsw0");
for (i = 0 ; i < 8 ; i++) {
name[5] = L'1' + i;
cfg->dipsw[i] = GetPrivateProfileIntW(L"gpio", name, 0, filename);
}
}

View File

@ -17,6 +17,7 @@
#include "platform/pcbid.h"
#include "platform/platform.h"
#include "platform/vfs.h"
#include "platform/dipsw.h"
void platform_config_load(
struct platform_config *cfg,
@ -32,3 +33,4 @@ void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename);
void nusec_config_load(struct nusec_config *cfg, const wchar_t *filename);
void pcbid_config_load(struct pcbid_config *cfg, const wchar_t *filename);
void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename);
void dipsw_config_load(struct dipsw_config *cfg, const wchar_t *filename);

153
platform/dipsw.c Normal file
View File

@ -0,0 +1,153 @@
#include <windows.h>
#include <ntstatus.h>
#include <assert.h>
#include <string.h>
// #include <zlib.h>
#include "platform/dipsw.h"
#include "platform/vfs.h"
#include "util/dprintf.h"
#include "util/str.h"
#include "util/crc.h"
#define DATA_SIZE 503
#define BLOCK_SIZE (sizeof(uint32_t) + 4 + 1 + DATA_SIZE)
#pragma pack(push, 1)
typedef struct
{
uint32_t checksum;
char padding_1[4];
uint8_t dip_switches;
char data[DATA_SIZE];
} DipSwitchBlock;
typedef struct
{
DipSwitchBlock dip_switch_block;
char *data;
} DipSwitches;
#pragma pack(pop)
static DipSwitches dip_switches;
static struct dipsw_config dipsw_config;
static struct vfs_config vfs_config;
static void dipsw_read_sysfile(const wchar_t *sys_file);
static void dipsw_save_sysfile(const wchar_t *sys_file);
HRESULT dipsw_init(const struct dipsw_config *cfg, const struct vfs_config *vfs_cfg)
{
HRESULT hr;
wchar_t sys_file_path[MAX_PATH];
assert(cfg != NULL);
assert(vfs_cfg != NULL);
if (!cfg->enable)
{
return S_FALSE;
}
memcpy(&dipsw_config, cfg, sizeof(*cfg));
sys_file_path[0] = L'\0';
// concatenate vfs_config.amfs with L"sysfile.dat"
wcsncpy(sys_file_path, vfs_cfg->amfs, MAX_PATH);
wcsncat(sys_file_path, L"\\sysfile.dat", MAX_PATH);
dipsw_read_sysfile(sys_file_path);
// now write the dipsw_config.dipsw to the dip_switch_block
dipsw_save_sysfile(sys_file_path);
return S_OK;
}
static void dipsw_read_sysfile(const wchar_t *sys_file)
{
FILE *f = _wfopen(sys_file, L"r");
if (f == NULL)
{
dprintf("First run detected, DipSw settings can only be applied AFTER the first run\n");
return;
}
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
fseek(f, 0, SEEK_SET);
if (file_size != 0x6000)
{
dprintf("Invalid sysfile.dat file size\n");
fclose(f);
return;
}
dip_switches.data = malloc(file_size);
fread(dip_switches.data, 1, file_size, f);
fclose(f);
// memcpy(dip_switches.dip_switch_block, dip_switches.data + 0x2800, BLOCK_SIZE);
memcpy(&dip_switches.dip_switch_block, dip_switches.data + 0x2800, BLOCK_SIZE);
}
static void dipsw_save_sysfile(const wchar_t *sys_file)
{
uint8_t dipsw = 0;
// open the sysfile.dat for writing in bytes mode
FILE *f = _wfopen(sys_file, L"rb+");
if (f == NULL)
{
return;
}
// write the dipsw_config.dipsw to the dip_switch_block
for (int i = 0; i < 8; i++)
{
if (dipsw_config.dipsw[i])
{
// print which dipsw is enabled
dprintf("DipSw: DipSw%d=1 set\n", i + 1);
dipsw |= (1 << i);
}
}
dip_switches.dip_switch_block.dip_switches = dipsw;
// calculate the new checksum, skip the old crc32 value
// which is at the beginning of the block, thats's why the +4
// conver the struct to chars in order for the crc32 calculation to work
dip_switches.dip_switch_block.checksum = crc32(
(char *)&dip_switches.dip_switch_block + 4, BLOCK_SIZE - 4, 0);
// build the new dip switch block
char block[BLOCK_SIZE];
memcpy(block, (char *)&dip_switches.dip_switch_block, BLOCK_SIZE);
// replace the old block with the new one
memcpy(dip_switches.data + 0x2800, block, BLOCK_SIZE);
memcpy(dip_switches.data + 0x5800, block, BLOCK_SIZE);
// print the dip_switch_block in hex
/*
dprintf("DipSw Block: ");
for (size_t i = 0; i < BLOCK_SIZE; i++)
{
dprintf("%02X ", ((uint8_t *)&dip_switches.dip_switch_block)[i]);
}
dprintf("\n");
*/
fwrite(dip_switches.data, 1, 0x6000, f);
fclose(f);
}

15
platform/dipsw.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include "platform/vfs.h"
struct dipsw_config {
bool enable;
bool dipsw[8];
};
HRESULT dipsw_init(const struct dipsw_config *cfg, const struct vfs_config *vfs_cfg);

View File

@ -79,5 +79,13 @@ HRESULT dns_platform_hook_init(const struct dns_config *cfg)
return hr;
}
// Disable api/polling to the original servers
hr = dns_hook_push(L"amlog.sys-all.net", NULL);
if (FAILED(hr)) {
return hr;
}
return S_OK;
}

View File

@ -32,5 +32,7 @@ platform_lib = static_library(
'platform.h',
'vfs.c',
'vfs.h',
'dipsw.c',
'dipsw.h',
],
)

View File

@ -12,6 +12,7 @@
#include "platform/pcbid.h"
#include "platform/platform.h"
#include "platform/vfs.h"
#include "platform/dipsw.h"
HRESULT platform_hook_init(
const struct platform_config *cfg,
@ -80,5 +81,11 @@ HRESULT platform_hook_init(
return hr;
}
hr = dipsw_init(&cfg->dipsw, &cfg->vfs);
if (FAILED(hr)) {
return hr;
}
return S_OK;
}

View File

@ -12,6 +12,7 @@
#include "platform/nusec.h"
#include "platform/pcbid.h"
#include "platform/vfs.h"
#include "platform/dipsw.h"
struct platform_config {
struct amvideo_config amvideo;
@ -24,6 +25,7 @@ struct platform_config {
struct netenv_config netenv;
struct nusec_config nusec;
struct vfs_config vfs;
struct dipsw_config dipsw;
};
HRESULT platform_hook_init(

View File

@ -28,7 +28,9 @@ static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes);
static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes);
static wchar_t vfs_nthome_real[MAX_PATH];
static const wchar_t vfs_nthome[] = L"C:\\Documents and Settings\\AppUser";
// new home for ALLS
static const wchar_t vfs_nthome[] = L"C:\\Users\\AppUser";
// static const wchar_t vfs_nthome[] = L"C:\\Documents and Settings\\AppUser";
static const size_t vfs_nthome_len = _countof(vfs_nthome) - 1;
static const wchar_t vfs_option[] = L"C:\\Mount\\Option";
@ -273,8 +275,8 @@ static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count)
}
switch (src[0]) {
case L'D': // later AMDaemon versions default to D: for AMFS if it can't find it
case L'd':
// case L'D': // later AMDaemon versions default to D: for AMFS if it can't find it
// case L'd':
case L'e':
case L'E':
redir = vfs_config.amfs;

456
segatools.md Normal file
View File

@ -0,0 +1,456 @@
# Segatools common configuration settings
This file describes configuration settings for Segatools that are common to
all games.
Keyboard binding settings use
[Virtual-Key Codes](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes).
## `[aimeio]`
Controls the card reader driver.
### `path`
Specify a path for a third-party card reader driver DLL. Default is empty
(use built-in emulation based on text files and keyboard input).
In previous versions of Segatools this was accomplished by replacing the
AIMEIO.DLL file that came with Segatools. Segatools no longer ships with a
separate AIMEIO.DLL file (its functionality is now built into the various hook
DLLs).
## `[aime]`
Controls emulation of the Aime card reader assembly.
### `enable`
Default: `1`
Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime
reader (COM port number varies by game).
### `aimePath`
Default: `DEVICE\aime.txt`
Path to a text file containing a classic Aime IC card ID. **This does not
currently work**.
### `felicaPath`
Default: `DEVICE\felica.txt`
Path to a text file containing a FeliCa e-cash card IDm serial number.
### `felicaGen`
Default: `1`
Whether to generate a random FeliCa ID if the file at `felicaPath` does not
exist.
### `scan`
Default: `0x0D` (`VK_RETURN`)
Virtual-key code. If this button is **held** then the emulated IC card reader
emulates an IC card in its proximity. A variety of different IC cards can be
emulated; the exact choice of card that is emulated depends on the presence or
absence of the configured card ID files.
## `[amvideo]`
Controls the `amvideo.dll` stub built into Segatools. This is a DLL that is
normally present on the SEGA operating system image which is responsible for
changing screen resolution and orientation.
### `enable`
Default: `1`
Enable stub `amvideo.dll`. Disable to use a real `amvideo.dll` build. Note that
you must have the correct registry settings installed and you must use the
version of `amvideo.dll` that matches your GPU vendor (since these DLLs make
use of vendor-specific APIs).
## `[clock]`
Controls hooks for Windows time-of-day APIs.
### `timezone`
Default: `1`
Make the system time zone appear to be JST. SEGA games malfunction in strange
ways if the system time zone is not JST. There should not be any reason to
disable this hook other than possible implementation bugs, but the option is
provided if you need it.
### `timewarp`
Default: `0`
Experimental time-of-day warping hook that skips over the hardcoded server
maintenance period. Causes an incorrect in-game time-of-day to be reported.
Better solutions for this problem exist and this feature will probably be
removed soon.
### `writeable`
Default: `0`
Allow game to adjust system clock and time zone settings. This should normally
be left at `0`, but the option is provided if you need it.
## `[dns]`
Controls redirection of network server hostname lookups
### `default`
Default: `localhost`
Controls hostname of all of the common network services servers, unless
overriden by a specific setting below. Most users will only need to change this
setting. Also, loopback addresses are specifically checked for and rejected by
the games themselves; this needs to be a LAN or WAN IP (or a hostname that
resolves to one).
### `router`
Default: Empty string (i.e. use value from `default` setting)
Overrides the target of the `tenporouter.loc` and `bbrouter.loc` hostname
lookups.
### `startup`
Default: Empty string (i.e. use value from `default` setting)
Overrides the target of the `naominet.jp` host lookup.
### `billing`
Default: Empty string (i.e. use value from `default` setting)
Overrides the target of the `ib.naominet.jp` host lookup.
### `aimedb`
Default: Empty string (i.e. use value from `default` setting)
Overrides the target of the `aime.naominet.jp` host lookup.
## `[ds]`
Controls emulation of the "DS (Dallas Semiconductor) EEPROM" chip on the AMEX
PCIe board. This is a small (32 byte) EEPROM that contains serial number and
region code information. It is not normally written to outside of inital
factory provisioning of a Sega Nu.
### `enable`
Default: `1`
Enable DS EEPROM emulation. Disable to use the DS EEPROM chip on a real AMEX.
### `region`
Default: `1`
AMEX Board region code. This appears to be a bit mask?
- `1`: Japan
- `2`: USA? (Dead code, not used)
- `4`: Export
- `8`: China
### `serialNo`
Default `AAVE-01A99999999`
"MAIN ID" serial number. First three characters are hardware series:
- `AAV`: Nu-series
- `AAW`: NuSX-series
- `ACA`: ALLS-series
## `[eeprom]`
Controls emulation of the bulk EEPROM on the AMEX PCIe board. This chip stores
status and configuration information.
### `enable`
Default: `1`
Enable bulk EEPROM emulation. Disable to use the bulk EEPROM chip on a real
AMEX.
### `path`
Default: `DEVICE\eeprom.bin`
Path to the storage file for EEPROM emulation. This file is automatically
created and initialized with a suitable number of zero bytes if it does not
already exist.
## `[gpio]`
Configure emulation of the AMEX PCIe GPIO (General Purpose Input Output)
controller.
### `enable`
Default: `1`
Enable GPIO emulation. Disable to use the GPIO controller on a real AMEX.
### `sw1`
Default `0x70` (`VK_F1`)
Keyboard binding for Nu chassis SW1 button (alternative Test)
### `sw2`
Default `0x71` (`VK_F2`)
Keyboard binding for Nu chassis SW2 button (alternative Service)
### `dipsw1` .. `dipsw8`
Defaults: `1`, `0`, `0`, `0`, `0`, `0`, `0`, `0`
Nu chassis DIP switch settings:
- Switch 1: Game-specific, but usually controls the "distribution server"
setting. Exactly one arcade machine on a cabinet router must be set to the
Server setting.
- `0`: Client
- `1`: Server
- Switch 2,3: Game-specific.
- Used by Mario&Sonic to configure cabinet ID, possibly other games.
- Switch 4: Screen orientation. Only used by the Nu system startup program.
- `0`: YOKO/Horizontal
- `1`: TATE/Vertical
- Switch 5,6,7: Screen resolution. Only used by the Nu system startup program.
- `000`: No change
- `100`: 640x480
- `010`: 1024x600
- `110`: 1024x768
- `001`: 1280x720
- `101`: 1280x1024
- `110`: 1360x768
- `111`: 1920x1080
- Switch 8: Game-specific. Not used in any shipping game.
## `[hwmon]`
Configure stub implementation of the platform hardware monitor driver. The
real implementation of this driver monitors CPU temperatures by reading from
Intel Model Specific Registers, which is an action that is only permitted from
kernel mode.
### `enable`
Default `1`
Enable hwmon emulation. Disable to use the real hwmon driver.
## `[jvs]`
Configure emulation of the AMEX PCIe JVS *controller* (not IO board!)
### `enable`
Default `1`
Enable JVS port emulation. Disable to use the JVS port on a real AMEX.
## `[keychip]`
Configure keychip emulation.
### `enable`
Enable keychip emulation. Disable to use a real keychip.
### `id`
Default: `A69E-01A88888888`
Keychip serial number. Keychip serials observed in the wild follow this
pattern: `A6xE-01Ayyyyyyyy`.
### `gameId`
Default: (Varies depending on game)
Override the game's four-character model code. Changing this from the game's
expected value will probably just cause a system error.
### `platformId`
Default: (Varies depending on game)
Override the game's four-character platform code (e.g. `AAV2` for Nu 2). This
is actually supposed to be a separate three-character `platformId` and
integer `modelType` setting, but they are combined here for convenience. Valid
values include:
- `AAV0`: Nu 1 (Project DIVA)
- `AAV1`: Nu 1.1 (Chunithm)
- `AAV2`: Nu 2 (Initial D Zero)
- `AAW0`: NuSX 1
- `AAW1`: NuSX 1.1
- `ACA0`: ALLS UX
- `ACA1`: ALLS HX
- `ACA2`: ALLS UX (without dedicated GPU)
- `ACA4`: ALLS MX
### `region`
Default: `1`
Override the keychip's region code. Most games seem to pay attention to the
DS EEPROM region code and not the keychip region code, and this seems to be
a bit mask that controls which Nu PCB region codes this keychip is authorized
for. So it probably only affects the system software and not the game software.
Bit values are:
- 1: JPN: Japan
- 2: USA (unused)
- 3: EXP: Export (for Asian markets)
- 4: CHS: China (Simplified Chinese?)
### `systemFlag`
Default: `0x64`
An 8-bit bitfield of unclear meaning. The least significant bit indicates a
developer dongle, I think? Changing this doesn't seem to have any effect on
anything other than Project DIVA.
Other values observed in the wild:
- `0x04`: SDCH, SDCA
- `0x20`: SDCA
### `subnet`
Default `192.168.100.0`
The LAN IP range that the game will expect. The prefix length is hardcoded into
the game program: for some games this is `/24`, for others it is `/20`.
## `[netenv]`
Configure network environment virtualization. This module helps bypass various
restrictions placed upon the game's LAN environment.
### `enable`
Default `1`
Enable network environment virtualization. You may need to disable this if
you want to do any head-to-head play on your LAN.
Note: The virtualized LAN IP range is taken from the emulated keychip's
`subnet` setting.
### `addrSuffix`
Default: `11`
The final octet of the local host's IP address on the virtualized subnet (so,
if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the
local host's virtualized LAN IP is `192.168.32.11`).
### `routerSuffix`
Default: `1`
The final octet of the default gateway's IP address on the virtualized subnet.
### `macAddr`
Default: `01:02:03:04:05:06`
The MAC address of the virtualized Ethernet adapter. The exact value shouldn't
ever matter.
## `[pcbid]`
Configure Windows host name virtualization. The ALLS-series platform no longer
has an AMEX board, so the MAIN ID serial number is stored in the Windows
hostname.
### `enable`
Default: `1`
Enable Windows host name virtualization. This is only needed for ALLS-platform
games (since the ALLS lacks an AMEX and therefore has no DS EEPROM, so it needs
another way to store the PCB serial), but it does no harm on games that run on
earlier hardware.
### `serialNo`
Default: `ACAE01A99999999`
Set the Windows host name. This should be an ALLS MAIN ID, without the
hyphen (which is not a valid character in a Windows host name).
## `[sram]`
Configure emulation of the AMEX PCIe battery-backed SRAM. This stores
bookkeeping state and settings. This file is automatically created and
initialized with a suitable number of zero bytes if it does not already exist.
### `enable`
Default `1`
Enable SRAM emulation. Disable to use the SRAM on a real AMEX.
### `path`
Default `DEVICE\sram.bin`
Path to the storage file for SRAM emulation.
## `[vfs]`
Configure Windows path redirection hooks.
### `enable`
Default: `1`
Enable path redirection.
### `amfs`
Default: Empty string (causes a startup error)
Configure the location of the SEGA AMFS volume. Stored on the `E` partition on
real hardware.
### `appdata`
Default: Empty string (causes a startup error)
Configure the location of the SEGA "APPDATA" volume (nothing to do with the
Windows user's `%APPDATA%` directory). Stored on the `Y` partition on real
hardware.
### `option`
Default: Empty string
Configure the location of the "Option" data mount point. This mount point is
optional (hence the name, probably) and contains directories which contain
minor over-the-air content updates.