From 9fe98b227b910d38e10d301fbba26eb775e666f5 Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Sun, 12 May 2024 22:02:53 +0200 Subject: [PATCH] mu3: added lights hook --- board/io4.c | 8 +- board/io4.h | 3 + chuniio/config.c | 10 +-- chuniio/config.h | 9 +-- chuniio/ledoutput.c | 18 ++--- chuniio/pipeimpl.h | 1 + dist/chuni/segatools.ini | 28 +++---- dist/chusan/segatools.ini | 38 ++++----- dist/mu3/segatools.ini | 48 ++++++++++++ jvs/jvs-cmd.h | 6 ++ mu3hook/io4.c | 46 +++++++++++ mu3io/config.c | 23 ++++++ mu3io/config.h | 11 +++ mu3io/leddata.h | 23 ++++++ mu3io/ledoutput.c | 130 +++++++++++++++++++++++++++++++ mu3io/ledoutput.h | 20 +++++ mu3io/meson.build | 11 ++- mu3io/mu3io.c | 16 +++- mu3io/pipeimpl.c | 160 ++++++++++++++++++++++++++++++++++++++ mu3io/pipeimpl.h | 15 ++++ mu3io/serialimpl.c | 88 +++++++++++++++++++++ mu3io/serialimpl.h | 15 ++++ 22 files changed, 669 insertions(+), 58 deletions(-) create mode 100644 mu3io/leddata.h create mode 100644 mu3io/ledoutput.c create mode 100644 mu3io/ledoutput.h create mode 100644 mu3io/pipeimpl.c create mode 100644 mu3io/pipeimpl.h create mode 100644 mu3io/serialimpl.c create mode 100644 mu3io/serialimpl.h diff --git a/board/io4.c b/board/io4.c index 912f733..5c7d822 100644 --- a/board/io4.c +++ b/board/io4.c @@ -48,7 +48,7 @@ static_assert(sizeof(struct io4_report_in) == 0x40, "IO4 IN report size"); struct io4_report_out { uint8_t report_id; uint8_t cmd; - uint8_t payload[62]; + uint8_t payload[IO4_REPORT_OUT_PAYLOAD_LEN]; }; static_assert(sizeof(struct io4_report_out) == 0x40, "IO4 OUT report size"); @@ -223,7 +223,11 @@ static HRESULT io4_handle_write(struct irp *irp) return S_OK; case IO4_CMD_SET_GENERAL_OUTPUT: - dprintf("USB I/O: GPIO Out\n"); + // dprintf("USB I/O: GPIO Out\n"); + + if (io4_ops->write_gpio != NULL) { + return io4_ops->write_gpio(out.payload, IO4_REPORT_OUT_PAYLOAD_LEN); + } return S_OK; diff --git a/board/io4.h b/board/io4.h index 1a6cc05..914c045 100644 --- a/board/io4.h +++ b/board/io4.h @@ -4,6 +4,8 @@ #include +#define IO4_REPORT_OUT_PAYLOAD_LEN 62 + enum { /* System buttons in button[0] */ @@ -24,6 +26,7 @@ struct io4_state { struct io4_ops { HRESULT (*poll)(void *ctx, struct io4_state *state); + HRESULT (*write_gpio)(uint8_t* payload, size_t len); }; HRESULT io4_hook_init( diff --git a/chuniio/config.c b/chuniio/config.c index c41ae99..aaa048e 100644 --- a/chuniio/config.c +++ b/chuniio/config.c @@ -57,11 +57,11 @@ void chuni_io_config_load( filename); } - cfg->led_output_pipe = GetPrivateProfileIntW(L"led", L"cabLedOutputPipe", 1, filename); - cfg->led_output_serial = GetPrivateProfileIntW(L"led", L"cabLedOutputSerial", 0, filename); + cfg->cab_led_output_pipe = GetPrivateProfileIntW(L"led", L"cabLedOutputPipe", 1, filename); + cfg->cab_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->controller_led_output_pipe = GetPrivateProfileIntW(L"led", L"controllerLedOutputPipe", 1, filename); + cfg->controller_led_output_serial = GetPrivateProfileIntW(L"led", L"controllerLedOutputSerial", 0, filename); cfg->led_serial_baud = GetPrivateProfileIntW(L"led", L"serialBaud", 921600, filename); @@ -70,7 +70,7 @@ void chuni_io_config_load( L"serialPort", L"COM5", port_input, - 6, + _countof(port_input), filename); // Sanitize the output path. If it's a serial COM port, it needs to be prefixed diff --git a/chuniio/config.h b/chuniio/config.h index fc8884c..3eb8223 100644 --- a/chuniio/config.h +++ b/chuniio/config.h @@ -12,16 +12,15 @@ struct chuni_io_config { uint8_t vk_cell[32]; // Which ways to output LED information are enabled - bool led_output_pipe; - bool led_output_serial; + bool cab_led_output_pipe; + bool cab_led_output_serial; - bool slider_led_output_pipe; - bool slider_led_output_serial; + bool controller_led_output_pipe; + bool controller_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/ledoutput.c b/chuniio/ledoutput.c index ab0187f..8cac5a3 100644 --- a/chuniio/ledoutput.c +++ b/chuniio/ledoutput.c @@ -48,15 +48,15 @@ HRESULT led_output_init(struct chuni_io_config* const cfg) 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; + any_outputs_enabled = config->cab_led_output_pipe || config->controller_led_output_pipe + || config->cab_led_output_serial || config->controller_led_output_serial; - if (config->led_output_pipe || config->slider_led_output_pipe) + if (config->cab_led_output_pipe || config->controller_led_output_pipe) { led_pipe_init(); // don't really care about errors here tbh } - if (config->led_output_serial || config->slider_led_output_serial) + if (config->cab_led_output_serial || config->controller_led_output_serial) { led_serial_init(config->led_serial_port, config->led_serial_baud); } @@ -106,13 +106,13 @@ void led_output_update(uint8_t board, const byte* rgb) if (board < 2) { - // billboard - if (config->led_output_pipe) + // billboard (cab) + if (config->cab_led_output_pipe) { led_pipe_update(escaped_data); } - if (config->led_output_serial) + if (config->cab_led_output_serial) { led_serial_update(escaped_data); } @@ -120,12 +120,12 @@ void led_output_update(uint8_t board, const byte* rgb) else { // slider - if (config->slider_led_output_pipe) + if (config->controller_led_output_pipe) { led_pipe_update(escaped_data); } - if (config->slider_led_output_serial) + if (config->controller_led_output_serial) { led_serial_update(escaped_data); } diff --git a/chuniio/pipeimpl.h b/chuniio/pipeimpl.h index 4f7a268..c95801f 100644 --- a/chuniio/pipeimpl.h +++ b/chuniio/pipeimpl.h @@ -4,6 +4,7 @@ Credits: somewhatlurker, skogaby */ + #pragma once #include diff --git a/dist/chuni/segatools.ini b/dist/chuni/segatools.ini index 5497643..87d923d 100644 --- a/dist/chuni/segatools.ini +++ b/dist/chuni/segatools.ini @@ -63,20 +63,6 @@ framed=1 ; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) monitor=0 -; ----------------------------------------------------------------------------- -; Custom IO settings -; ----------------------------------------------------------------------------- - -[aimeio] -; To use a custom card reader IO DLL enter its path here. -; Leave empty if you want to use Segatools built-in keyboard input. -path= - -[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= - ; ----------------------------------------------------------------------------- ; LED settings ; ----------------------------------------------------------------------------- @@ -122,6 +108,20 @@ controllerLedOutputSerial=0 ; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +[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= + ; ----------------------------------------------------------------------------- ; Input settings ; ----------------------------------------------------------------------------- diff --git a/dist/chusan/segatools.ini b/dist/chusan/segatools.ini index a5e3d51..f41f630 100644 --- a/dist/chusan/segatools.ini +++ b/dist/chusan/segatools.ini @@ -88,25 +88,6 @@ framed=0 ; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) monitor=0 -; ----------------------------------------------------------------------------- -; Custom IO settings -; ----------------------------------------------------------------------------- - -[aimeio] -; To use a custom card reader IO DLL (x64) enter its path here. -; Leave empty if you want to use Segatools built-in keyboard input. -path= - -[chuniio] -; 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= - ; ----------------------------------------------------------------------------- ; LED settings ; ----------------------------------------------------------------------------- @@ -152,6 +133,25 @@ controllerLedOutputSerial=0 ; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[aimeio] +; To use a custom card reader IO DLL (x64) enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +[chuniio] +; 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= + ; ----------------------------------------------------------------------------- ; Input settings ; ----------------------------------------------------------------------------- diff --git a/dist/mu3/segatools.ini b/dist/mu3/segatools.ini index 1da77e9..d7b8166 100644 --- a/dist/mu3/segatools.ini +++ b/dist/mu3/segatools.ini @@ -80,6 +80,54 @@ enable=1 ; modding frameworks such as BepInEx. targetAssembly= +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[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\ongeki_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 61 LEDs: +; [0]-[1]: left side button +; [2]-[8]: left pillar lower LEDs +; [9]-[17]: left pillar center LEDs +; [18]-[24]: left pillar upper LEDs +; [25]-[35]: billboard LEDs +; [36]-[42]: right pillar upper LEDs +; [43]-[51]: right pillar center LEDs +; [52]-[58]: right pillar lower LEDs +; [59]-[60]: right side button +; +; Board 1 has 6 LEDs: +; [0]-[5]: 3 left and 3 right controller buttons +; + ; ----------------------------------------------------------------------------- ; Custom IO settings ; ----------------------------------------------------------------------------- diff --git a/jvs/jvs-cmd.h b/jvs/jvs-cmd.h index 0197c9f..65ad599 100644 --- a/jvs/jvs-cmd.h +++ b/jvs/jvs-cmd.h @@ -9,6 +9,7 @@ enum { JVS_CMD_READ_SWITCHES = 0x20, JVS_CMD_READ_COIN = 0x21, JVS_CMD_READ_ANALOGS = 0x22, + JVS_CMD_READ_ROTARYS = 0x23, JVS_CMD_WRITE_GPIO = 0x32, JVS_CMD_RESET = 0xF0, JVS_CMD_ASSIGN_ADDR = 0xF1, @@ -32,6 +33,11 @@ struct jvs_req_read_analogs { uint8_t nanalogs; }; +struct jvs_req_read_rotarys { + uint8_t cmd; + uint8_t nrotarys; +}; + struct jvs_req_reset { uint8_t cmd; uint8_t unknown; diff --git a/mu3hook/io4.c b/mu3hook/io4.c index 9515b50..1af69e7 100644 --- a/mu3hook/io4.c +++ b/mu3hook/io4.c @@ -11,10 +11,13 @@ #include "util/dprintf.h" static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state); +static HRESULT mu3_io4_write_gpio(uint8_t* payload, size_t len); + static uint16_t coins; static const struct io4_ops mu3_io4_ops = { .poll = mu3_io4_poll, + .write_gpio = mu3_io4_write_gpio, }; HRESULT mu3_io4_hook_init(const struct io4_config *cfg) @@ -124,3 +127,46 @@ static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state) return S_OK; } + +static HRESULT mu3_io4_write_gpio(uint8_t* payload, size_t len) +{ + // Just fast fail if there aren't enough bytes in the payload + if (len < 3) + return S_OK; + + // This command is used for lights in Ongeki, but it only contains button lights, + // and only in the first 3 bytes of the payload; everything else is padding to + // make the payload 62 bytes. The rest of the cabinet lights and the side button + // lights are handled separately, by the 15093 lights controller. + uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 | + (uint8_t)(payload[1]) << 16 | + (uint8_t)(payload[2]) << 8); + + // Since Sega uses an odd ordering for the first part of the bitfield, + // let's normalize the data and just send over bytes for the receiver + // to interpret as RGB values. + uint8_t rgb_out[6 * 3] = { + lights_data & MU3_IO_LED_L1_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L1_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L1_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L2_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L2_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L2_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L3_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L3_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L3_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R1_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R1_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R1_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R2_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R2_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R2_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R3_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R3_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R3_B ? 0xFF : 0x00, + }; + + mu3_io_led_set_colors(1, rgb_out); + + return S_OK; +} diff --git a/mu3io/config.c b/mu3io/config.c index 612c860..fd4b0a7 100644 --- a/mu3io/config.c +++ b/mu3io/config.c @@ -14,6 +14,8 @@ void mu3_io_config_load( assert(cfg != NULL); assert(filename != NULL); + wchar_t output_path_input[6]; + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename); cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); @@ -30,4 +32,25 @@ void mu3_io_config_load( cfg->vk_right_3 = GetPrivateProfileIntW(L"io4", L"right3", 'L', filename); cfg->vk_left_menu = GetPrivateProfileIntW(L"io4", L"leftMenu", 'U', filename); cfg->vk_right_menu = GetPrivateProfileIntW(L"io4", L"rightMenu", 'O', filename); + + cfg->cab_led_output_pipe = GetPrivateProfileIntW(L"led", L"cabLedOutputPipe", 1, filename); + cfg->cab_led_output_serial = GetPrivateProfileIntW(L"led", L"cabLedOutputSerial", 0, filename); + + cfg->controller_led_output_pipe = GetPrivateProfileIntW(L"led", L"controllerLedOutputPipe", 1, filename); + cfg->controller_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", + output_path_input, + _countof(output_path_input), + 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, MAX_PATH, output_path_input, MAX_PATH); } diff --git a/mu3io/config.h b/mu3io/config.h index ff74c3b..328a460 100644 --- a/mu3io/config.h +++ b/mu3io/config.h @@ -22,6 +22,17 @@ struct mu3_io_config { uint8_t vk_right_3; uint8_t vk_left_menu; uint8_t vk_right_menu; + + // Which ways to output LED information are enabled + bool cab_led_output_pipe; + bool cab_led_output_serial; + + bool controller_led_output_pipe; + bool controller_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 mu3_io_config_load( diff --git a/mu3io/leddata.h b/mu3io/leddata.h new file mode 100644 index 0000000..1dcf42d --- /dev/null +++ b/mu3io/leddata.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +#define LED_PACKET_FRAMING 0xE0 +#define LED_PACKET_ESCAPE 0xD0 +#define LED_NUM_MAX 66 +#define LED_BOARDS_TOTAL 2 +#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 button and cab LEDs +struct _ongeki_led_data_buf_t { + byte framing; // Sync byte + uint8_t board; // LED output the data is for (0: cab, 1: control deck) + byte data[LED_OUTPUT_DATA_SIZE_MAX]; // Buffer for LEDs + byte data_len; // How many bytes to output from the buffer +}; + +static byte ongeki_led_board_data_lens[LED_BOARDS_TOTAL] = {9*3, 6*3}; diff --git a/mu3io/ledoutput.c b/mu3io/ledoutput.c new file mode 100644 index 0000000..9c20d1a --- /dev/null +++ b/mu3io/ledoutput.c @@ -0,0 +1,130 @@ +#include + +#include +#include +#include + +#include "mu3io/config.h" +#include "mu3io/leddata.h" +#include "mu3io/ledoutput.h" +#include "mu3io/pipeimpl.h" +#include "mu3io/serialimpl.h" + +static struct _ongeki_led_data_buf_t mu3_led_unescaped_buf[LED_BOARDS_TOTAL]; +static struct _ongeki_led_data_buf_t mu3_led_escaped_buf[LED_BOARDS_TOTAL]; + +static bool mu3_led_output_is_init = false; +static struct mu3_io_config* mu3_io_config; +static bool mu3_led_any_outputs_enabled; + +HANDLE mu3_led_init_mutex; + +HRESULT mu3_led_output_init(struct mu3_io_config* const cfg) +{ + DWORD dwWaitResult = WaitForSingleObject(mu3_led_init_mutex, INFINITE); + if (dwWaitResult == WAIT_FAILED) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + else if (dwWaitResult != WAIT_OBJECT_0) + { + return E_FAIL; + } + + if (!mu3_led_output_is_init) + { + mu3_io_config = cfg; + + // Setup the framing bytes for the packets + for (int i = 0; i < LED_BOARDS_TOTAL; i++) { + mu3_led_unescaped_buf[i].framing = LED_PACKET_FRAMING; + mu3_led_unescaped_buf[i].board = i; + mu3_led_unescaped_buf[i].data_len = ongeki_led_board_data_lens[i]; + + mu3_led_escaped_buf[i].framing = LED_PACKET_FRAMING; + mu3_led_escaped_buf[i].board = i; + mu3_led_escaped_buf[i].data_len = ongeki_led_board_data_lens[i]; + } + + mu3_led_any_outputs_enabled = mu3_io_config->cab_led_output_pipe || mu3_io_config->controller_led_output_pipe + || mu3_io_config->cab_led_output_serial || mu3_io_config->controller_led_output_serial; + + if (mu3_io_config->cab_led_output_pipe || mu3_io_config->controller_led_output_pipe) + { + mu3_led_pipe_init(); // don't really care about errors here tbh + } + + if (mu3_io_config->cab_led_output_serial || mu3_io_config->controller_led_output_serial) + { + mu3_led_serial_init(mu3_io_config->led_serial_port, mu3_io_config->led_serial_baud); + } + } + + mu3_led_output_is_init = true; + + ReleaseMutex(mu3_led_init_mutex); + return S_OK; +} + +struct _ongeki_led_data_buf_t* escape_led_data(struct _ongeki_led_data_buf_t* unescaped) +{ + struct _ongeki_led_data_buf_t* out_struct = &mu3_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 mu3_led_output_update(int board, const byte* rgb) +{ + if (board < 0 || board > 1 || !mu3_led_any_outputs_enabled) + { + return; + } + + memcpy(mu3_led_unescaped_buf[board].data, rgb, mu3_led_unescaped_buf[board].data_len); + struct _ongeki_led_data_buf_t* escaped_data = escape_led_data(&mu3_led_unescaped_buf[board]); + + if (board == 0) + { + // cab + if (mu3_io_config->cab_led_output_pipe) + { + mu3_led_pipe_update(escaped_data); + } + + if (mu3_io_config->cab_led_output_serial) + { + mu3_led_serial_update(escaped_data); + } + } + else + { + // slider + if (mu3_io_config->controller_led_output_pipe) + { + mu3_led_pipe_update(escaped_data); + } + + if (mu3_io_config->controller_led_output_serial) + { + mu3_led_serial_update(escaped_data); + } + } +} diff --git a/mu3io/ledoutput.h b/mu3io/ledoutput.h new file mode 100644 index 0000000..eb9e810 --- /dev/null +++ b/mu3io/ledoutput.h @@ -0,0 +1,20 @@ +/* + LED output functions + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#include +#include + +#include "mu3io/config.h" + +extern HANDLE mu3_led_init_mutex; + +HRESULT mu3_led_output_init(struct mu3_io_config* const cfg); +void mu3_led_output_update(int board, const byte* rgb); diff --git a/mu3io/meson.build b/mu3io/meson.build index 0b509bd..3a2b571 100644 --- a/mu3io/meson.build +++ b/mu3io/meson.build @@ -8,9 +8,16 @@ mu3io_lib = static_library( xinput_lib, ], sources : [ - 'mu3io.c', - 'mu3io.h', 'config.c', 'config.h', + 'leddata.h', + 'ledoutput.c', + 'ledoutput.h', + 'mu3io.c', + 'mu3io.h', + 'pipeimpl.c', + 'pipeimpl.h', + 'serialimpl.c', + 'serialimpl.h' ], ) diff --git a/mu3io/mu3io.c b/mu3io/mu3io.c index 918c22d..2658612 100644 --- a/mu3io/mu3io.c +++ b/mu3io/mu3io.c @@ -6,6 +6,8 @@ #include "mu3io/mu3io.h" #include "mu3io/config.h" +#include "mu3io/ledoutput.h" + #include "util/dprintf.h" static uint8_t mu3_opbtn; @@ -32,7 +34,17 @@ HRESULT mu3_io_init(void) dprintf("XInput: Mouse lever emulation : %i\n", mu3_io_cfg.use_mouse); dprintf("XInput: --- End configuration ---\n"); - return S_OK; + mu3_led_init_mutex = CreateMutex( + NULL, // default security attributes + FALSE, // initially not owned + NULL); // unnamed mutex + + if (mu3_led_init_mutex == NULL) + { + return E_FAIL; + } + + return mu3_led_output_init(&mu3_io_cfg); } HRESULT mu3_io_poll(void) @@ -203,5 +215,5 @@ HRESULT mu3_io_led_init(void) void mu3_io_led_set_colors(uint8_t board, uint8_t *rgb) { - return; + mu3_led_output_update(board, rgb); } diff --git a/mu3io/pipeimpl.c b/mu3io/pipeimpl.c new file mode 100644 index 0000000..7010601 --- /dev/null +++ b/mu3io/pipeimpl.c @@ -0,0 +1,160 @@ +#include + +#include +#include +#include + +#include "mu3io/leddata.h" +#include "mu3io/pipeimpl.h" + +static bool mu3_pipe_update[LED_BOARDS_TOTAL]; + +// incoming data is copied into these to ensure it isn't written during output +static struct _ongeki_led_data_buf_t mu3_pipe_write_buf[LED_BOARDS_TOTAL]; +static HANDLE mu3_pipe_write_mutex; + +static HRESULT mu3_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 mu3_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 mu3_io_led_pipe_thread_proc(void *ctx) +{ + HANDLE hPipe; + LPCWSTR lpszPipename = L"\\\\.\\pipe\\ongeki_led"; + + while (true) + { + hPipe = INVALID_HANDLE_VALUE; + + if (mu3_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(mu3_pipe_write_mutex, INFINITE) != WAIT_OBJECT_0) + { + continue; + } + + for (int i = 0; i < LED_BOARDS_TOTAL; i++) { + if (mu3_pipe_update[i]) + { + HRESULT result = mu3_pipe_write( + hPipe, + &mu3_pipe_write_buf[i], + LED_OUTPUT_HEADER_SIZE + mu3_pipe_write_buf[i].data_len); + + if (result != S_OK) + { + //if (result == E_ABORT) + //{ + fConnected = false; + //} + break; + } + + mu3_pipe_update[i] = false; + } + } + + ReleaseMutex(mu3_pipe_write_mutex); + } + + FlushFileBuffers(hPipe); + DisconnectNamedPipe(hPipe); + CloseHandle(hPipe); + } + + return 0; +} + +HRESULT mu3_led_pipe_init() +{ + mu3_pipe_write_mutex = CreateMutex( + NULL, // default security attributes + FALSE, // initially not owned + NULL); // unnamed mutex + + if (mu3_pipe_write_mutex == NULL) + { + return E_FAIL; + } + + // clear out update bools + for (int i = 0; i < LED_BOARDS_TOTAL; i++) { + mu3_pipe_update[i] = false; + } + + _beginthreadex( + NULL, + 0, + mu3_io_led_pipe_thread_proc, + 0, + 0, + NULL); + + return S_OK; +} + +void mu3_led_pipe_update(struct _ongeki_led_data_buf_t* data) +{ + if (data->board > 1) + { + return; + } + + if (WaitForSingleObject(mu3_pipe_write_mutex, INFINITE) != WAIT_OBJECT_0) + { + return; + } + + memcpy(&mu3_pipe_write_buf[data->board], data, sizeof(struct _ongeki_led_data_buf_t)); + mu3_pipe_update[data->board] = true; + + ReleaseMutex(mu3_pipe_write_mutex); +} diff --git a/mu3io/pipeimpl.h b/mu3io/pipeimpl.h new file mode 100644 index 0000000..cb5162a --- /dev/null +++ b/mu3io/pipeimpl.h @@ -0,0 +1,15 @@ +/* + Pipe implementation for chuniio + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#include "mu3io/leddata.h" + +HRESULT mu3_led_pipe_init(); +void mu3_led_pipe_update(struct _ongeki_led_data_buf_t* data); diff --git a/mu3io/serialimpl.c b/mu3io/serialimpl.c new file mode 100644 index 0000000..6ad07a6 --- /dev/null +++ b/mu3io/serialimpl.c @@ -0,0 +1,88 @@ +#include + +#include +#include +#include + +#include "mu3io/leddata.h" +#include "mu3io/serialimpl.h" + +#include "util/dprintf.h" + +static HANDLE mu3_serial_port; + +HRESULT mu3_led_serial_init(wchar_t led_com[12], DWORD baud) +{ + // Setup the serial communications + BOOL status; + + mu3_serial_port = CreateFileW(led_com, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + + if (mu3_serial_port == INVALID_HANDLE_VALUE) + { + dprintf("Ongeki Serial LEDs: Failed to open COM port (attempted on %S)\n", led_com); + return E_FAIL; + } + + DCB dcb_serial_params = { 0 }; + dcb_serial_params.DCBlength = sizeof(dcb_serial_params); + status = GetCommState(mu3_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(mu3_serial_port, &dcb_serial_params); + + COMMTIMEOUTS timeouts = { 0 }; + timeouts.ReadIntervalTimeout = 50; + timeouts.ReadTotalTimeoutConstant = 50; + timeouts.ReadTotalTimeoutMultiplier = 10; + timeouts.WriteTotalTimeoutConstant = 50; + timeouts.WriteTotalTimeoutMultiplier = 10; + + SetCommTimeouts(mu3_serial_port, &timeouts); + + if (!status) + { + return E_FAIL; + } + + return S_OK; +} + +void mu3_led_serial_update(struct _ongeki_led_data_buf_t* data) +{ + if (data->board > 1) + { + return; + } + + if (mu3_serial_port != INVALID_HANDLE_VALUE) + { + DWORD bytes_written = 0; + + BOOL status = WriteFile( + mu3_serial_port, + data, + LED_OUTPUT_HEADER_SIZE + data->data_len, + &bytes_written, + NULL); + + if (!status) + { + DWORD last_err = GetLastError(); + dprintf("Ongeki Serial LEDs: Serial port write failed -- %d\n", (int) last_err); + } + } + else + { + dprintf("Ongeki Serial LEDs: Invalid serial port handle\n"); + } +} diff --git a/mu3io/serialimpl.h b/mu3io/serialimpl.h new file mode 100644 index 0000000..151d042 --- /dev/null +++ b/mu3io/serialimpl.h @@ -0,0 +1,15 @@ +/* + Serial LED implementation for chuniio + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#include "mu3io/leddata.h" + +HRESULT mu3_led_serial_init(wchar_t led_com[12], DWORD baud); +void mu3_led_serial_update(struct _ongeki_led_data_buf_t* data);