diff --git a/board/led15093.c b/board/led15093.c index c18b1b0..606ce27 100644 --- a/board/led15093.c +++ b/board/led15093.c @@ -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; @@ -229,6 +236,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); @@ -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) diff --git a/board/led15093.h b/board/led15093.h index 4e1b7f4..869a11b 100644 --- a/board/led15093.h +++ b/board/led15093.h @@ -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); diff --git a/chunihook/chuni-dll.c b/chunihook/chuni-dll.c index c98a9de..5867f66 100644 --- a/chunihook/chuni-dll.c +++ b/chunihook/chuni-dll.c @@ -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); } diff --git a/chunihook/chuni-dll.h b/chunihook/chuni-dll.h index ecd88ee..8341b48 100644 --- a/chunihook/chuni-dll.h +++ b/chunihook/chuni-dll.h @@ -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 { diff --git a/chunihook/chunihook.def b/chunihook/chunihook.def index 2a90ab6..517a10b 100644 --- a/chunihook/chunihook.def +++ b/chunihook/chunihook.def @@ -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 diff --git a/chunihook/dllmain.c b/chunihook/dllmain.c index d598672..1a3fb57 100644 --- a/chunihook/dllmain.c +++ b/chunihook/dllmain.c @@ -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); diff --git a/chuniio/chu2to3.c b/chuniio/chu2to3.c new file mode 100644 index 0000000..550583b --- /dev/null +++ b/chuniio/chu2to3.c @@ -0,0 +1,358 @@ +#include + +#include +#include +#include +#include + +#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); + } +} diff --git a/chuniio/chu2to3.h b/chuniio/chu2to3.h new file mode 100644 index 0000000..de6e711 --- /dev/null +++ b/chuniio/chu2to3.h @@ -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 + +#include +#include + +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); diff --git a/chuniio/chuniio.c b/chuniio/chuniio.c index 5df741f..27fe7b0 100644 --- a/chuniio/chuniio.c +++ b/chuniio/chuniio.c @@ -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; } @@ -81,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) @@ -111,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) @@ -158,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) @@ -211,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); +} diff --git a/chuniio/chuniio.h b/chuniio/chuniio.h index 08a30ca..e149a52 100644 --- a/chuniio/chuniio.h +++ b/chuniio/chuniio.h @@ -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 @@ -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); diff --git a/chuniio/config.c b/chuniio/config.c index 07d78ad..0ce06a2 100644 --- a/chuniio/config.c +++ b/chuniio/config.c @@ -57,7 +57,24 @@ void chuni_io_config_load( filename); } - GetPrivateProfileStringW(L"slider", L"ledport", L"COM5", 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); } diff --git a/chuniio/config.h b/chuniio/config.h index b615804..fc8884c 100644 --- a/chuniio/config.h +++ b/chuniio/config.h @@ -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( diff --git a/chuniio/leddata.h b/chuniio/leddata.h new file mode 100644 index 0000000..0764c9f --- /dev/null +++ b/chuniio/leddata.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#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}; \ No newline at end of file diff --git a/chuniio/ledoutput.c b/chuniio/ledoutput.c new file mode 100644 index 0000000..ab0187f --- /dev/null +++ b/chuniio/ledoutput.c @@ -0,0 +1,133 @@ +#include + +#include +#include +#include + +#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); + } + } +} diff --git a/chuniio/ledoutput.h b/chuniio/ledoutput.h new file mode 100644 index 0000000..33a4180 --- /dev/null +++ b/chuniio/ledoutput.h @@ -0,0 +1,19 @@ +/* + LED output functions + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#include +#include + +#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); diff --git a/chuniio/meson.build b/chuniio/meson.build index 9e61229..864aa87 100644 --- a/chuniio/meson.build +++ b/chuniio/meson.build @@ -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' ], ) diff --git a/chuniio/pipeimpl.c b/chuniio/pipeimpl.c new file mode 100644 index 0000000..972d84d --- /dev/null +++ b/chuniio/pipeimpl.c @@ -0,0 +1,160 @@ +#include + +#include +#include +#include + +#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); +} diff --git a/chuniio/pipeimpl.h b/chuniio/pipeimpl.h new file mode 100644 index 0000000..4f7a268 --- /dev/null +++ b/chuniio/pipeimpl.h @@ -0,0 +1,14 @@ +/* + Pipe implementation for chuniio + + Credits: + somewhatlurker, skogaby +*/ +#pragma once + +#include + +#include "chuniio/leddata.h" + +HRESULT led_pipe_init(); +void led_pipe_update(struct _chuni_led_data_buf_t* data); diff --git a/chuniio/serialimpl.c b/chuniio/serialimpl.c new file mode 100644 index 0000000..57ebb7f --- /dev/null +++ b/chuniio/serialimpl.c @@ -0,0 +1,99 @@ +#include + +#include +#include +#include + +#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); +} diff --git a/chuniio/serialimpl.h b/chuniio/serialimpl.h new file mode 100644 index 0000000..f8f276b --- /dev/null +++ b/chuniio/serialimpl.h @@ -0,0 +1,15 @@ +/* + Serial LED implementation for chuniio + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#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); diff --git a/chusanhook/chuni-dll.c b/chusanhook/chuni-dll.c index c946043..e02b60b 100644 --- a/chusanhook/chuni-dll.c +++ b/chusanhook/chuni-dll.c @@ -3,6 +3,7 @@ #include #include +#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); } diff --git a/chusanhook/chuni-dll.h b/chusanhook/chuni-dll.h index ecd88ee..bfc415c 100644 --- a/chusanhook/chuni-dll.h +++ b/chusanhook/chuni-dll.h @@ -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; diff --git a/chusanhook/chusanhook.def b/chusanhook/chusanhook.def index 5039c7d..ea786f5 100644 --- a/chusanhook/chusanhook.def +++ b/chusanhook/chusanhook.def @@ -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 diff --git a/chusanhook/config.c b/chusanhook/config.c index 0361e98..d71ad71 100644 --- a/chusanhook/config.c +++ b/chusanhook/config.c @@ -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) - #if defined(ENV32BIT) - GetPrivateProfileStringW( - L"chuniio", - L"path32", - L"", - cfg->path, - _countof(cfg->path), - filename); + 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); #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) diff --git a/chusanhook/dllmain.c b/chusanhook/dllmain.c index 61c7f47..e8fce32 100644 --- a/chusanhook/dllmain.c +++ b/chusanhook/dllmain.c @@ -100,7 +100,7 @@ static DWORD CALLBACK chusan_pre_startup(void) } bool *dipsw = &chusan_hook_cfg.platform.dipsw.dipsw[0]; - bool *is_sp = dipsw + 2; + bool is_cvt = dipsw[2]; for (int i = 0; i < 3; i++) { switch (i) { @@ -113,15 +113,15 @@ static DWORD CALLBACK chusan_pre_startup(void) break; case 2: - dprintf("DipSw: Cab Type: %s\n", is_sp ? "SP" : "CVT"); + dprintf("DipSw: Cab Type: %s\n", is_cvt ? "CVT" : "SP"); break; } } - unsigned int first_port = is_sp ? 20 : 2; + unsigned int first_port = is_cvt ? 2 : 20; - if (is_sp) { + if (!is_cvt) { hr = vfd_hook_init(2); if (FAILED(hr)) { @@ -129,13 +129,19 @@ static DWORD CALLBACK chusan_pre_startup(void) } } - hr = led15093_hook_init(&chusan_hook_cfg.led15093, first_port, 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(&chusan_hook_cfg.led15093, + chuni_dll.led_init, chuni_dll.led_set_leds, first_port, 2, 2, 1); - if (FAILED(hr)) { - goto fail; + if (FAILED(hr)) { + goto fail; + } } - hr = sg_reader_hook_init(&chusan_hook_cfg.aime, 4, is_sp ? 3: 2, chusan_hook_mod); + hr = sg_reader_hook_init(&chusan_hook_cfg.aime, 4, is_cvt ? 2: 3, chusan_hook_mod); if (FAILED(hr)) { goto fail; diff --git a/chusanhook/led1509306.h b/chusanhook/led1509306.h deleted file mode 100644 index 03dc62a..0000000 --- a/chusanhook/led1509306.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -#include - -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); diff --git a/dist/chuni/segatools.ini b/dist/chuni/segatools.ini index ba75d61..57524f8 100644 --- a/dist/chuni/segatools.ini +++ b/dist/chuni/segatools.ini @@ -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,12 +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 ... - -; Enable slider LED serial output. This follows OpeNITHM Serial LED Protocol. -; eg. COM5 -;ledport= +;cell31=0x53 +;cell32=0x53 diff --git a/dist/chusan/segatools.ini b/dist/chusan/segatools.ini index 44d1a96..3531bdf 100644 --- a/dist/chusan/segatools.ini +++ b/dist/chusan/segatools.ini @@ -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 ; ----------------------------------------------------------------------------- diff --git a/fgohook/dllmain.c b/fgohook/dllmain.c index fcac111..2e8619e 100644 --- a/fgohook/dllmain.c +++ b/fgohook/dllmain.c @@ -3,6 +3,7 @@ #include #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; diff --git a/fgohook/fgo-dll.c b/fgohook/fgo-dll.c index 2f4ef7d..dce25ab 100644 --- a/fgohook/fgo-dll.c +++ b/fgohook/fgo-dll.c @@ -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), } }; diff --git a/fgohook/fgo-dll.h b/fgohook/fgo-dll.h index 41d6e92..22bf02d 100644 --- a/fgohook/fgo-dll.h +++ b/fgohook/fgo-dll.h @@ -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 { diff --git a/fgohook/fgohook.def b/fgohook/fgohook.def index 04bf2f0..2f4c1c6 100644 --- a/fgohook/fgohook.def +++ b/fgohook/fgohook.def @@ -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 diff --git a/fgoio/fgoio.c b/fgoio/fgoio.c index c6bd88f..95c86d9 100644 --- a/fgoio/fgoio.c +++ b/fgoio/fgoio.c @@ -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; +} \ No newline at end of file diff --git a/fgoio/fgoio.h b/fgoio/fgoio.h index f302dfa..5a815a7 100644 --- a/fgoio/fgoio.h +++ b/fgoio/fgoio.h @@ -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); diff --git a/meson.build b/meson.build index c4936be..a0db24a 100644 --- a/meson.build +++ b/meson.build @@ -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') diff --git a/mu3hook/config.h b/mu3hook/config.h index 1273983..623397c 100644 --- a/mu3hook/config.h +++ b/mu3hook/config.h @@ -3,7 +3,7 @@ #include #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; }; diff --git a/mu3hook/dllmain.c b/mu3hook/dllmain.c index b867345..c27fd4d 100644 --- a/mu3hook/dllmain.c +++ b/mu3hook/dllmain.c @@ -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);