forked from Dniel97/segatools
Merge branch 'idac_test' into develop
This commit is contained in:
commit
89195ed60b
16
Package.mk
16
Package.mk
@ -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
136
dist/idac/segatools.ini
vendored
Normal 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
28
dist/idac/start.bat
vendored
Normal 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
|
@ -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
53
idachook/config.c
Normal file
@ -0,0 +1,53 @@
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "board/config.h"
|
||||
#include "board/sg-reader.h"
|
||||
|
||||
#include "hooklib/config.h"
|
||||
#include "hooklib/dvd.h"
|
||||
|
||||
#include "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
32
idachook/config.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "board/config.h"
|
||||
|
||||
#include "hooklib/dvd.h"
|
||||
|
||||
#include "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
105
idachook/dllmain.c
Normal 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
115
idachook/idac-dll.c
Normal 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
23
idachook/idac-dll.h
Normal 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
20
idachook/idachook.def
Normal 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
138
idachook/io4.c
Normal 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
7
idachook/io4.h
Normal 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
32
idachook/meson.build
Normal 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
186
idachook/zinput.c
Normal 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
11
idachook/zinput.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct zinput_config {
|
||||
bool enable;
|
||||
};
|
||||
|
||||
HRESULT zinput_hook_init(struct zinput_config *cfg);
|
12
idacio/backend.h
Normal file
12
idacio/backend.h
Normal 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
121
idacio/config.c
Normal 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
45
idacio/config.h
Normal 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
163
idacio/di-dev.c
Normal 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
19
idacio/di-dev.h
Normal 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
523
idacio/di.c
Normal 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
9
idacio/di.h
Normal 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
120
idacio/dllmain.c
Normal 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
9
idacio/idacio.def
Normal 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
102
idacio/idacio.h
Normal 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
32
idacio/meson.build
Normal 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
32
idacio/shifter.c
Normal 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
8
idacio/shifter.h
Normal 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
86
idacio/wnd.c
Normal file
@ -0,0 +1,86 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/dprintf.h"
|
||||
|
||||
/* DirectInput requires a window for correct initialization (and also force
|
||||
feedback), so this source file provides some utilities for creating a
|
||||
generic message-only window. */
|
||||
|
||||
static LRESULT WINAPI 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
5
idacio/wnd.h
Normal 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
186
idacio/xi.c
Normal 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
10
idacio/xi.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
/* Can't call this xinput.h or it will conflict with <xinput.h> */
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "idacio/backend.h"
|
||||
#include "idacio/config.h"
|
||||
|
||||
HRESULT idac_xi_init(const struct idac_xi_config *cfg, const struct idac_io_backend **backend);
|
@ -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')
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
153
platform/dipsw.c
Normal 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
15
platform/dipsw.h
Normal 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);
|
@ -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;
|
||||
}
|
||||
|
@ -32,5 +32,7 @@ platform_lib = static_library(
|
||||
'platform.h',
|
||||
'vfs.c',
|
||||
'vfs.h',
|
||||
'dipsw.c',
|
||||
'dipsw.h',
|
||||
],
|
||||
)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
456
segatools.md
Normal 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.
|
Loading…
Reference in New Issue
Block a user