From 8ebdf67d6e06fae3d613fdf60265c8311f3de077 Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Tue, 19 Dec 2023 12:43:26 +0100 Subject: [PATCH] chuni/chusan: added LED output to DLLs (will break most DLLs) Credits: somewhatlurker, skogaby https://dev.s-ul.net/skogaby/segatools/-/blob/ongeki-15093/ --- board/led15093.c | 31 +++++++- board/led15093.h | 7 +- chunihook/chuni-dll.c | 6 ++ chunihook/chuni-dll.h | 2 + chunihook/chunihook.def | 2 + chunihook/dllmain.c | 4 +- chuniio/chuniio.c | 84 ++++++-------------- chuniio/chuniio.h | 18 +++++ chuniio/config.c | 23 +++++- chuniio/config.h | 13 +++- chuniio/leddata.h | 22 ++++++ chuniio/ledoutput.c | 133 +++++++++++++++++++++++++++++++ chuniio/ledoutput.h | 19 +++++ chuniio/meson.build | 7 ++ chuniio/pipeimpl.c | 160 ++++++++++++++++++++++++++++++++++++++ chuniio/pipeimpl.h | 14 ++++ chuniio/serialimpl.c | 99 +++++++++++++++++++++++ chuniio/serialimpl.h | 15 ++++ chusanhook/chuni-dll.c | 6 ++ chusanhook/chuni-dll.h | 2 + chusanhook/chusanhook.def | 2 + chusanhook/dllmain.c | 3 +- chusanhook/led1509306.h | 16 ---- dist/chuni/segatools.ini | 71 ++++++++++++++--- dist/chusan/segatools.ini | 45 ++++++++++- fgohook/dllmain.c | 4 +- fgohook/fgo-dll.c | 6 ++ fgohook/fgo-dll.h | 2 + fgohook/fgohook.def | 2 + fgoio/fgoio.c | 10 +++ fgoio/fgoio.h | 15 ++++ mu3hook/config.h | 4 +- mu3hook/dllmain.c | 3 + 33 files changed, 743 insertions(+), 107 deletions(-) create mode 100644 chuniio/leddata.h create mode 100644 chuniio/ledoutput.c create mode 100644 chuniio/ledoutput.h create mode 100644 chuniio/pipeimpl.c create mode 100644 chuniio/pipeimpl.h create mode 100644 chuniio/serialimpl.c create mode 100644 chuniio/serialimpl.h delete mode 100644 chusanhook/led1509306.h diff --git a/board/led15093.c b/board/led15093.c index c18b1b0..4224303 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,19 @@ 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(); + + if (res != 0) { + dprintf("LED 15093: Backend error, LED board disconnected: " + "%d\n", + res); + + return E_FAIL; + } + } hr = uart_handle_irp(boarduart, irp); @@ -624,6 +644,9 @@ static HRESULT led15093_req_set_led(int board, const struct led15093_req_set_led memcpy(v->led, req->data, req->hdr.nbytes - 1); + // Return the current LED data, remove const qualifier + set_leds(board, (uint8_t *) req->data); + if (!v->enable_response) return S_OK; diff --git a/board/led15093.h b/board/led15093.h index 4e1b7f4..ff2f612 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 int (*io_led_init_t)(); +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..72393e9 100644 --- a/chunihook/chuni-dll.c +++ b/chunihook/chuni-dll.c @@ -30,6 +30,12 @@ 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), } }; diff --git a/chunihook/chuni-dll.h b/chunihook/chuni-dll.h index ecd88ee..034b204 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); + int (*led_init)(); + 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..69cf559 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,7 +97,8 @@ static DWORD CALLBACK chuni_pre_startup(void) goto fail; } - hr = led15093_hook_init(&chuni_hook_cfg.led15093, 10, 2, 2, 1); + 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; diff --git a/chuniio/chuniio.c b/chuniio/chuniio.c index 5df741f..32481f1 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,7 +19,6 @@ 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) { @@ -28,7 +28,17 @@ uint16_t chuni_io_get_api_version(void) 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; } @@ -93,7 +103,7 @@ void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams) 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; } + +int chuni_io_led_init() +{ + 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..5fc7ebe 100644 --- a/chuniio/chuniio.h +++ b/chuniio/chuniio.h @@ -145,3 +145,21 @@ 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. */ + +int chuni_io_led_init(); + +/* Update the RGB LEDs. rgb is a pointer to an array of 66 * 3 = 198 + bytes. The majority of these are for the marquee display, but the final + LEDs are for the side partitions. + + Chunithm uses two chains/boards. One is on the left side and one on the + right side of the cab. Exact layout is TBD. */ + +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..84509bc --- /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; + +int 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..82e54b1 --- /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; +int 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..fabf499 100644 --- a/chuniio/meson.build +++ b/chuniio/meson.build @@ -9,5 +9,12 @@ chuniio_lib = static_library( '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..3a100ac 100644 --- a/chusanhook/chuni-dll.c +++ b/chusanhook/chuni-dll.c @@ -30,6 +30,12 @@ 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), } }; diff --git a/chusanhook/chuni-dll.h b/chusanhook/chuni-dll.h index ecd88ee..034b204 100644 --- a/chusanhook/chuni-dll.h +++ b/chusanhook/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); + int (*led_init)(); + void (*led_set_leds)(uint8_t board, uint8_t *rgb); }; struct chuni_dll_config { diff --git a/chusanhook/chusanhook.def b/chusanhook/chusanhook.def index 5039c7d..66ae5ec 100644 --- a/chusanhook/chusanhook.def +++ b/chusanhook/chusanhook.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/chusanhook/dllmain.c b/chusanhook/dllmain.c index 61c7f47..ee8bc77 100644 --- a/chusanhook/dllmain.c +++ b/chusanhook/dllmain.c @@ -129,7 +129,8 @@ static DWORD CALLBACK chusan_pre_startup(void) } } - hr = led15093_hook_init(&chusan_hook_cfg.led15093, first_port, 2, 2, 1); + 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; 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..45e2df9 100644 --- a/dist/chusan/segatools.ini +++ b/dist/chusan/segatools.ini @@ -63,16 +63,53 @@ 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. ; 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..05dd997 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); + int (*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..5ffbd72 100644 --- a/fgoio/fgoio.c +++ b/fgoio/fgoio.c @@ -139,3 +139,13 @@ void fgo_io_get_analogs(int16_t *stick_x, int16_t *stick_y) *stick_y = fgo_stick_y; } } + +int fgo_io_led_init() +{ + return 0; +} + +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..6e526f5 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. */ + +int fgo_io_led_init(); + +/* Update the RGB LEDs. + + Exact layout is TBD. */ + +void fgo_io_led_set_leds(uint8_t board, uint8_t *rgb); 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);