Compare commits

..

15 Commits

Author SHA1 Message Date
63320f8456 Merge pull request 'fix chunihook' (#2) from CrazyRedMachine/segatools:chuniio into chuniio
Reviewed-on: Dniel97/segatools#2
2023-12-27 15:14:07 +00:00
926493290b fix chunihook 2023-12-26 06:47:19 -05:00
f4a3a5f78d optimized meson.build to decrease filesize 2023-12-25 15:55:43 +01:00
f5f275c8e9 chuniio: fixed individual IR notes 2023-12-25 15:12:25 +01:00
b38dea23ac chu2to3: fixed docker compilation, bugfixes 2023-12-25 15:03:44 +01:00
16bbd87c73 Merge pull request 'Include chu2to3 engine' (#1) from CrazyRedMachine/segatools:chuniio into chuniio
Reviewed-on: Dniel97/segatools#1
2023-12-25 14:02:33 +00:00
ee414d122b Include chu2to3 engine 2023-12-24 08:33:30 -05:00
d4372fa5c2 chuniio: use HRESULT instead of int for chuni_io_led_init() 2023-12-21 00:45:41 +01:00
ac9b889d71 chusan/chuni: tower LEDs added 2023-12-19 14:40:02 +01:00
3bf223c04e chusan: fixed SP/CVT switching 2023-12-19 13:45:06 +01:00
8ebdf67d6e chuni/chusan: added LED output to DLLs (will break most DLLs)
Credits: somewhatlurker, skogaby
https://dev.s-ul.net/skogaby/segatools/-/blob/ongeki-15093/
2023-12-19 12:48:33 +01:00
ed042176d7 chusan: ledport changed to COM5, fixes VFD 2023-12-17 20:17:26 +01:00
ad154a83e5 chusan: fixed dipsw settings, added aimeio DLL loading 2023-12-17 19:43:00 +01:00
1cbc33d97d chuasn: fixed coin insert for custom chuniio 2023-12-17 09:26:30 +01:00
72db08ac93 idz, idac, swdc: added FFB centering spring feature 2023-12-11 19:18:25 +01:00
58 changed files with 1542 additions and 218 deletions

View File

@ -18,6 +18,7 @@ struct aime_dll {
struct aime_dll_config {
wchar_t path[MAX_PATH];
bool path64;
};
extern struct aime_dll aime_dll;

View File

@ -9,18 +9,59 @@
#include "board/config.h"
#include "board/sg-reader.h"
#include "util/dprintf.h"
// Check windows
#if _WIN32 || _WIN64
#if _WIN64
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
// Check GCC
#if __GNUC__
#if __x86_64__ || __ppc64__
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
static void aime_dll_config_load(struct aime_dll_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
// Workaround for x64/x86 external IO dlls
// path32 for 32bit, path64 for 64bit
// for else.. is that possible? idk
if (cfg->path64) {
#if defined(ENV32BIT)
// Always empty, due to amdaemon being 64 bit in 32 bit mode
memset(cfg->path, 0, sizeof(cfg->path));
#elif defined(ENV64BIT)
GetPrivateProfileStringW(
L"aimeio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
#else
#error "Unknown environment"
#endif
} else {
GetPrivateProfileStringW(
L"aimeio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
}
}
void aime_config_load(struct aime_config *cfg, const wchar_t *filename)

View File

@ -103,11 +103,16 @@ static uint16_t led15093_fw_sum;
static uint8_t led15093_board_adr = 1;
static uint8_t led15093_host_adr = 1;
HRESULT led15093_hook_init(const struct led15093_config *cfg, unsigned int first_port,
unsigned int num_boards, uint8_t board_adr, uint8_t host_adr)
static io_led_init_t led_init;
static io_led_set_leds_t set_leds;
HRESULT led15093_hook_init(const struct led15093_config *cfg, io_led_init_t _led_init,
io_led_set_leds_t _set_leds, unsigned int first_port, unsigned int num_boards, uint8_t board_adr, uint8_t host_adr)
{
assert(cfg != NULL);
assert(_led_init != NULL);
assert(_set_leds != NULL);
if (!cfg->enable) {
return S_FALSE;
@ -117,6 +122,8 @@ HRESULT led15093_hook_init(const struct led15093_config *cfg, unsigned int first
first_port = cfg->port_no;
}
led_init = _led_init;
set_leds = _set_leds;
led15093_board_adr = board_adr;
led15093_host_adr = host_adr;
@ -207,9 +214,9 @@ static HRESULT led15093_handle_irp_locked(int board, struct irp *irp)
if (!v->started) {
dprintf("LED 15093: Starting LED backend\n");
// hr = fgo_dll.led_init();
hr = S_OK;
hr = led_init();
// hr = S_OK;
v->started = true;
v->start_hr = hr;
@ -230,6 +237,29 @@ static HRESULT led15093_handle_irp_locked(int board, struct irp *irp)
}
*/
if (irp->op == IRP_OP_OPEN) {
dprintf("LED 15093: Starting backend DLL\n");
// int res = led_init();
hr = led_init();
/*
if (res != 0) {
dprintf("LED 15093: Backend error, LED board disconnected: "
"%d\n",
res);
return E_FAIL;
}
*/
if (FAILED(hr)) {
dprintf("LED 15093: Backend error, LED board disconnected: "
"%x\n",
(int) hr);
return hr;
}
}
hr = uart_handle_irp(boarduart, irp);
if (FAILED(hr) || irp->op != IRP_OP_WRITE) {
@ -657,8 +687,20 @@ static HRESULT led15093_req_set_imm_led(int board, const struct led15093_req_set
return E_INVALIDARG;
}
memcpy(v->led, req->data, req->hdr.nbytes - 1);
/*
if (board == 0) {
dprintf("board %d: red: %d, green: %d, blue: %d\n", board, req->data[0x96], req->data[0x97], req->data[0x98]);
}
else if (board == 1)
{
dprintf("board %d: red: %d, green: %d, blue: %d\n", board, req->data[0xb4], req->data[0xb5], req->data[0xb6]);
}
*/
// Return the current LED data, remove const qualifier
set_leds(board, (uint8_t *) req->data);
memcpy(v->led, req->data, req->hdr.nbytes - 1);
// fgo_dll.led_gr_set_imm((const uint8_t*)&v->led);
if (!v->enable_response)

View File

@ -16,6 +16,9 @@ struct led15093_config {
uint16_t fw_sum;
};
HRESULT led15093_hook_init(const struct led15093_config *cfg, unsigned int first_port,
unsigned int num_boards, uint8_t board_adr, uint8_t host_adr);
typedef HRESULT (*io_led_init_t)(void);
typedef void (*io_led_set_leds_t)(uint8_t board, uint8_t *rgb);
HRESULT led15093_hook_init(const struct led15093_config *cfg, io_led_init_t _led_init,
io_led_set_leds_t _set_leds, unsigned int first_port, unsigned int num_boards, uint8_t board_adr, uint8_t host_adr);

View File

@ -30,9 +30,28 @@ const struct dll_bind_sym chuni_dll_syms[] = {
}, {
.sym = "chuni_io_slider_set_leds",
.off = offsetof(struct chuni_dll, slider_set_leds),
}, {
.sym = "chuni_io_led_init",
.off = offsetof(struct chuni_dll, led_init),
}, {
.sym = "chuni_io_led_set_colors",
.off = offsetof(struct chuni_dll, led_set_leds),
}
};
/* Helper function to determine upon dll_bind failure whether the required functions were found
NOTE: relies on symbols order declared above */
static HRESULT has_enough_symbols(uint16_t version, uint8_t count)
{
if ( version <= 0x0101 && count == 7 )
return S_OK;
if ( version >= 0x0102 && count == 9 )
return S_OK;
return E_FAIL;
}
struct chuni_dll chuni_dll;
// Copypasta DLL binding and diagnostic message boilerplate.
@ -92,16 +111,24 @@ HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self)
}
sym = chuni_dll_syms;
const struct dll_bind_sym *init_sym = &sym[0];
hr = dll_bind(&chuni_dll, src, &sym, _countof(chuni_dll_syms));
if (FAILED(hr)) {
if (src != self) {
dprintf("Chunithm IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
// Might still be ok depending on external dll API version
int bind_count = sym - init_sym;
if ( has_enough_symbols(chuni_dll.api_version, bind_count) == S_OK )
{
hr = S_OK;
} else {
dprintf("Chunithm IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
goto end;
goto end;
}
} else {
dprintf("Internal error: could not reflect \"%s\"\n", sym->sym);
}

View File

@ -13,6 +13,8 @@ struct chuni_dll {
void (*slider_start)(chuni_io_slider_callback_t callback);
void (*slider_stop)(void);
void (*slider_set_leds)(const uint8_t *rgb);
HRESULT (*led_init)(void);
void (*led_set_leds)(uint8_t board, uint8_t *rgb);
};
struct chuni_dll_config {

View File

@ -20,3 +20,5 @@ EXPORTS
chuni_io_slider_set_leds
chuni_io_slider_start
chuni_io_slider_stop
chuni_io_led_init
chuni_io_led_set_colors

View File

@ -4,6 +4,7 @@
#include "amex/amex.h"
#include "board/led15093.h"
#include "board/sg-reader.h"
#include "chunihook/config.h"
@ -96,10 +97,16 @@ static DWORD CALLBACK chuni_pre_startup(void)
goto fail;
}
hr = led15093_hook_init(&chuni_hook_cfg.led15093, 10, 2, 2, 1);
if ( chuni_dll.led_init == NULL || chuni_dll.led_set_leds == NULL )
{
dprintf("IO DLL doesn't support led_init/led_set_leds, cannot start LED15093 hook\n");
} else {
hr = led15093_hook_init(&chuni_hook_cfg.led15093,
chuni_dll.led_init, chuni_dll.led_set_leds, 10, 2, 2, 1);
if (FAILED(hr)) {
goto fail;
if (FAILED(hr)) {
goto fail;
}
}
hr = sg_reader_hook_init(&chuni_hook_cfg.aime, 12, 1, chuni_hook_mod);

358
chuniio/chu2to3.c Normal file
View File

@ -0,0 +1,358 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "chuniio/chu2to3.h"
#include "util/dprintf.h"
// Check windows
#if _WIN32 || _WIN64
#if _WIN64
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
// Check GCC
#if __GNUC__
#if __x86_64__ || __ppc64__
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
/* chuniio.dll dynamic loading */
HMODULE hinstLib;
typedef uint16_t (*chuni_io_get_api_version_t)(void);
typedef HRESULT (*chuni_io_jvs_init_t)(void);
typedef void (*chuni_io_jvs_poll_t)(uint8_t*, uint8_t*);
typedef void (*chuni_io_jvs_read_coin_counter_t)(uint16_t *);
typedef HRESULT (*chuni_io_slider_init_t)(void);
typedef void (*chuni_io_slider_set_leds_t)(const uint8_t *);
typedef void (*chuni_io_slider_start_t)(chuni_io_slider_callback_t);
typedef void (*chuni_io_slider_stop_t)(void);
typedef HRESULT (*chuni_io_led_init_t)(void);
typedef void (*chuni_io_led_set_colors_t)(uint8_t, uint8_t *);
chuni_io_get_api_version_t _chuni_io_get_api_version;
chuni_io_jvs_init_t _chuni_io_jvs_init;
chuni_io_jvs_poll_t _chuni_io_jvs_poll;
chuni_io_jvs_read_coin_counter_t _chuni_io_jvs_read_coin_counter;
chuni_io_slider_init_t _chuni_io_slider_init;
chuni_io_slider_set_leds_t _chuni_io_slider_set_leds;
chuni_io_slider_start_t _chuni_io_slider_start;
chuni_io_slider_stop_t _chuni_io_slider_stop;
chuni_io_led_init_t _chuni_io_led_init;
chuni_io_led_set_colors_t _chuni_io_led_set_colors;
/* SHMEM Handling */
#define BUF_SIZE 1024
#define SHMEM_WRITE(buf, size) CopyMemory((PVOID)g_pBuf, buf, size)
#define SHMEM_READ(buf, size) CopyMemory(buf,(PVOID)g_pBuf, size)
TCHAR g_shmem_name[]=TEXT("Local\\Chu2to3Shmem");
HANDLE g_hMapFile;
LPVOID g_pBuf;
#pragma pack(1)
typedef struct shared_data_s {
uint16_t coin_counter;
uint8_t opbtn;
uint8_t beams;
uint16_t version;
} shared_data_t;
shared_data_t g_shared_data;
bool shmem_create()
{
g_hMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // maximum object size (high-order DWORD)
BUF_SIZE, // maximum object size (low-order DWORD)
g_shmem_name); // name of mapping object
if (g_hMapFile == NULL)
{
dprintf("shmem_create : Could not create file mapping object (%ld).\n",
GetLastError());
return 0;
}
g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
BUF_SIZE);
if (g_pBuf == NULL)
{
dprintf("shmem_create : Could not map view of file (%ld).\n",
GetLastError());
CloseHandle(g_hMapFile);
return 0;
}
return 1;
}
bool shmem_load()
{
g_hMapFile = OpenFileMapping(
FILE_MAP_ALL_ACCESS, // read/write access
FALSE, // do not inherit the name
g_shmem_name); // name of mapping object
if (g_hMapFile == NULL)
{
dprintf("shmem_load : Could not open file mapping object (%ld).\n", GetLastError());
return 0;
}
g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
BUF_SIZE);
if (g_pBuf == NULL)
{
dprintf("shmem_load : Could not map view of file (%ld).\n", GetLastError());
CloseHandle(g_hMapFile);
return 0;
}
dprintf("shmem_load : shmem loaded succesfully.\n");
return 1;
}
void shmem_free()
{
UnmapViewOfFile(g_pBuf);
CloseHandle(g_hMapFile);
}
/* jvs polling thread (to forward info to x64 dll) */
static HANDLE jvs_poll_thread;
static bool jvs_poll_stop_flag;
static unsigned int __stdcall jvs_poll_thread_proc(void *ctx)
{
while (1) {
_chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter);
g_shared_data.opbtn = 0;
g_shared_data.beams = 0;
_chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams);
SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t));
Sleep(1);
}
return 0;
}
uint16_t chu2to3_load_dll(const wchar_t *dllname)
{
#if defined(ENV64BIT)
/* x64 must just open the shmem and do nothing else */
int errcount = 0;
while (!shmem_load())
{
if (errcount >= 10)
return -1;
Sleep(5000);
errcount++;
}
Sleep(1000);
return S_OK;
#endif
/* this is the first function called so let's setup the chuniio forwarding */
hinstLib = LoadLibraryW(dllname);
if (hinstLib == NULL) {
dprintf("ERROR: unable to load %S (error %ld)\n",dllname, GetLastError());
return -1;
}
_chuni_io_get_api_version = (chuni_io_get_api_version_t)GetProcAddress(hinstLib, "chuni_io_get_api_version");
_chuni_io_jvs_init = (chuni_io_jvs_init_t)GetProcAddress(hinstLib, "chuni_io_jvs_init");
_chuni_io_jvs_poll = (chuni_io_jvs_poll_t)GetProcAddress(hinstLib, "chuni_io_jvs_poll");
_chuni_io_jvs_read_coin_counter = (chuni_io_jvs_read_coin_counter_t)GetProcAddress(hinstLib, "chuni_io_jvs_read_coin_counter");
_chuni_io_slider_init = (chuni_io_slider_init_t)GetProcAddress(hinstLib, "chuni_io_slider_init");
_chuni_io_slider_set_leds = (chuni_io_slider_set_leds_t)GetProcAddress(hinstLib, "chuni_io_slider_set_leds");
_chuni_io_slider_start = (chuni_io_slider_start_t)GetProcAddress(hinstLib, "chuni_io_slider_start");
_chuni_io_slider_stop = (chuni_io_slider_stop_t)GetProcAddress(hinstLib, "chuni_io_slider_stop");
_chuni_io_led_init = (chuni_io_led_init_t)GetProcAddress(hinstLib, "chuni_io_led_init");
_chuni_io_led_set_colors = (chuni_io_led_set_colors_t)GetProcAddress(hinstLib, "chuni_io_led_set_colors");
/* x86 has to create the shmem */
if (!shmem_create())
{
return -1;
}
return 0;
}
/* chuniio exports */
uint16_t chu2to3_io_get_api_version(void)
{
#if defined(ENV64BIT)
/* This might be called too soon so let's make sure x86 has time to write to the shmem */
SHMEM_READ(&g_shared_data, sizeof(shared_data_t));
int errcount = 0;
while (g_shared_data.version == 0)
{
if (errcount >= 3)
{
dprintf("CHU2TO3 X64: Couldn't retrieve api version from shmem, assuming 0x0100\n");
return 0x0100;
}
Sleep(5000);
errcount++;
SHMEM_READ(&g_shared_data, sizeof(shared_data_t));
}
dprintf("CHU2TO3 X64: api version is %04X\n", g_shared_data.version);
return g_shared_data.version;
#endif
if ( _chuni_io_get_api_version == NULL )
{
g_shared_data.version = 0x0100;
}
else
{
g_shared_data.version = _chuni_io_get_api_version();
}
dprintf("CHU2TO3: api version is %04X\n", g_shared_data.version);
SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t));
return g_shared_data.version;
}
HRESULT chu2to3_io_jvs_init(void)
{
#if defined(ENV64BIT)
/* x86 only */
return S_OK;
#endif
_chuni_io_jvs_init();
/* start jvs poll thread now that jvs_init is done */
if (jvs_poll_thread != NULL) {
return S_OK;
}
jvs_poll_thread = (HANDLE) _beginthreadex(NULL,
0,
jvs_poll_thread_proc,
NULL,
0,
NULL);
return S_OK;
}
void chu2to3_io_jvs_read_coin_counter(uint16_t *out)
{
#if defined(ENV32BIT)
/* x86 can perform the call and update shmem (although this call never happens) */
_chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter);
SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t));
return;
#endif
/* x64 must read value from shmem and update arg */
SHMEM_READ(&g_shared_data, sizeof(shared_data_t));
if (out == NULL) {
return;
}
*out = g_shared_data.coin_counter;
}
void chu2to3_io_jvs_poll(uint8_t *opbtn, uint8_t *beams)
{
#if defined(ENV32BIT)
/* x86 can perform the call and update shmem (although this call never happens) */
_chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams);
SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t));
return;
#endif
/* x64 must read value from shmem and update args */
SHMEM_READ(&g_shared_data, sizeof(shared_data_t));
*opbtn = g_shared_data.opbtn;
*beams = g_shared_data.beams;
}
HRESULT chu2to3_io_slider_init(void)
{
#if defined(ENV64BIT)
/* x86 only */
return S_OK;
#endif
return _chuni_io_slider_init();
}
void chu2to3_io_slider_start(chuni_io_slider_callback_t callback)
{
#if defined(ENV64BIT)
/* x86 only */
return;
#endif
_chuni_io_slider_start(callback);
}
void chu2to3_io_slider_stop(void)
{
#if defined(ENV64BIT)
/* x86 only */
return;
#endif
_chuni_io_slider_stop();
}
void chu2to3_io_slider_set_leds(const uint8_t *rgb)
{
#if defined(ENV64BIT)
/* x86 only */
return;
#endif
_chuni_io_slider_set_leds(rgb);
}
HRESULT chu2to3_io_led_init(void)
{
#if defined(ENV64BIT)
/* x86 only */
return S_OK;
#endif
if (_chuni_io_led_init != NULL)
return _chuni_io_led_init();
return S_OK;
}
void chu2to3_io_led_set_colors(uint8_t board, uint8_t *rgb)
{
#if defined(ENV64BIT)
/* x86 only */
return;
#endif
if (_chuni_io_led_set_colors != NULL)
{
_chuni_io_led_set_colors(board, rgb);
}
}

26
chuniio/chu2to3.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
/*
CHU2TO3 CUSTOM IO API
This dll just mirrors chuniio dll binds but with a dynamic library loading and
a SHMEM system to let a single 32bit dll talk with x86 and x64 processes at once
*/
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
uint16_t chu2to3_io_get_api_version(void);
HRESULT chu2to3_io_jvs_init(void);
void chu2to3_io_jvs_poll(uint8_t *opbtn, uint8_t *beams);
void chu2to3_io_jvs_read_coin_counter(uint16_t *total);
HRESULT chu2to3_io_slider_init(void);
typedef void (*chuni_io_slider_callback_t)(const uint8_t *state);
void chu2to3_io_slider_start(chuni_io_slider_callback_t callback);
void chu2to3_io_slider_stop(void);
void chu2to3_io_slider_set_leds(const uint8_t *rgb);
HRESULT chu2to3_io_led_init(void);
void chu2to3_io_led_set_colors(uint8_t board, uint8_t *rgb);
uint16_t chu2to3_load_dll(const wchar_t *dllname);

View File

@ -7,6 +7,7 @@
#include "chuniio/chuniio.h"
#include "chuniio/config.h"
#include "chuniio/ledoutput.h"
#include "util/dprintf.h"
@ -18,17 +19,26 @@ static uint8_t chuni_io_hand_pos;
static HANDLE chuni_io_slider_thread;
static bool chuni_io_slider_stop_flag;
static struct chuni_io_config chuni_io_cfg;
static HANDLE chuni_io_slider_led_port;
uint16_t chuni_io_get_api_version(void)
{
return 0x0101;
return 0x0102;
}
HRESULT chuni_io_jvs_init(void)
{
chuni_io_config_load(&chuni_io_cfg, L".\\segatools.ini");
led_init_mutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (led_init_mutex == NULL)
{
return E_FAIL;
}
return S_OK;
}
@ -38,7 +48,7 @@ void chuni_io_jvs_read_coin_counter(uint16_t *out)
return;
}
if (GetAsyncKeyState(chuni_io_cfg.vk_coin)) {
if (GetAsyncKeyState(chuni_io_cfg.vk_coin) & 0x8000) {
if (!chuni_io_coin) {
chuni_io_coin = true;
chuni_io_coins++;
@ -62,15 +72,6 @@ void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams)
*opbtn |= CHUNI_IO_OPBTN_SERVICE;
}
if (GetAsyncKeyState(chuni_io_cfg.vk_coin) & 0x8000) {
if (!chuni_io_coin) {
chuni_io_coin = true;
*opbtn |= CHUNI_IO_OPBTN_COIN;
}
} else {
chuni_io_coin = false;
}
if (chuni_io_cfg.vk_ir_emu) {
// Use emulated AIR
if (GetAsyncKeyState(chuni_io_cfg.vk_ir_emu)) {
@ -90,19 +91,19 @@ void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams)
}
} else {
// Use actual AIR
// IR format is beams[5:0] = {b5,b6,b3,b4,b1,b2};
for (i = 0 ; i < 3 ; i++) {
if (GetAsyncKeyState(chuni_io_cfg.vk_ir[i*2]) & 0x8000)
*beams |= (1 << (i*2+1));
if (GetAsyncKeyState(chuni_io_cfg.vk_ir[i*2+1]) & 0x8000)
*beams |= (1 << (i*2));
for (i = 0; i < 6; i++) {
if(GetAsyncKeyState(chuni_io_cfg.vk_ir[i]) & 0x8000) {
*beams |= (1 << i);
} else {
*beams &= ~(1 << i);
}
}
}
}
HRESULT chuni_io_slider_init(void)
{
return S_OK;
return led_output_init(&chuni_io_cfg); // because of slider LEDs
}
void chuni_io_slider_start(chuni_io_slider_callback_t callback)
@ -120,39 +121,6 @@ void chuni_io_slider_start(chuni_io_slider_callback_t callback)
callback,
0,
NULL);
chuni_io_slider_led_port = CreateFileW(chuni_io_cfg.led_com,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (chuni_io_slider_led_port == INVALID_HANDLE_VALUE)
dprintf("Chunithm LEDs: Failed to open COM port (Attempted on %S)\n", chuni_io_cfg.led_com);
else
dprintf("Chunithm LEDs: COM Port Success!\n");
DCB dcb_serial_params = { 0 };
dcb_serial_params.DCBlength = sizeof(dcb_serial_params);
status = GetCommState(chuni_io_slider_led_port, &dcb_serial_params);
dcb_serial_params.BaudRate = CBR_115200; // Setting BaudRate = 115200
dcb_serial_params.ByteSize = 8; // Setting ByteSize = 8
dcb_serial_params.StopBits = ONESTOPBIT;// Setting StopBits = 1
dcb_serial_params.Parity = NOPARITY; // Setting Parity = None
SetCommState(chuni_io_slider_led_port, &dcb_serial_params);
COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 50; // in milliseconds
timeouts.ReadTotalTimeoutConstant = 50; // in milliseconds
timeouts.ReadTotalTimeoutMultiplier = 10; // in milliseconds
timeouts.WriteTotalTimeoutConstant = 50; // in milliseconds
timeouts.WriteTotalTimeoutMultiplier = 10; // in milliseconds
SetCommTimeouts(chuni_io_slider_led_port, &timeouts);
}
void chuni_io_slider_stop(void)
@ -167,34 +135,11 @@ void chuni_io_slider_stop(void)
CloseHandle(chuni_io_slider_thread);
chuni_io_slider_thread = NULL;
chuni_io_slider_stop_flag = false;
dprintf("Chunithm LEDs: Closing COM port\n");
CloseHandle(chuni_io_slider_led_port);
}
void chuni_io_slider_set_leds(const uint8_t *rgb)
{
if (chuni_io_slider_led_port != INVALID_HANDLE_VALUE)
{
char led_buffer[100];
DWORD bytes_to_write; // No of bytes to write into the port
DWORD bytes_written = 0; // No of bytes written to the port
bytes_to_write = sizeof(led_buffer);
BOOL status;
led_buffer[0] = 0xAA;
led_buffer[1] = 0xAA;
memcpy(led_buffer+2, rgb, sizeof(uint8_t) * 96);
led_buffer[98] = 0xDD;
led_buffer[99] = 0xDD;
status = WriteFile(chuni_io_slider_led_port, // Handle to the Serial port
led_buffer, // Data to be written to the port
bytes_to_write, //No of bytes to write
&bytes_written, //Bytes written
NULL);
}
led_output_update(2, rgb);
}
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx)
@ -220,3 +165,13 @@ static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx)
return 0;
}
HRESULT chuni_io_led_init(void)
{
return led_output_init(&chuni_io_cfg);
}
void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb)
{
led_output_update(board, rgb);
}

View File

@ -8,6 +8,7 @@
- 0x0100: Initial API version (assumed if chuni_io_get_api_version is not
exported)
- 0x0101: Fix IR beam mappings
- 0x0102: Add air tower led and billboard support
*/
#include <windows.h>
@ -145,3 +146,30 @@ void chuni_io_slider_stop(void);
Minimum API version: 0x0100 */
void chuni_io_slider_set_leds(const uint8_t *rgb);
/* Initialize LED emulation. This function will be called before any
other chuni_io_led_*() function calls.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0102 */
HRESULT chuni_io_led_init(void);
/* Update the RGB LEDs. rgb is a pointer to an array of up to 63 * 3 = 189 bytes.
Chunithm uses two chains/boards with WS2811 protocol (each logical led corresponds to 3 physical leds).
board 0 is on the left side and board 1 on the right side of the cab
left side has 5*10 rgb values for the billboard, followed by 3 rgb values for the air tower
right side has 6*10 rgb values for the billboard, followed by 3 rgb values for the air tower
Each rgb value is comprised of 3 bytes in R,G,B order
NOTE: billboard strips have alternating direction (bottom to top, top to bottom, ...)
Minimum API version: 0x0102 */
void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb);

View File

@ -57,7 +57,24 @@ void chuni_io_config_load(
filename);
}
GetPrivateProfileStringW(L"slider", L"ledport", L"COM2", port_input, 6, filename);
wcsncpy(cfg->led_com, L"\\\\.\\", 4);
wcsncat_s(cfg->led_com, 11, port_input, 6);
cfg->led_output_pipe = GetPrivateProfileIntW(L"led", L"cabLedOutputPipe", 1, filename);
cfg->led_output_serial = GetPrivateProfileIntW(L"led", L"cabLedOutputSerial", 0, filename);
cfg->slider_led_output_pipe = GetPrivateProfileIntW(L"led", L"controllerLedOutputPipe", 1, filename);
cfg->slider_led_output_serial = GetPrivateProfileIntW(L"led", L"controllerLedOutputSerial", 0, filename);
cfg->led_serial_baud = GetPrivateProfileIntW(L"led", L"serialBaud", 921600, filename);
GetPrivateProfileStringW(
L"led",
L"serialPort",
L"COM5",
port_input,
6,
filename);
// Sanitize the output path. If it's a serial COM port, it needs to be prefixed
// with `\\.\`.
wcsncpy(cfg->led_serial_port, L"\\\\.\\", 4);
wcsncat_s(cfg->led_serial_port, 11, port_input, 6);
}

View File

@ -10,7 +10,18 @@ struct chuni_io_config {
uint8_t vk_ir_emu;
uint8_t vk_ir[6];
uint8_t vk_cell[32];
wchar_t led_com[12];
// Which ways to output LED information are enabled
bool led_output_pipe;
bool led_output_serial;
bool slider_led_output_pipe;
bool slider_led_output_serial;
// The name of a COM port to output LED data on, in serial mode
wchar_t led_serial_port[12];
int32_t led_serial_baud;
};
void chuni_io_config_load(

22
chuniio/leddata.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <windows.h>
#include <stdint.h>
#define LED_PACKET_FRAMING 0xE0
#define LED_PACKET_ESCAPE 0xD0
#define LED_NUM_MAX 66
#define LED_BOARDS_TOTAL 3
#define LED_OUTPUT_HEADER_SIZE 2
#define LED_OUTPUT_DATA_SIZE_MAX LED_NUM_MAX * 3 * 2 // max if every byte's escaped
#define LED_OUTPUT_TOTAL_SIZE_MAX LED_OUTPUT_HEADER_SIZE + LED_OUTPUT_DATA_SIZE_MAX
// This struct is used to send data related to the slider and billboard LEDs
struct _chuni_led_data_buf_t {
byte framing; // Sync byte
uint8_t board; // LED output the data is for (0-1: billboard, 2: slider)
byte data[LED_OUTPUT_DATA_SIZE_MAX]; // Buffer for LEDs
byte data_len; // How many bytes to output from the buffer
};
static byte chuni_led_board_data_lens[LED_BOARDS_TOTAL] = {53*3, 63*3, 31*3};

133
chuniio/ledoutput.c Normal file
View File

@ -0,0 +1,133 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include "chuniio/config.h"
#include "chuniio/leddata.h"
#include "chuniio/ledoutput.h"
#include "chuniio/pipeimpl.h"
#include "chuniio/serialimpl.h"
static struct _chuni_led_data_buf_t led_unescaped_buf[LED_BOARDS_TOTAL];
static struct _chuni_led_data_buf_t led_escaped_buf[LED_BOARDS_TOTAL];
static bool led_output_is_init = false;
static struct chuni_io_config* config;
static bool any_outputs_enabled;
HANDLE led_init_mutex;
HRESULT led_output_init(struct chuni_io_config* const cfg)
{
DWORD dwWaitResult = WaitForSingleObject(led_init_mutex, INFINITE);
if (dwWaitResult == WAIT_FAILED)
{
return HRESULT_FROM_WIN32(GetLastError());
// return 1;
}
else if (dwWaitResult != WAIT_OBJECT_0)
{
return E_FAIL;
// return 1;
}
if (!led_output_is_init)
{
config = cfg;
// Setup the framing bytes for the packets
for (int i = 0; i < LED_BOARDS_TOTAL; i++) {
led_unescaped_buf[i].framing = LED_PACKET_FRAMING;
led_unescaped_buf[i].board = i;
led_unescaped_buf[i].data_len = chuni_led_board_data_lens[i];
led_escaped_buf[i].framing = LED_PACKET_FRAMING;
led_escaped_buf[i].board = i;
led_escaped_buf[i].data_len = chuni_led_board_data_lens[i];
}
any_outputs_enabled = config->led_output_pipe || config->slider_led_output_pipe
|| config->led_output_serial || config->slider_led_output_serial;
if (config->led_output_pipe || config->slider_led_output_pipe)
{
led_pipe_init(); // don't really care about errors here tbh
}
if (config->led_output_serial || config->slider_led_output_serial)
{
led_serial_init(config->led_serial_port, config->led_serial_baud);
}
}
led_output_is_init = true;
ReleaseMutex(led_init_mutex);
return S_OK;
// return 0;
}
struct _chuni_led_data_buf_t* escape_led_data(struct _chuni_led_data_buf_t* unescaped)
{
struct _chuni_led_data_buf_t* out_struct = &led_escaped_buf[unescaped->board];
byte* in_buf = unescaped->data;
byte* out_buf = out_struct->data;
int i = 0;
int o = 0;
while (i < unescaped->data_len)
{
byte b = in_buf[i++];
if (b == LED_PACKET_FRAMING || b == LED_PACKET_ESCAPE)
{
out_buf[o++] = LED_PACKET_ESCAPE;
b--;
}
out_buf[o++] = b;
}
out_struct->data_len = o;
return out_struct;
}
void led_output_update(uint8_t board, const byte* rgb)
{
if (board < 0 || board > 2 || !any_outputs_enabled)
{
return;
}
memcpy(led_unescaped_buf[board].data, rgb, led_unescaped_buf[board].data_len);
struct _chuni_led_data_buf_t* escaped_data = escape_led_data(&led_unescaped_buf[board]);
if (board < 2)
{
// billboard
if (config->led_output_pipe)
{
led_pipe_update(escaped_data);
}
if (config->led_output_serial)
{
led_serial_update(escaped_data);
}
}
else
{
// slider
if (config->slider_led_output_pipe)
{
led_pipe_update(escaped_data);
}
if (config->slider_led_output_serial)
{
led_serial_update(escaped_data);
}
}
}

19
chuniio/ledoutput.h Normal file
View File

@ -0,0 +1,19 @@
/*
LED output functions
Credits:
somewhatlurker, skogaby
*/
#pragma once
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include "chuniio/config.h"
extern HANDLE led_init_mutex;
HRESULT led_output_init(struct chuni_io_config* const cfg);
void led_output_update(uint8_t board, const byte* rgb);

View File

@ -5,9 +5,18 @@ chuniio_lib = static_library(
implicit_include_directories : false,
c_pch : '../precompiled.h',
sources : [
'chu2to3.c',
'chu2to3.h',
'chuniio.c',
'chuniio.h',
'config.c',
'config.h',
'leddata.h',
'ledoutput.c',
'ledoutput.h',
'pipeimpl.c',
'pipeimpl.h',
'serialimpl.c',
'serialimpl.h'
],
)

160
chuniio/pipeimpl.c Normal file
View File

@ -0,0 +1,160 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include "chuniio/leddata.h"
#include "chuniio/pipeimpl.h"
static bool pipe_update[LED_BOARDS_TOTAL];
// incoming data is copied into these to ensure it isn't written during output
static struct _chuni_led_data_buf_t pipe_write_buf[LED_BOARDS_TOTAL];
static HANDLE pipe_write_mutex;
static HRESULT pipe_create(LPHANDLE hPipe, LPCWSTR lpszPipename, DWORD dwBufSize)
{
*hPipe = INVALID_HANDLE_VALUE;
*hPipe = CreateNamedPipeW(
lpszPipename, // pipe name
PIPE_ACCESS_OUTBOUND, // read/write access
PIPE_TYPE_BYTE | // byte type pipe
PIPE_WAIT, // blocking mode
PIPE_UNLIMITED_INSTANCES, // max. instances
dwBufSize, // output buffer size
0, // input buffer size
0, // client time-out
NULL); // default security attribute
if (*hPipe == INVALID_HANDLE_VALUE)
{
return E_FAIL;
}
return S_OK;
}
static HRESULT pipe_write(HANDLE hPipe, LPCVOID lpBuffer, DWORD dwSize)
{
DWORD cbWritten = 0;
bool fSuccess = WriteFile(
hPipe,
lpBuffer,
dwSize,
&cbWritten,
NULL);
if (!fSuccess || cbWritten != dwSize)
{
DWORD last_err = GetLastError();
return (last_err == ERROR_BROKEN_PIPE) ? E_ABORT : E_FAIL;
}
return S_OK;
}
static unsigned int __stdcall chuni_io_led_pipe_thread_proc(void *ctx)
{
HANDLE hPipe;
LPCWSTR lpszPipename = L"\\\\.\\pipe\\chuni_led";
while (true)
{
hPipe = INVALID_HANDLE_VALUE;
if (pipe_create(&hPipe, lpszPipename, LED_OUTPUT_TOTAL_SIZE_MAX) != S_OK)
{
continue;
}
// wait for a connection to the pipe
bool fConnected = ConnectNamedPipe(hPipe, NULL) ?
true : (GetLastError() == ERROR_PIPE_CONNECTED);
while (fConnected)
{
if (WaitForSingleObject(pipe_write_mutex, INFINITE) != WAIT_OBJECT_0)
{
continue;
}
for (int i = 0; i < LED_BOARDS_TOTAL; i++) {
if (pipe_update[i])
{
HRESULT result = pipe_write(
hPipe,
&pipe_write_buf[i],
LED_OUTPUT_HEADER_SIZE + pipe_write_buf[i].data_len);
if (result != S_OK)
{
//if (result == E_ABORT)
//{
fConnected = false;
//}
break;
}
pipe_update[i] = false;
}
}
ReleaseMutex(pipe_write_mutex);
}
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
}
return 0;
}
HRESULT led_pipe_init()
{
pipe_write_mutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (pipe_write_mutex == NULL)
{
return E_FAIL;
}
// clear out update bools
for (int i = 0; i < LED_BOARDS_TOTAL; i++) {
pipe_update[i] = false;
}
_beginthreadex(
NULL,
0,
chuni_io_led_pipe_thread_proc,
0,
0,
NULL);
return S_OK;
}
void led_pipe_update(struct _chuni_led_data_buf_t* data)
{
if (data->board > 2)
{
return;
}
if (WaitForSingleObject(pipe_write_mutex, INFINITE) != WAIT_OBJECT_0)
{
return;
}
memcpy(&pipe_write_buf[data->board], data, sizeof(struct _chuni_led_data_buf_t));
pipe_update[data->board] = true;
ReleaseMutex(pipe_write_mutex);
}

14
chuniio/pipeimpl.h Normal file
View File

@ -0,0 +1,14 @@
/*
Pipe implementation for chuniio
Credits:
somewhatlurker, skogaby
*/
#pragma once
#include <windows.h>
#include "chuniio/leddata.h"
HRESULT led_pipe_init();
void led_pipe_update(struct _chuni_led_data_buf_t* data);

99
chuniio/serialimpl.c Normal file
View File

@ -0,0 +1,99 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include "chuniio/leddata.h"
#include "chuniio/serialimpl.h"
#include "util/dprintf.h"
static HANDLE serial_port;
static HANDLE serial_write_mutex;
HRESULT led_serial_init(wchar_t led_com[12], DWORD baud)
{
// Setup the serial communications
BOOL status;
serial_port = CreateFileW(led_com,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (serial_port == INVALID_HANDLE_VALUE)
dprintf("Chunithm Serial LEDs: Failed to open COM port (Attempted on %S)\n", led_com);
else
dprintf("Chunithm Serial LEDs: COM port success!\n");
DCB dcb_serial_params = { 0 };
dcb_serial_params.DCBlength = sizeof(dcb_serial_params);
status = GetCommState(serial_port, &dcb_serial_params);
dcb_serial_params.BaudRate = baud;
dcb_serial_params.ByteSize = 8;
dcb_serial_params.StopBits = ONESTOPBIT;
dcb_serial_params.Parity = NOPARITY;
SetCommState(serial_port, &dcb_serial_params);
COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
SetCommTimeouts(serial_port, &timeouts);
if (!status)
{
return E_FAIL;
}
serial_write_mutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (serial_write_mutex == NULL)
{
return E_FAIL;
}
return S_OK;
}
void led_serial_update(struct _chuni_led_data_buf_t* data)
{
if (data->board > 2)
{
return;
}
if (WaitForSingleObject(serial_write_mutex, INFINITE) != WAIT_OBJECT_0)
{
return;
}
BOOL status = true;
DWORD bytes_written = 0;
if (serial_port != INVALID_HANDLE_VALUE) {
status = WriteFile(
serial_port,
data,
LED_OUTPUT_HEADER_SIZE + data->data_len,
&bytes_written,
NULL);
}
if (!status) {
DWORD last_err = GetLastError();
// dprintf("Chunithm Serial LEDs: Serial port write failed -- %d\n", last_err);
}
ReleaseMutex(serial_write_mutex);
}

15
chuniio/serialimpl.h Normal file
View File

@ -0,0 +1,15 @@
/*
Serial LED implementation for chuniio
Credits:
somewhatlurker, skogaby
*/
#pragma once
#include <windows.h>
#include "chuniio/leddata.h"
HRESULT led_serial_init(wchar_t led_com[12], DWORD baud);
void led_serial_update(struct _chuni_led_data_buf_t* data);

View File

@ -3,6 +3,7 @@
#include <assert.h>
#include <stdlib.h>
#include "chuniio/chu2to3.h"
#include "chusanhook/chuni-dll.h"
#include "util/dll-bind.h"
@ -30,9 +31,59 @@ const struct dll_bind_sym chuni_dll_syms[] = {
}, {
.sym = "chuni_io_slider_set_leds",
.off = offsetof(struct chuni_dll, slider_set_leds),
}, {
.sym = "chuni_io_led_init",
.off = offsetof(struct chuni_dll, led_init),
}, {
.sym = "chuni_io_led_set_colors",
.off = offsetof(struct chuni_dll, led_set_leds),
}
};
const struct dll_bind_sym chu2to3_dll_syms[] = {
{
.sym = "chu2to3_io_jvs_init",
.off = offsetof(struct chuni_dll, jvs_init),
}, {
.sym = "chu2to3_io_jvs_poll",
.off = offsetof(struct chuni_dll, jvs_poll),
}, {
.sym = "chu2to3_io_jvs_read_coin_counter",
.off = offsetof(struct chuni_dll, jvs_read_coin_counter),
}, {
.sym = "chu2to3_io_slider_init",
.off = offsetof(struct chuni_dll, slider_init),
}, {
.sym = "chu2to3_io_slider_start",
.off = offsetof(struct chuni_dll, slider_start),
}, {
.sym = "chu2to3_io_slider_stop",
.off = offsetof(struct chuni_dll, slider_stop),
}, {
.sym = "chu2to3_io_slider_set_leds",
.off = offsetof(struct chuni_dll, slider_set_leds),
}, {
.sym = "chu2to3_io_led_init",
.off = offsetof(struct chuni_dll, led_init),
}, {
.sym = "chu2to3_io_led_set_colors",
.off = offsetof(struct chuni_dll, led_set_leds),
}
};
/* Helper function to determine upon dll_bind failure whether the required functions were found
NOTE: relies on symbols order declared above */
static HRESULT has_enough_symbols(uint16_t version, uint8_t count)
{
if ( version <= 0x0101 && count == 7 )
return S_OK;
if ( version >= 0x0102 && count == 9 )
return S_OK;
return E_FAIL;
}
struct chuni_dll chuni_dll;
// Copypasta DLL binding and diagnostic message boilerplate.
@ -52,7 +103,12 @@ HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self)
assert(cfg != NULL);
assert(self != NULL);
if (cfg->path[0] != L'\0') {
owned = NULL;
src = self;
if (cfg->chu2to3) {
dprintf("Chunithm IO: using chu2to3 engine for IO DLL: %S\n", cfg->path);
} else if (cfg->path[0] != L'\0') {
owned = LoadLibraryW(cfg->path);
if (owned == NULL) {
@ -66,12 +122,18 @@ HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self)
dprintf("Chunithm IO: Using custom IO DLL: %S\n", cfg->path);
src = owned;
} else {
owned = NULL;
src = self;
}
get_api_version = (void *) GetProcAddress(src, "chuni_io_get_api_version");
if (cfg->chu2to3) {
if (chu2to3_load_dll(cfg->path) != 0)
dprintf("Could not init chu2to3 engine\n");
get_api_version = (void *) GetProcAddress(src, "chu2to3_io_get_api_version");
}
else
{
get_api_version = (void *) GetProcAddress(src, "chuni_io_get_api_version");
}
if (get_api_version != NULL) {
chuni_dll.api_version = get_api_version();
@ -91,17 +153,26 @@ HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self)
goto end;
}
sym = chuni_dll_syms;
sym = cfg->chu2to3 ? chu2to3_dll_syms : chuni_dll_syms;
const struct dll_bind_sym *init_sym = &sym[0];
hr = dll_bind(&chuni_dll, src, &sym, _countof(chuni_dll_syms));
if (FAILED(hr)) {
if (src != self) {
dprintf("Chunithm IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
goto end;
// Might still be ok depending on external dll API version
int bind_count = sym - init_sym;
if ( has_enough_symbols(chuni_dll.api_version, bind_count) == S_OK )
{
hr = S_OK;
} else {
dprintf("Chunithm IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
dprintf("imported %d symbols\n",bind_count);
goto end;
}
} else {
dprintf("Internal error: could not reflect \"%s\"\n", sym->sym);
}

View File

@ -13,10 +13,13 @@ struct chuni_dll {
void (*slider_start)(chuni_io_slider_callback_t callback);
void (*slider_stop)(void);
void (*slider_set_leds)(const uint8_t *rgb);
HRESULT (*led_init)(void);
void (*led_set_leds)(uint8_t board, uint8_t *rgb);
};
struct chuni_dll_config {
wchar_t path[MAX_PATH];
uint8_t chu2to3;
};
extern struct chuni_dll chuni_dll;

View File

@ -20,3 +20,15 @@ EXPORTS
chuni_io_slider_set_leds
chuni_io_slider_start
chuni_io_slider_stop
chuni_io_led_init
chuni_io_led_set_colors
chu2to3_io_get_api_version
chu2to3_io_jvs_init
chu2to3_io_jvs_poll
chu2to3_io_jvs_read_coin_counter
chu2to3_io_slider_init
chu2to3_io_slider_set_leds
chu2to3_io_slider_start
chu2to3_io_slider_stop
chu2to3_io_led_init
chu2to3_io_led_set_colors

View File

@ -39,16 +39,27 @@ void chuni_dll_config_load(
// Workaround for x64/x86 external IO dlls
// path32 for 32bit, path64 for 64bit
// for else.. is that possible? idk
// path for 32bit only dlls (internal chu2to3 engine)
GetPrivateProfileStringW(
L"chuniio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
if (cfg->path[0] != L'\0') {
cfg->chu2to3 = 1;
} else {
cfg->chu2to3 = 0;
#if defined(ENV32BIT)
GetPrivateProfileStringW(
L"chuniio",
L"path32",
L"",
cfg->path,
_countof(cfg->path),
filename);
GetPrivateProfileStringW(
L"chuniio",
L"path32",
L"",
cfg->path,
_countof(cfg->path),
filename);
#elif defined(ENV64BIT)
GetPrivateProfileStringW(
L"chuniio",
@ -60,7 +71,7 @@ void chuni_dll_config_load(
#else
#error "Unknown environment"
#endif
}
}
void slider_config_load(struct slider_config *cfg, const wchar_t *filename)
@ -141,6 +152,9 @@ void chusan_hook_config_load(
memset(cfg, 0, sizeof(*cfg));
// Force load the 64bit Aime DLL instead of the 32bit one
cfg->aime.dll.path64 = true;
platform_config_load(&cfg->platform, filename);
aime_config_load(&cfg->aime, filename);
dvd_config_load(&cfg->dvd, filename);

View File

@ -100,11 +100,7 @@ static DWORD CALLBACK chusan_pre_startup(void)
}
bool *dipsw = &chusan_hook_cfg.platform.dipsw.dipsw[0];
if (dipsw[1] != dipsw[2]) {
dprintf("DipSw: DipSw2 and 3 must be set to the same value!\n");
goto fail;
}
bool is_cvt = dipsw[2];
for (int i = 0; i < 3; i++) {
switch (i) {
@ -117,21 +113,35 @@ static DWORD CALLBACK chusan_pre_startup(void)
break;
case 2:
dprintf("DipSw: Aime Reader: %s\n", dipsw[2] ? "CVT" : "SP");
dprintf("DipSw: Cab Type: %s\n", is_cvt ? "CVT" : "SP");
break;
}
}
unsigned int first_port = dipsw[1] ? 2 : 20;
unsigned int first_port = is_cvt ? 2 : 20;
hr = led15093_hook_init(&chusan_hook_cfg.led15093, first_port, 2, 2, 1);
if (!is_cvt) {
hr = vfd_hook_init(2);
if (FAILED(hr)) {
goto fail;
if (FAILED(hr)) {
goto fail;
}
}
hr = sg_reader_hook_init(&chusan_hook_cfg.aime, 4, dipsw[2] ? 2 : 3, chusan_hook_mod);
if ( chuni_dll.led_init == NULL || chuni_dll.led_set_leds == NULL )
{
dprintf("IO DLL doesn't support led_init/led_set_leds, cannot start LED15093 hook\n");
} else {
hr = led15093_hook_init(&chusan_hook_cfg.led15093,
chuni_dll.led_init, chuni_dll.led_set_leds, first_port, 2, 2, 1);
if (FAILED(hr)) {
goto fail;
}
}
hr = sg_reader_hook_init(&chusan_hook_cfg.aime, 4, is_cvt ? 2: 3, chusan_hook_mod);
if (FAILED(hr)) {
goto fail;

View File

@ -73,6 +73,7 @@ static HRESULT chusan_io4_poll(void* ctx, struct io4_state* state)
beams = 0;
chuni_dll.jvs_poll(&opbtn, &beams);
chuni_dll.jvs_read_coin_counter(&coins);
if (chuni_dll.api_version >= 0x0101) {
// Use correct mapping
@ -90,9 +91,7 @@ static HRESULT chusan_io4_poll(void* ctx, struct io4_state* state)
state->buttons[0] |= IO4_BUTTON_SERVICE;
}
if (opbtn & CHUNI_IO_OPBTN_COIN) {
coins++;
}
// Update the coin counter with the value from jvs_read_coin_counter
state->chutes[0] = coins << 8;
for (i = 0; i < 6; i++) {

View File

@ -1,16 +0,0 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
struct led1509306_config {
bool enable;
bool cvt_port;
char board_number[8];
char chip_number[5];
uint8_t fw_ver;
uint16_t fw_sum;
};
HRESULT led1509306_hook_init(const struct led1509306_config *cfg);

View File

@ -38,15 +38,52 @@ monitor=0
; Leave empty if you want to use Segatools built-in keyboard input.
path=
[led15093]
; 837-15093-06 LED strip emulation setting.
enable=1
[chuniio]
; To use a custom Chunithm IO DLL enter its path here.
; Leave empty if you want to use Segatools built-in keyboard input.
path=
[led15093]
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
; RGBs and the rear LED panel (billboard) on the cabinet.
enable=1
[led]
; Output billboard LED strip data to a named pipe called "\\.\pipe\chuni_ledstrip"
cabLedOutputPipe=1
; Output billboard LED strip data to serial
cabLedOutputSerial=0
; Output slider LED data to the named pipe
controllerLedOutputPipe=1
; Output slider LED data to the serial port
controllerLedOutputSerial=0
; Serial port to send data to if using serial output. Default is COM5.
;serialPort=COM5
; Baud rate for serial data
;serialBaud=921600
; Data output a sequence of bytes, with JVS-like framing.
; Each "packet" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere,
; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore
; it and use the next sent byte plus one instead.
;
; After the sync is one byte for the board number that was updated, followed by
; the red, green and blue values for each LED.
;
; Board 0 has 53 LEDs:
; [0]-[49]: snakes through left half of billboard (first column starts at top)
; [50]-[52]: left side partition LEDs
;
; Board 1 has 63 LEDs:
; [0]-[59]: right half of billboard (first column starts at bottom)
; [60]-[62]: right side partition LEDs
;
; Board 2 is the slider and has 31 LEDs:
; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers
; -----------------------------------------------------------------------------
; Input settings
; -----------------------------------------------------------------------------
@ -66,6 +103,20 @@ test=0x31
service=0x32
; Keyboard button to increment coin counter. Default is the 3 key.
coin=0x33
; Set to 0 for enable separate ir control. Deafult is space key.
ir=0x20
[ir]
; Uncomment and complete the following sequence of settings to configure a
; custom ir-cappable controller if you have one.
;ir6=0x53
; ... etc ...
;ir1=0x53
[slider]
; Enable slider emulation. If you have real AC slider, set this to 0.
; Slider serial port must be COM1.
;enable=1
; Key bindings for each of the 32 touch cells. The default key map, depicted
; in left-to-right order, is as follows:
@ -77,8 +128,8 @@ coin=0x33
;
; Uncomment and complete the following sequence of settings to configure a
; custom high-precision touch strip controller if you have one.
[slider]
;cell32=0x53
;cell31=0x53
;cell30=0x53
;cell1=0x53
;cell2=0x53
; ... etc ...
;cell31=0x53
;cell32=0x53

View File

@ -16,9 +16,9 @@ aimePath=DEVICE\aime.txt
;highbaud=1
[aimeio]
; x64 aimeio dll path.
; Uncomment this if you have custom aime implementation.
;path64=
; Uncomment this if you have custom (x64) aime implementation.
; Leave empty if you want to use Segatools built-in keyboard input.
;path=
[dns]
; Insert the hostname or IP address of the server you wish to use here.
@ -35,7 +35,7 @@ enable=1
; 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.
subnet=192.168.100.0
subnet=192.168.139.0
[gpio]
; ALLS DIP switches.
@ -49,10 +49,10 @@ freeplay=0
; LAN Install: 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
; Monitor type: 0 = 120FPS (SP), 1 = 60FPS (CVT)
; Monitor type: 0 = 120FPS, 1 = 60FPS
dipsw2=1
; Aime reader hardware type: 0 = SP, 1 = CVT. Both dipsw2 and dipsw3 must be
; the same value.
; Cab type: 0 = SP, 1 = CVT. SP will enable VFD and eMoney. This setting will switch
; the LED 837-15093-06 COM port and the AiMe reder hardware generation as well.
dipsw3=1
[gfx]
@ -63,16 +63,57 @@ framed=0
; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen)
monitor=0
[led15093]
; 837-15093-06 LED strip emulation setting.
enable=1
[chuniio]
; Uncomment this if you have custom chuniio implementation.
; Uncomment this if you have custom chuniio implementation comprised of a single 32bit DLL.
; (will use chu2to3 engine internally)
;path=
; Uncomment both of these if you have custom chuniio implementation comprised of two DLLs.
; x86 chuniio to path32, x64 to path64. Both are necessary.
;path32=
;path64=
[led15093]
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
; RGBs and the rear LED panel (billboard) on the cabinet.
enable=1
[led]
; Output billboard LED strip data to a named pipe called "\\.\pipe\chuni_led"
cabLedOutputPipe=1
; Output billboard LED strip data to serial
cabLedOutputSerial=0
; Output slider LED data to the named pipe
controllerLedOutputPipe=1
; Output slider LED data to the serial port
controllerLedOutputSerial=0
; Serial port to send data to if using serial output. Default is COM5.
;serialPort=COM5
; Baud rate for serial data
;serialBaud=921600
; Data output a sequence of bytes, with JVS-like framing.
; Each "packet" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere,
; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore
; it and use the next sent byte plus one instead.
;
; After the sync is one byte for the board number that was updated, followed by
; the red, green and blue values for each LED.
;
; Board 0 has 53 LEDs:
; [0]-[49]: snakes through left half of billboard (first column starts at top)
; [50]-[52]: left side partition LEDs
;
; Board 1 has 63 LEDs:
; [0]-[59]: right half of billboard (first column starts at bottom)
; [60]-[62]: right side partition LEDs
;
; Board 2 is the slider and has 31 LEDs:
; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers
; -----------------------------------------------------------------------------
; Input settings
; -----------------------------------------------------------------------------

View File

@ -29,7 +29,7 @@ enable=1
; 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
subnet=192.168.158.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
@ -174,3 +174,8 @@ gear6=18
; (Needed when using DirectInput for the Dualshock 4 for example)
reverseAccelAxis=0
reverseBrakeAxis=0
; Force feedback settings.
; Strength of the force feedback spring effect in percent. Possible values
; are 0-100.
centerSpringStrength=30

View File

@ -173,3 +173,8 @@ gear6=18
; (Needed when using DirectInput for the Dualshock 4 for example)
reverseAccelAxis=0
reverseBrakeAxis=0
; Force feedback settings.
; Strength of the force feedback spring effect in percent. Possible values
; are 0-100.
centerSpringStrength=30

View File

@ -133,3 +133,8 @@ wheelGreen=10
; (Needed when using DirectInput for the Dualshock 4 for example)
reverseAccelAxis=0
reverseBrakeAxis=0
; Force feedback settings.
; Strength of the force feedback spring effect in percent. Possible values
; are 0-100.
centerSpringStrength=30

View File

@ -3,6 +3,7 @@
#include <stdlib.h>
#include "board/io4.h"
#include "board/led15093.h"
#include "board/sg-reader.h"
#include "board/vfd.h"
@ -96,7 +97,8 @@ static DWORD CALLBACK fgo_pre_startup(void)
goto fail;
}
hr = led15093_hook_init(&fgo_hook_cfg.led15093, 17, 1, 1, 2);
hr = led15093_hook_init(&fgo_hook_cfg.led15093,
fgo_dll.led_init, fgo_dll.led_set_leds, 17, 1, 1, 2);
if (FAILED(hr)) {
goto fail;

View File

@ -24,6 +24,12 @@ const struct dll_bind_sym fgo_dll_syms[] = {
}, {
.sym = "fgo_io_get_analogs",
.off = offsetof(struct fgo_dll, get_analogs),
}, {
.sym = "fgo_io_led_init",
.off = offsetof(struct fgo_dll, led_init),
}, {
.sym = "fgo_io_led_set_leds",
.off = offsetof(struct fgo_dll, led_set_leds),
}
};

View File

@ -11,6 +11,8 @@ struct fgo_dll {
void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint8_t *gamebtn);
void (*get_analogs)(int16_t *stick_x, int16_t *stick_y);
HRESULT (*led_init)();
void (*led_set_leds)(uint8_t board, uint8_t *rgb);
};
struct fgo_dll_config {

View File

@ -17,6 +17,8 @@ EXPORTS
fgo_io_get_opbtns
fgo_io_init
fgo_io_poll
fgo_io_led_init
fgo_io_led_set_leds
fwdlusb_open
fwdlusb_close
fwdlusb_listupPrinter

View File

@ -139,3 +139,14 @@ void fgo_io_get_analogs(int16_t *stick_x, int16_t *stick_y)
*stick_y = fgo_stick_y;
}
}
HRESULT fgo_io_led_init(void)
{
// return 0;
return S_OK;
}
void fgo_io_led_set_leds(uint8_t board, uint8_t *rgb)
{
return;
}

View File

@ -69,3 +69,18 @@ void fgo_io_get_gamebtns(uint8_t *btn);
Minimum API version: 0x0100 */
void fgo_io_get_analogs(int16_t *stick_x, int16_t *stick_y);
/* Initialize LED emulation. This function will be called before any
other fgo_io_led_*() function calls.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility. */
HRESULT fgo_io_led_init(void);
/* Update the RGB LEDs.
Exact layout is TBD. */
void fgo_io_led_set_leds(uint8_t board, uint8_t *rgb);

View File

@ -79,6 +79,14 @@ void idac_di_config_load(struct idac_di_config *cfg, const wchar_t *filename)
cfg->gear[i] = GetPrivateProfileIntW(L"dinput", key, i + 1, filename);
}
// FFB configuration
cfg->center_spring_strength = GetPrivateProfileIntW(
L"dinput",
L"centerSpringStrength",
30,
filename);
}
void idac_xi_config_load(struct idac_xi_config *cfg, const wchar_t *filename)

View File

@ -23,6 +23,9 @@ struct idac_di_config {
uint8_t gear[6];
bool reverse_brake_axis;
bool reverse_accel_axis;
// FFB configuration
uint16_t center_spring_strength;
};
struct idac_xi_config {

View File

@ -44,7 +44,8 @@ HRESULT idac_di_dev_start(IDirectInputDevice8W *dev, HWND wnd)
return hr;
}
void idac_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
void idac_di_dev_start_fx(
IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength)
{
/* 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
@ -67,7 +68,7 @@ void idac_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
DWORD axis;
LONG direction;
DIEFFECT fx;
DICONSTANTFORCE cf;
DICONDITION cond;
HRESULT hr;
assert(dev != NULL);
@ -77,11 +78,17 @@ void idac_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
dprintf("DirectInput: Starting force feedback (may take a sec)\n");
// Auto-centering effect
axis = DIJOFS_X;
direction = 0;
memset(&cf, 0, sizeof(cf));
cf.lMagnitude = 0;
memset(&cond, 0, sizeof(cond));
cond.lOffset = 0;
cond.lPositiveCoefficient = strength;
cond.lNegativeCoefficient = strength;
cond.dwPositiveSaturation = strength; // For FG920?
cond.dwNegativeSaturation = strength; // For FG920?
cond.lDeadBand = 0;
memset(&fx, 0, sizeof(fx));
fx.dwSize = sizeof(fx);
@ -93,20 +100,19 @@ void idac_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
fx.cAxes = 1;
fx.rgdwAxes = &axis;
fx.rglDirection = &direction;
fx.cbTypeSpecificParams = sizeof(cf);
fx.lpvTypeSpecificParams = &cf;
fx.cbTypeSpecificParams = sizeof(cond);
fx.lpvTypeSpecificParams = &cond;
hr = IDirectInputDevice8_CreateEffect(
dev,
&GUID_ConstantForce,
&GUID_Spring,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: DirectInput force feedback unavailable: %08x\n",
dprintf("DirectInput: Centering spring force feedback unavailable: %08x\n",
(int) hr);
return;
}
@ -114,15 +120,15 @@ void idac_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
if (FAILED(hr)) {
IDirectInputEffect_Release(obj);
dprintf("DirectInput: DirectInput force feedback start failed: %08x\n",
dprintf("DirectInput: Centering spring force feedback start failed: %08x\n",
(int) hr);
return;
}
*out = obj;
dprintf("DirectInput: Force feedback initialized and set to zero\n");
dprintf("DirectInput: Centering spring effects initialized with strength %d%%\n",
strength / 100);
}
HRESULT idac_di_dev_poll(

View File

@ -11,7 +11,7 @@ union idac_di_state {
};
HRESULT idac_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
void idac_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out);
void idac_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength);
HRESULT idac_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,

View File

@ -75,6 +75,7 @@ static uint8_t idac_di_gear[6];
static bool idac_di_use_pedals;
static bool idac_di_reverse_brake_axis;
static bool idac_di_reverse_accel_axis;
static uint16_t idac_di_center_spring_strength;
HRESULT idac_di_init(
const struct idac_di_config *cfg,
@ -173,7 +174,9 @@ HRESULT idac_di_init(
return hr;
}
idac_di_dev_start_fx(idac_di_dev, &idac_di_fx);
// Convert the strength from 0-100 to 0-10000 for DirectInput
idac_di_dev_start_fx(idac_di_dev, &idac_di_fx,
idac_di_center_spring_strength * 100);
if (cfg->pedals_name[0] != L'\0') {
hr = IDirectInput8_EnumDevices(
@ -364,6 +367,16 @@ static HRESULT idac_di_config_apply(const struct idac_di_config *cfg)
idac_di_gear[i] = cfg->gear[i];
}
// FFB configuration
if (cfg->center_spring_strength < 0 || cfg->center_spring_strength > 100) {
dprintf("Wheel: Invalid center spring strength: %i\n", cfg->center_spring_strength);
return E_INVALIDARG;
}
idac_di_center_spring_strength = cfg->center_spring_strength;
return S_OK;
}

View File

@ -77,6 +77,13 @@ void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename)
cfg->gear[i] = GetPrivateProfileIntW(L"dinput", key, i + 1, filename);
}
// FFB configuration
cfg->center_spring_strength = GetPrivateProfileIntW(
L"dinput",
L"centerSpringStrength",
30,
filename);
}
void idz_xi_config_load(struct idz_xi_config *cfg, const wchar_t *filename)

View File

@ -21,6 +21,9 @@ struct idz_di_config {
uint8_t gear[6];
bool reverse_brake_axis;
bool reverse_accel_axis;
// FFB configuration
uint16_t center_spring_strength;
};
struct idz_xi_config {

View File

@ -44,7 +44,8 @@ HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd)
return hr;
}
void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
void idz_di_dev_start_fx(
IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength)
{
/* 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
@ -67,7 +68,7 @@ void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
DWORD axis;
LONG direction;
DIEFFECT fx;
DICONSTANTFORCE cf;
DICONDITION cond;
HRESULT hr;
assert(dev != NULL);
@ -77,11 +78,17 @@ void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
dprintf("DirectInput: Starting force feedback (may take a sec)\n");
// Auto-centering effect
axis = DIJOFS_X;
direction = 0;
memset(&cf, 0, sizeof(cf));
cf.lMagnitude = 0;
memset(&cond, 0, sizeof(cond));
cond.lOffset = 0;
cond.lPositiveCoefficient = strength;
cond.lNegativeCoefficient = strength;
cond.dwPositiveSaturation = strength; // For FG920?
cond.dwNegativeSaturation = strength; // For FG920?
cond.lDeadBand = 0;
memset(&fx, 0, sizeof(fx));
fx.dwSize = sizeof(fx);
@ -93,20 +100,19 @@ void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
fx.cAxes = 1;
fx.rgdwAxes = &axis;
fx.rglDirection = &direction;
fx.cbTypeSpecificParams = sizeof(cf);
fx.lpvTypeSpecificParams = &cf;
fx.cbTypeSpecificParams = sizeof(cond);
fx.lpvTypeSpecificParams = &cond;
hr = IDirectInputDevice8_CreateEffect(
dev,
&GUID_ConstantForce,
&GUID_Spring,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: DirectInput force feedback unavailable: %08x\n",
dprintf("DirectInput: Centering spring force feedback unavailable: %08x\n",
(int) hr);
return;
}
@ -114,15 +120,15 @@ void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
if (FAILED(hr)) {
IDirectInputEffect_Release(obj);
dprintf("DirectInput: DirectInput force feedback start failed: %08x\n",
dprintf("DirectInput: Centering spring force feedback start failed: %08x\n",
(int) hr);
return;
}
*out = obj;
dprintf("DirectInput: Force feedback initialized and set to zero\n");
dprintf("DirectInput: Centering spring effects initialized with strength %d%%\n",
strength / 100);
}
HRESULT idz_di_dev_poll(

View File

@ -11,7 +11,7 @@ union idz_di_state {
};
HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out);
void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength);
HRESULT idz_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,

View File

@ -73,6 +73,7 @@ static uint8_t idz_di_gear[6];
static bool idz_di_use_pedals;
static bool idz_di_reverse_brake_axis;
static bool idz_di_reverse_accel_axis;
static uint16_t idz_di_center_spring_strength;
HRESULT idz_di_init(
const struct idz_di_config *cfg,
@ -171,7 +172,9 @@ HRESULT idz_di_init(
return hr;
}
idz_di_dev_start_fx(idz_di_dev, &idz_di_fx);
// Convert the strength from 0-100 to 0-10000 for DirectInput
idz_di_dev_start_fx(idz_di_dev, &idz_di_fx,
idz_di_center_spring_strength * 100);
if (cfg->pedals_name[0] != L'\0') {
hr = IDirectInput8_EnumDevices(
@ -346,6 +349,16 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg)
idz_di_gear[i] = cfg->gear[i];
}
// FFB configuration
if (cfg->center_spring_strength < 0 || cfg->center_spring_strength > 100) {
dprintf("Wheel: Invalid center spring strength: %i\n", cfg->center_spring_strength);
return E_INVALIDARG;
}
idz_di_center_spring_strength = cfg->center_spring_strength;
return S_OK;
}

View File

@ -17,11 +17,13 @@ add_project_arguments(
language: 'c',
)
# Use get_argument_syntax() instead once Meson 0.49.0 releases
if meson.get_compiler('c').get_id() != 'msvc'
cc = meson.get_compiler('c')
if cc.get_id() != 'msvc'
add_project_arguments(
'-ffunction-sections',
'-fdata-sections',
'-flto', # Enable Link-Time Optimization
language: 'c',
)
@ -30,11 +32,12 @@ if meson.get_compiler('c').get_id() != 'msvc'
'-Wl,--exclude-all-symbols',
'-Wl,--gc-sections',
'-static-libgcc',
'-flto', # Enable Link-Time Optimization
'-Wl,-s', # Strip debug symbols
language: 'c',
)
endif
cc = meson.get_compiler('c')
shlwapi_lib = cc.find_library('shlwapi')
dinput8_lib = cc.find_library('dinput8')
dxguid_lib = cc.find_library('dxguid')

View File

@ -3,7 +3,7 @@
#include <stddef.h>
#include "board/config.h"
#include "board/led15093.h"
// #include "board/led15093.h"
#include "gfxhook/gfx.h"
@ -19,7 +19,7 @@ struct mu3_hook_config {
struct dvd_config dvd;
struct io4_config io4;
struct gfx_config gfx;
struct led15093_config led15093;
// struct led15093_config led15093;
struct mu3_dll_config dll;
};

View File

@ -61,11 +61,14 @@ static DWORD CALLBACK mu3_pre_startup(void)
goto fail;
}
/*
// Does not work, Unity moment
hr = led15093_hook_init(&mu3_hook_cfg.led15093, 3, 1, 1, 2);
if (FAILED(hr)) {
return hr;
}
*/
hr = sg_reader_hook_init(&mu3_hook_cfg.aime, 1, 1, mu3_hook_mod);

View File

@ -67,6 +67,14 @@ void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename)
L"reverseAccelAxis",
0,
filename);
// FFB configuration
cfg->center_spring_strength = GetPrivateProfileIntW(
L"dinput",
L"centerSpringStrength",
30,
filename);
}
void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename)

View File

@ -19,6 +19,9 @@ struct swdc_di_config {
uint8_t wheel_yellow;
bool reverse_brake_axis;
bool reverse_accel_axis;
// FFB configuration
uint16_t center_spring_strength;
};
struct swdc_xi_config {

View File

@ -44,7 +44,8 @@ HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd)
return hr;
}
void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
void swdc_di_dev_start_fx(
IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength)
{
/* 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
@ -67,7 +68,7 @@ void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
DWORD axis;
LONG direction;
DIEFFECT fx;
DICONSTANTFORCE cf;
DICONDITION cond;
HRESULT hr;
assert(dev != NULL);
@ -77,11 +78,17 @@ void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
dprintf("DirectInput: Starting force feedback (may take a sec)\n");
// Auto-centering effect
axis = DIJOFS_X;
direction = 0;
memset(&cf, 0, sizeof(cf));
cf.lMagnitude = 0;
memset(&cond, 0, sizeof(cond));
cond.lOffset = 0;
cond.lPositiveCoefficient = strength;
cond.lNegativeCoefficient = strength;
cond.dwPositiveSaturation = strength; // For FG920?
cond.dwNegativeSaturation = strength; // For FG920?
cond.lDeadBand = 0;
memset(&fx, 0, sizeof(fx));
fx.dwSize = sizeof(fx);
@ -93,20 +100,19 @@ void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
fx.cAxes = 1;
fx.rgdwAxes = &axis;
fx.rglDirection = &direction;
fx.cbTypeSpecificParams = sizeof(cf);
fx.lpvTypeSpecificParams = &cf;
fx.cbTypeSpecificParams = sizeof(cond);
fx.lpvTypeSpecificParams = &cond;
hr = IDirectInputDevice8_CreateEffect(
dev,
&GUID_ConstantForce,
&GUID_Spring,
&fx,
&obj,
NULL);
if (FAILED(hr)) {
dprintf("DirectInput: DirectInput force feedback unavailable: %08x\n",
dprintf("DirectInput: Centering spring force feedback unavailable: %08x\n",
(int) hr);
return;
}
@ -114,15 +120,15 @@ void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out)
if (FAILED(hr)) {
IDirectInputEffect_Release(obj);
dprintf("DirectInput: DirectInput force feedback start failed: %08x\n",
dprintf("DirectInput: Centering spring force feedback start failed: %08x\n",
(int) hr);
return;
}
*out = obj;
dprintf("DirectInput: Force feedback initialized and set to zero\n");
dprintf("DirectInput: Centering spring effects initialized with strength %d%%\n",
strength / 100);
}
HRESULT swdc_di_dev_poll(

View File

@ -11,7 +11,7 @@ union swdc_di_state {
};
HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out);
void swdc_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out, uint16_t strength);
HRESULT swdc_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,

View File

@ -70,6 +70,7 @@ static uint8_t swdc_di_wheel_yellow;
static bool swdc_di_use_pedals;
static bool swdc_di_reverse_brake_axis;
static bool swdc_di_reverse_accel_axis;
static uint16_t swdc_di_center_spring_strength;
HRESULT swdc_di_init(
const struct swdc_di_config *cfg,
@ -168,7 +169,9 @@ HRESULT swdc_di_init(
return hr;
}
swdc_di_dev_start_fx(swdc_di_dev, &swdc_di_fx);
// Convert the strength from 0-100 to 0-10000 for DirectInput
swdc_di_dev_start_fx(swdc_di_dev, &swdc_di_fx,
swdc_di_center_spring_strength * 100);
if (cfg->pedals_name[0] != L'\0') {
hr = IDirectInput8_EnumDevices(
@ -320,6 +323,16 @@ static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg)
swdc_di_reverse_brake_axis = cfg->reverse_brake_axis;
swdc_di_reverse_accel_axis = cfg->reverse_accel_axis;
// FFB configuration
if (cfg->center_spring_strength < 0 || cfg->center_spring_strength > 100) {
dprintf("Wheel: Invalid center spring strength: %i\n", cfg->center_spring_strength);
return E_INVALIDARG;
}
swdc_di_center_spring_strength = cfg->center_spring_strength;
return S_OK;
}