From 46ab6c3d9621a3ced6c5d5bfc5b18ad661196d39 Mon Sep 17 00:00:00 2001 From: Tau Date: Thu, 2 May 2019 22:12:06 -0400 Subject: [PATCH] chuniio: Break out Chunithm IO DLL --- chunihook/dllmain.c | 6 ++ chunihook/jvs.c | 79 ++++++++++++------------ chunihook/meson.build | 1 + chunihook/slider-hook.c | 93 ++++++++-------------------- chuniio/chuniio.c | 130 ++++++++++++++++++++++++++++++++++++++++ chuniio/chuniio.def | 10 ++++ chuniio/chuniio.h | 99 ++++++++++++++++++++++++++++++ chuniio/meson.build | 12 ++++ meson.build | 2 + 9 files changed, 321 insertions(+), 111 deletions(-) create mode 100644 chuniio/chuniio.c create mode 100644 chuniio/chuniio.def create mode 100644 chuniio/chuniio.h create mode 100644 chuniio/meson.build diff --git a/chunihook/dllmain.c b/chunihook/dllmain.c index 5179a62..8b1e573 100644 --- a/chunihook/dllmain.c +++ b/chunihook/dllmain.c @@ -12,6 +12,8 @@ #include "chunihook/jvs.h" #include "chunihook/slider-hook.h" +#include "chuniio/chuniio.h" + #include "hook/process.h" #include "hooklib/serial.h" @@ -73,6 +75,10 @@ static DWORD CALLBACK chuni_pre_startup(void) dprintf("--- End chuni_pre_startup ---\n"); + /* Initialize IO DLL */ + + chuni_io_init(); + /* Jump to EXE start address */ return chuni_startup(); diff --git a/chunihook/jvs.c b/chunihook/jvs.c index 5f1d386..701631e 100644 --- a/chunihook/jvs.c +++ b/chunihook/jvs.c @@ -9,10 +9,17 @@ #include "board/io3.h" +#include "chuniio/chuniio.h" + #include "jvs/jvs-bus.h" #include "util/dprintf.h" +struct chunithm_jvs_ir_mask { + uint16_t p1; + uint16_t p2; +}; + static void chunithm_jvs_read_switches(void *ctx, struct io3_switch_state *out); static uint16_t chunithm_jvs_read_coin_counter(void *ctx, uint8_t slot_no); @@ -21,10 +28,16 @@ static const struct io3_ops chunithm_jvs_io3_ops = { .read_coin_counter = chunithm_jvs_read_coin_counter, }; +static const struct chunithm_jvs_ir_mask chunithm_jvs_ir_masks[] = { + { 0x0000, 0x0020 }, + { 0x0020, 0x0000 }, + { 0x0000, 0x0010 }, + { 0x0010, 0x0000 }, + { 0x0000, 0x0008 }, + { 0x0008, 0x0000 }, +}; + static struct io3 chunithm_jvs_io3; -static size_t chunithm_jvs_rise_pos; -static bool chunithm_jvs_coin; -static uint16_t chunithm_jvs_coins; void chunithm_jvs_init(void) { @@ -34,45 +47,37 @@ void chunithm_jvs_init(void) static void chunithm_jvs_read_switches(void *ctx, struct io3_switch_state *out) { + uint8_t opbtn; + uint8_t beams; + size_t i; + assert(out != NULL); - /* Update simulated raise/lower state */ + opbtn = 0; + beams = 0; - if (GetAsyncKeyState(VK_SPACE)) { - if (chunithm_jvs_rise_pos < 6) { - chunithm_jvs_rise_pos++; - } - } else { - if (chunithm_jvs_rise_pos > 0) { - chunithm_jvs_rise_pos--; - } - } + chuni_io_jvs_poll(&opbtn, &beams); - /* Render the state. Every case falls through, this is intentional. */ + out->system = 0x00; + out->p1 = 0x0000; + out->p2 = 0x0000; - out->p1 = 0; - out->p2 = 0; - - switch (chunithm_jvs_rise_pos) { - case 0: out->p2 |= 0x0020; - case 1: out->p1 |= 0x0020; - case 2: out->p2 |= 0x0010; - case 3: out->p1 |= 0x0010; - case 4: out->p2 |= 0x0008; - case 5: out->p1 |= 0x0008; - } - - /* Update test/service buttons */ - - if (GetAsyncKeyState('1')) { + if (opbtn & 0x01) { out->system = 0x80; } else { - out->system = 0; + out->system = 0x00; } - if (GetAsyncKeyState('2')) { + if (opbtn & 0x02) { out->p1 |= 0x4000; } + + for (i = 0 ; i < 6 ; i++) { + if (beams & (1 << i)) { + out->p1 |= chunithm_jvs_ir_masks[i].p1; + out->p2 |= chunithm_jvs_ir_masks[i].p2; + } + } } static uint16_t chunithm_jvs_read_coin_counter(void *ctx, uint8_t slot_no) @@ -81,15 +86,5 @@ static uint16_t chunithm_jvs_read_coin_counter(void *ctx, uint8_t slot_no) return 0; } - if (GetAsyncKeyState('3')) { - if (!chunithm_jvs_coin) { - dprintf("Chunithm JVS: Coin drop\n"); - chunithm_jvs_coin = true; - chunithm_jvs_coins++; - } - } else { - chunithm_jvs_coin = false; - } - - return chunithm_jvs_coins; + return chuni_io_jvs_read_coin_counter(); } diff --git a/chunihook/meson.build b/chunihook/meson.build index 9b8636e..b3afdbc 100644 --- a/chunihook/meson.build +++ b/chunihook/meson.build @@ -11,6 +11,7 @@ shared_library( link_with : [ amex_lib, board_lib, + chuniio_dll, jvs_lib, platform_lib, util_lib, diff --git a/chunihook/slider-hook.c b/chunihook/slider-hook.c index 7863409..7c2ae76 100644 --- a/chunihook/slider-hook.c +++ b/chunihook/slider-hook.c @@ -11,6 +11,8 @@ #include "chunihook/slider-hook.h" +#include "chuniio/chuniio.h" + #include "hook/iobuf.h" #include "hook/iohook.h" @@ -29,16 +31,13 @@ static HRESULT slider_req_auto_scan_start(void); static HRESULT slider_req_auto_scan_stop(void); static HRESULT slider_req_set_led(const struct slider_req_set_led *req); -static unsigned int __stdcall slider_thread_proc(void *ctx); +static void slider_res_auto_scan(const uint8_t *state); static CRITICAL_SECTION slider_lock; static struct uart slider_uart; static uint8_t slider_written_bytes[520]; static uint8_t slider_readable_bytes[520]; -static HANDLE slider_thread; -static bool slider_stop; - void slider_hook_init(void) { InitializeCriticalSection(&slider_lock); @@ -173,25 +172,8 @@ static HRESULT slider_req_get_board_info(void) static HRESULT slider_req_auto_scan_start(void) { - dprintf("Chunithm slider: Start slider thread\n"); - - if (slider_thread != NULL) { - dprintf("Thread is already running\n"); - - return S_OK; - } - - slider_thread = (HANDLE) _beginthreadex( - NULL, - 0, - slider_thread_proc, - NULL, - 0, - NULL); - - if (slider_thread == NULL) { - dprintf("Thread launch failed\n"); - } + dprintf("Chunithm slider: Start slider notifications\n"); + chuni_io_slider_start(slider_res_auto_scan); /* This message is not acknowledged */ @@ -202,21 +184,16 @@ static HRESULT slider_req_auto_scan_stop(void) { struct slider_hdr resp; - dprintf("Chunithm slider: Stop slider thread\n"); + dprintf("Chunithm slider: Stop slider notifications\n"); - if (slider_thread != NULL) { - slider_stop = true; - LeaveCriticalSection(&slider_lock); + /* IO DLL worker thread might attempt to invoke the callback (which needs + to take slider_lock, which we are currently holding) before noticing that + it needs to shut down. Unlock here so that we don't deadlock in that + situation. */ - WaitForSingleObject(slider_thread, INFINITE); - CloseHandle(slider_thread); - slider_thread = NULL; - slider_stop = false; - - dprintf("Chunithm slider: Thread has terminated\n"); - - EnterCriticalSection(&slider_lock); - } + LeaveCriticalSection(&slider_lock); + chuni_io_slider_stop(); + EnterCriticalSection(&slider_lock); resp.sync = SLIDER_FRAME_SYNC; resp.cmd = SLIDER_CMD_AUTO_SCAN_STOP; @@ -227,45 +204,23 @@ static HRESULT slider_req_auto_scan_stop(void) static HRESULT slider_req_set_led(const struct slider_req_set_led *req) { + chuni_io_slider_set_leds(req->payload.rgb); + /* This message is not acknowledged */ + return S_OK; } -static unsigned int WINAPI slider_thread_proc(void *ctx) +static void slider_res_auto_scan(const uint8_t *state) { - static const int keys[] = { - 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', - }; - struct slider_resp_auto_scan resp; - uint8_t pressure; - bool stop; - size_t i; - for (;;) { - resp.hdr.sync = SLIDER_FRAME_SYNC; - resp.hdr.cmd = SLIDER_CMD_AUTO_SCAN; - resp.hdr.nbytes = sizeof(resp.pressure); + resp.hdr.sync = SLIDER_FRAME_SYNC; + resp.hdr.cmd = SLIDER_CMD_AUTO_SCAN; + resp.hdr.nbytes = sizeof(resp.pressure); + memcpy(resp.pressure, state, sizeof(resp.pressure)); - for (i = 0 ; i < 8 ; i++) { - pressure = GetAsyncKeyState(keys[i]) ? 20 : 0; - memset(&resp.pressure[28 - 4 * i], pressure, 4); - } - - EnterCriticalSection(&slider_lock); - - stop = slider_stop; - - if (!stop) { - slider_frame_encode(&slider_uart.readable, &resp, sizeof(resp)); - } - - LeaveCriticalSection(&slider_lock); - - if (stop) { - return 0; - } - - Sleep(1); - } + EnterCriticalSection(&slider_lock); + slider_frame_encode(&slider_uart.readable, &resp, sizeof(resp)); + LeaveCriticalSection(&slider_lock); } diff --git a/chuniio/chuniio.c b/chuniio/chuniio.c new file mode 100644 index 0000000..5ef79ed --- /dev/null +++ b/chuniio/chuniio.c @@ -0,0 +1,130 @@ +#include + +#include +#include +#include + +#include "chuniio/chuniio.h" + +static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx); + +static const int chuni_io_slider_keys[] = { + 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', +}; + +static bool chuni_io_coin; +static uint16_t chuni_io_coins; +static uint8_t chuni_io_hand_pos; +static HANDLE chuni_io_slider_thread; +static bool chuni_io_slider_stop_flag; + +HRESULT chuni_io_init(void) +{ + return S_OK; +} + +uint16_t chuni_io_jvs_read_coin_counter(void) +{ + if (GetAsyncKeyState('3')) { + if (!chuni_io_coin) { + chuni_io_coin = true; + chuni_io_coins++; + } + } else { + chuni_io_coin = false; + } + + return chuni_io_coins; +} + +void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams) +{ + size_t i; + + if (GetAsyncKeyState('1')) { + *opbtn |= 0x01; /* Test */ + } + + if (GetAsyncKeyState('2')) { + *opbtn |= 0x02; /* Service */ + } + + if (GetAsyncKeyState(VK_SPACE)) { + if (chuni_io_hand_pos < 6) { + chuni_io_hand_pos++; + } + } else { + if (chuni_io_hand_pos > 0) { + chuni_io_hand_pos--; + } + } + + for (i = 0 ; i < 6 ; i++) { + if (chuni_io_hand_pos > i) { + *beams |= (1 << i); + } + } +} + +void chuni_io_jvs_set_coin_blocker(bool open) +{} + +void chuni_io_slider_start(chuni_io_slider_callback_t callback) +{ + if (chuni_io_slider_thread != NULL) { + return; + } + + chuni_io_slider_thread = (HANDLE) _beginthreadex( + NULL, + 0, + chuni_io_slider_thread_proc, + callback, + 0, + NULL); +} + +void chuni_io_slider_stop(void) +{ + if (chuni_io_slider_thread == NULL) { + return; + } + + chuni_io_slider_stop_flag = true; + + WaitForSingleObject(chuni_io_slider_thread, INFINITE); + CloseHandle(chuni_io_slider_thread); + chuni_io_slider_thread = NULL; + chuni_io_slider_stop_flag = false; +} + +void chuni_io_slider_set_leds(const uint8_t *rgb) +{ +} + +static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx) +{ + chuni_io_slider_callback_t callback; + uint8_t pressure_val; + uint8_t pressure[32]; + size_t i; + + callback = ctx; + + while (!chuni_io_slider_stop_flag) { + for (i = 0 ; i < 8 ; i++) { + if (GetAsyncKeyState(chuni_io_slider_keys[i]) & 0x8000) { + pressure_val = 20; + } else { + pressure_val = 0; + } + + memset(&pressure[28 - 4 * i], pressure_val, 4); + } + + callback(pressure); + Sleep(1); + } + + return 0; +} diff --git a/chuniio/chuniio.def b/chuniio/chuniio.def new file mode 100644 index 0000000..b8e2754 --- /dev/null +++ b/chuniio/chuniio.def @@ -0,0 +1,10 @@ +LIBRARY chuniio + +EXPORTS + chuni_io_init + chuni_io_jvs_poll + chuni_io_jvs_read_coin_counter + chuni_io_jvs_set_coin_blocker + chuni_io_slider_set_leds + chuni_io_slider_start + chuni_io_slider_stop diff --git a/chuniio/chuniio.h b/chuniio/chuniio.h new file mode 100644 index 0000000..456c7f9 --- /dev/null +++ b/chuniio/chuniio.h @@ -0,0 +1,99 @@ +#pragma once + +#include + +#include +#include + +/* Initialize the Chunithm IO provider DLL. This is the first function to be + called on this DLL. Returning failure from this function will cause the + main application to immediately exit. + + 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 chuni_io_init(void); + +/* Poll JVS input. + + opbtn returns the cabinet test/service state, where bit 0 is Test and Bit 1 + is Service. + + beam returns the IR beams that are currently broken, where bit 0 is the + lowest IR beam and bit 5 is the highest IR beam, for a total of six beams. + + Both bit masks are active-high. + + Note that you cannot instantly break the entire IR grid in a single frame to + simulate hand movement; this will be judged as a miss. You need to simulate + a gradual raising and lowering of the hands. Consult the proof-of-concept + implementation for details. */ + +void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams); + +/* Read the current state of the coin counter. This value should be incremented + for every coin detected by the coin acceptor mechanism. This count does not + need to persist beyond the lifetime of the process. */ + +uint16_t chuni_io_jvs_read_coin_counter(void); + +/* Set the state of the coin blocker. Parameter is true if the blocker is + disengaged (i.e. coins can be inserted) and false if the blocker is engaged + (i.e. the coin slot should be physically blocked). */ + +void chuni_io_jvs_set_coin_blocker(bool open); + +/* Chunithm touch slider layout: + + ^^^ Toward screen ^^^ + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + 31 | 29 | 27 | 25 | 23 | 21 | 19 | 17 | 15 | 13 | 11 | 9 | 7 | 5 | 3 | 1 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + 32 | 30 | 28 | 26 | 24 | 22 | 20 | 18 | 16 | 14 | 12 | 10 | 8 | 6 | 4 | 2 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + + There are a total of 32 regions on the touch slider. Each region can return + an 8-bit pressure value. The operator menu allows the operator to adjust the + pressure level at which a region is considered to be pressed; the factory + default value for this setting is 20. */ + +/* Callback function supplied to your IO DLL. This must be called with a + pointer to a 32-byte array of pressure values, one byte per slider cell. + See above for layout and pressure threshold information. + + The callback will copy the pressure state data out of your buffer before + returning. The pointer will not be retained. */ + +typedef void (*chuni_io_slider_callback_t)(const uint8_t *state); + +/* Start polling the slider. Your DLL must start a polling thread and call the + supplied function periodically from that thread with new input state. The + update interval is up to you, but if your input device doesn't have any + preferred interval then 1 kHz is a reasonable maximum frequency. + + Note that you do have to have to call the callback "occasionally" even if + nothing is changing, otherwise the game will raise a comm timeout error. */ + +void chuni_io_slider_start(chuni_io_slider_callback_t callback); + +/* Stop polling the slider. You must cease to invoke the input callback before + returning from this function. + + This *will* be called in the course of regular operation. For example, + every time you go into the operator menu the slider and all of the other I/O + on the cabinet gets restarted. + + Following on from the above, the slider polling loop *will* be restarted + after being stopped in the course of regular operation. Do not permanently + tear down your input driver in response to this function call. */ + +void chuni_io_slider_stop(void); + +/* Update the RGB lighting on the slider. A pointer to an array of 32 * 3 = 96 + bytes is supplied. The illuminated areas on the touch slider are some + combination of rectangular regions and dividing lines between these regions + but the exact mapping of this lighting control buffer is still TBD. */ + +void chuni_io_slider_set_leds(const uint8_t *rgb); diff --git a/chuniio/meson.build b/chuniio/meson.build new file mode 100644 index 0000000..78a974b --- /dev/null +++ b/chuniio/meson.build @@ -0,0 +1,12 @@ +chuniio_dll = shared_library( + 'chuniio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'chuniio.def', + c_pch : '../precompiled.h', + sources : [ + 'chuniio.c', + 'chuniio.h', + ], +) diff --git a/meson.build b/meson.build index 0169c34..7fc08f3 100644 --- a/meson.build +++ b/meson.build @@ -37,6 +37,8 @@ subdir('platform') subdir('util') subdir('aimeio') +subdir('chuniio') + subdir('cardhook') subdir('chunihook') subdir('divahook')