chuni/chusan: added LED output to DLLs (will break most DLLs)

Credits: somewhatlurker, skogaby
https://dev.s-ul.net/skogaby/segatools/-/blob/ongeki-15093/
This commit is contained in:
2023-12-19 12:43:26 +01:00
parent ed042176d7
commit 8ebdf67d6e
33 changed files with 743 additions and 107 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

View File

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

22
chuniio/leddata.h Normal file
View File

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

133
chuniio/ledoutput.c Normal file
View File

@ -0,0 +1,133 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include "chuniio/config.h"
#include "chuniio/leddata.h"
#include "chuniio/ledoutput.h"
#include "chuniio/pipeimpl.h"
#include "chuniio/serialimpl.h"
static struct _chuni_led_data_buf_t led_unescaped_buf[LED_BOARDS_TOTAL];
static struct _chuni_led_data_buf_t led_escaped_buf[LED_BOARDS_TOTAL];
static bool led_output_is_init = false;
static struct chuni_io_config* config;
static bool any_outputs_enabled;
HANDLE led_init_mutex;
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);
}
}
}

19
chuniio/ledoutput.h Normal file
View File

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

View File

@ -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'
],
)

160
chuniio/pipeimpl.c Normal file
View File

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

14
chuniio/pipeimpl.h Normal file
View File

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

99
chuniio/serialimpl.c Normal file
View File

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

15
chuniio/serialimpl.h Normal file
View File

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