diff --git a/Module.mk b/Module.mk index df3f25d..6101aa5 100644 --- a/Module.mk +++ b/Module.mk @@ -155,6 +155,7 @@ include src/main/security/Module.mk include src/main/unicorntail/Module.mk include src/main/util/Module.mk include src/main/vefxio/Module.mk +include src/main/vigem-iidxio/Module.mk include src/main/vigem-sdvxio/Module.mk include src/main/vigemstub/Module.mk @@ -382,6 +383,7 @@ $(zipdir)/iidx-hwio-x86.zip: \ build/bin/indep-32/iidxio-ezusb.dll \ build/bin/indep-32/iidxio-ezusb2.dll \ dist/iidx/iidxio-bio2.conf \ + build/bin/indep-32/vigem-iidxio.exe \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -392,6 +394,7 @@ $(zipdir)/iidx-hwio-x64.zip: \ build/bin/indep-64/iidxio-ezusb.dll \ build/bin/indep-64/iidxio-ezusb2.dll \ dist/iidx/iidxio-bio2.conf \ + build/bin/indep-64/vigem-iidxio.exe \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ diff --git a/src/main/vigem-iidxio/Module.mk b/src/main/vigem-iidxio/Module.mk new file mode 100644 index 0000000..887a70f --- /dev/null +++ b/src/main/vigem-iidxio/Module.mk @@ -0,0 +1,22 @@ +exes += vigem-iidxio + +deplibs_vigem-iidxio := \ + ViGEmClient \ + +cppflags_vigem-iidxio := \ + -I src/imports \ + +ldflags_vigem-iidxio := \ + -lsetupapi \ + +libs_vigem-iidxio := \ + cconfig \ + iidxio \ + util \ + vigemstub \ + +src_vigem-iidxio := \ + cab-16seg-sequencer.c \ + cab-light-sequencer.c \ + main.c \ + config.c \ diff --git a/src/main/vigem-iidxio/cab-16seg-sequencer.c b/src/main/vigem-iidxio/cab-16seg-sequencer.c new file mode 100644 index 0000000..0eff372 --- /dev/null +++ b/src/main/vigem-iidxio/cab-16seg-sequencer.c @@ -0,0 +1,87 @@ +#define LOG_MODULE "vigem-iidx-cab-16seg-sequencer" + +#include +#include +#include + +#include "util/log.h" +#include "util/time.h" + +static const uint8_t _MAX_LEN_16SEG = 9; + +static bool _enabled; + +static uint64_t _time_counter; +static size_t _text_pos; + +static char _text[1024 + 1]; +static size_t _text_len; + +static uint32_t _scroll_cycle_time_ms; + +static void _create_display_string_with_wrap_around(char* out_16seg) +{ + size_t cur_text_pos = _text_pos; + + for (uint8_t i = 0; i < _MAX_LEN_16SEG; i++) { + if (cur_text_pos >= _text_len) { + cur_text_pos = 0; + } + + out_16seg[i] = _text[cur_text_pos]; + cur_text_pos++; + } +} + +void vigem_iidxio_cab_16seg_sequencer_init(const char* text, uint32_t scroll_cycle_time_ms) +{ + log_assert(text); + + _text_len = strlen(text); + + if (_text_len + 1 > sizeof(_text)) { + log_warning("Truncating input text as it exceeds the max size"); + strncpy(_text, text, sizeof(_text) - 1); + } else { + strcpy(_text, text); + } + + _scroll_cycle_time_ms = scroll_cycle_time_ms; + + _time_counter = time_get_counter(); + _enabled = true; + + if (_enabled) { + log_info("Initialized, cycle time %d ms, text \"%s\"", _scroll_cycle_time_ms, _text); + } +} + +void vigem_iidxio_cab_16seg_sequencer_update(char* out_16seg) +{ + log_assert(out_16seg); + + memset(out_16seg, ' ', _MAX_LEN_16SEG); + + if (!_enabled) { + return; + } + + uint64_t counter_now = time_get_counter(); + uint32_t cycle_time_elapsed_ms = time_get_elapsed_ms(counter_now - _time_counter); + + if (cycle_time_elapsed_ms >= _scroll_cycle_time_ms) { + _time_counter = counter_now; + _text_pos++; + + if (_text_pos >= _text_len) { + _text_pos = 0; + } + } + + // Have static text if not exceeding 16seg display size + if (_text_len <= _MAX_LEN_16SEG) { + memcpy(out_16seg, _text, _text_len); + } else { + _create_display_string_with_wrap_around(out_16seg); + } +} \ No newline at end of file diff --git a/src/main/vigem-iidxio/cab-16seg-sequencer.h b/src/main/vigem-iidxio/cab-16seg-sequencer.h new file mode 100644 index 0000000..f3bdd8f --- /dev/null +++ b/src/main/vigem-iidxio/cab-16seg-sequencer.h @@ -0,0 +1,8 @@ +#ifndef VIGEM_IIDXIO_CAB_16SEG_SEQUENCER_H +#define VIGEM_IIDXIO_CAB_16SEG_SEQUENCER_H + +void vigem_iidxio_cab_16seg_sequencer_init(const char* text, uint32_t scroll_cycle_time_ms); + +void vigem_iidxio_cab_16seg_sequencer_update(char* out_16seg); + +#endif \ No newline at end of file diff --git a/src/main/vigem-iidxio/cab-light-sequencer.c b/src/main/vigem-iidxio/cab-light-sequencer.c new file mode 100644 index 0000000..2a41518 --- /dev/null +++ b/src/main/vigem-iidxio/cab-light-sequencer.c @@ -0,0 +1,48 @@ +#define LOG_MODULE "vigem-iidx-cab-light-sequencer" + +#include +#include + +#include "util/log.h" +#include "util/time.h" + +static const uint32_t _SEQ_CYCLE_TIME_MS = 2000; + +static bool _enabled; + +static uint64_t _time_counter; + +void vigem_iidxio_cab_light_sequencer_init() +{ + _time_counter = time_get_counter(); + _enabled = true; + + if (_enabled) { + log_info("Initialized"); + } +} + +void vigem_iidxio_cab_light_sequencer_update(bool* out_neon, uint8_t* out_spots) +{ + log_assert(out_neon); + log_assert(out_spots); + + if (!_enabled) { + *out_neon = false; + *out_spots = 0; + return; + } + + uint64_t counter_now = time_get_counter(); + uint32_t cycle_time_elapsed_ms = time_get_elapsed_ms(counter_now - _time_counter); + + if (cycle_time_elapsed_ms < _SEQ_CYCLE_TIME_MS / 2) { + *out_neon = false; + } else { + *out_neon = true; + } + + if (cycle_time_elapsed_ms >= _SEQ_CYCLE_TIME_MS) { + _time_counter = counter_now; + } +} \ No newline at end of file diff --git a/src/main/vigem-iidxio/cab-light-sequencer.h b/src/main/vigem-iidxio/cab-light-sequencer.h new file mode 100644 index 0000000..514a4c5 --- /dev/null +++ b/src/main/vigem-iidxio/cab-light-sequencer.h @@ -0,0 +1,11 @@ +#ifndef VIGEM_IIDXIO_CAB_LIGHT_SEQUENCER_H +#define VIGEM_IIDXIO_CAB_LIGHT_SEQUENCER_H + +#include +#include + +void vigem_iidxio_cab_light_sequencer_init(); + +void vigem_iidxio_cab_light_sequencer_update(bool* out_neon, uint8_t* out_spots); + +#endif \ No newline at end of file diff --git a/src/main/vigem-iidxio/config.c b/src/main/vigem-iidxio/config.c new file mode 100644 index 0000000..359f75a --- /dev/null +++ b/src/main/vigem-iidxio/config.c @@ -0,0 +1,143 @@ +#include "cconfig/cconfig-main.h" +#include "cconfig/cconfig-util.h" + +#include "vigem-iidxio/config.h" + +#include "util/log.h" + +#define VIGEM_IIDXIO_CONFIG_ENABLE_KEYLIGHT_KEY "vigem.iidxio.enable_keylight" +#define VIGEM_IIDXIO_CONFIG_RELATIVE_ANALOG_KEY "vigem.iidxio.use_relative_analog" +#define VIGEM_IIDXIO_CONFIG_ENABLE_CAB_LIGHT_SEQ_KEY "vigem.iidxio.enable_cab_light_seq" +#define VIGEM_IIDXIO_CONFIG_TEXT_16SEG_KEY "vigem.iidxio.text_16seg" +#define VIGEM_IIDXIO_CONFIG_TEXT_SCROLL_CYCLE_TIME_MS_KEY "vigem.iidxio.text_scroll_cycle_time_ms" + +#define VIGEM_IIDXIO_CONFIG_DEFAULT_ENABLE_KEYLIGHT_VALUE true +#define VIGEM_IIDXIO_CONFIG_DEFAULT_RELATIVE_ANALOG_VALUE false +#define VIGEM_IIDXIO_CONFIG_DEFAULT_ENABLE_CAB_LIGHT_SEQ_VALUE false +#define VIGEM_IIDXIO_CONFIG_DEFAULT_TEXT_16SEG_VALUE "" +#define VIGEM_IIDXIO_CONFIG_DEFAULT_TEXT_SCROLL_CYCLE_TIME_MS_VALUE 500 + +static void _vigem_iidxio_config_init(struct cconfig *config) +{ + cconfig_util_set_bool( + config, + VIGEM_IIDXIO_CONFIG_ENABLE_KEYLIGHT_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_ENABLE_KEYLIGHT_VALUE, + "Enable input based key lighting"); + + cconfig_util_set_bool( + config, + VIGEM_IIDXIO_CONFIG_RELATIVE_ANALOG_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_RELATIVE_ANALOG_VALUE, + "Use relative mode analog mapping"); + + cconfig_util_set_bool( + config, + VIGEM_IIDXIO_CONFIG_ENABLE_CAB_LIGHT_SEQ_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_ENABLE_CAB_LIGHT_SEQ_VALUE, + "Enable built in cabinet light sequence"); + + cconfig_util_set_str( + config, + VIGEM_IIDXIO_CONFIG_TEXT_16SEG_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_TEXT_16SEG_VALUE, + "Display text on 16seg. If text exceeds 9 char display limit, it will scroll"); + + cconfig_util_set_int( + config, + VIGEM_IIDXIO_CONFIG_TEXT_SCROLL_CYCLE_TIME_MS_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_TEXT_SCROLL_CYCLE_TIME_MS_VALUE, + "Cycle time/scroll speed for text exceeding 16seg display length (9) to scroll from right"); +} + +static void _vigem_iidxio_config_get( + struct vigem_iidxio_config *vigem_config, struct cconfig *config) +{ + if (!cconfig_util_get_bool( + config, + VIGEM_IIDXIO_CONFIG_ENABLE_KEYLIGHT_KEY, + &vigem_config->enable_keylight, + VIGEM_IIDXIO_CONFIG_DEFAULT_ENABLE_KEYLIGHT_VALUE)) { + log_warning( + "Invalid value for key '%s' specified, fallback " + "to default '%d'", + VIGEM_IIDXIO_CONFIG_ENABLE_KEYLIGHT_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_ENABLE_KEYLIGHT_VALUE); + } + + if (!cconfig_util_get_bool( + config, + VIGEM_IIDXIO_CONFIG_RELATIVE_ANALOG_KEY, + &vigem_config->relative_analog, + VIGEM_IIDXIO_CONFIG_DEFAULT_RELATIVE_ANALOG_VALUE)) { + log_warning( + "Invalid value for key '%s' specified, fallback " + "to default '%d'", + VIGEM_IIDXIO_CONFIG_RELATIVE_ANALOG_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_RELATIVE_ANALOG_VALUE); + } + + if (!cconfig_util_get_bool( + config, + VIGEM_IIDXIO_CONFIG_ENABLE_CAB_LIGHT_SEQ_KEY, + &vigem_config->enable_cab_light_seq, + VIGEM_IIDXIO_CONFIG_DEFAULT_ENABLE_CAB_LIGHT_SEQ_VALUE)) { + log_warning( + "Invalid value for key '%s' specified, fallback " + "to default '%d'", + VIGEM_IIDXIO_CONFIG_ENABLE_CAB_LIGHT_SEQ_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_ENABLE_CAB_LIGHT_SEQ_VALUE); + } + + if (!cconfig_util_get_str( + config, + VIGEM_IIDXIO_CONFIG_TEXT_16SEG_KEY, + vigem_config->text_16seg, + sizeof(vigem_config->text_16seg), + VIGEM_IIDXIO_CONFIG_DEFAULT_TEXT_16SEG_VALUE)) { + log_warning( + "Invalid value for key '%s' specified, fallback " + "to default '%s'", + VIGEM_IIDXIO_CONFIG_TEXT_16SEG_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_TEXT_16SEG_VALUE); + } + + if (!cconfig_util_get_int( + config, + VIGEM_IIDXIO_CONFIG_TEXT_SCROLL_CYCLE_TIME_MS_KEY, + &vigem_config->text_scroll_cycle_time_ms, + VIGEM_IIDXIO_CONFIG_DEFAULT_TEXT_SCROLL_CYCLE_TIME_MS_VALUE)) { + log_warning( + "Invalid value for key '%s' specified, fallback " + "to default '%d'", + VIGEM_IIDXIO_CONFIG_TEXT_SCROLL_CYCLE_TIME_MS_KEY, + VIGEM_IIDXIO_CONFIG_DEFAULT_TEXT_SCROLL_CYCLE_TIME_MS_VALUE); + } +} + +bool vigem_iidxio_config_get(struct vigem_iidxio_config *config_out) +{ + struct cconfig *config; + + config = cconfig_init(); + + _vigem_iidxio_config_init(config); + + if (!cconfig_main_config_init( + config, + "--config", + "vigem-iidxio.conf", + "--help", + "-h", + "vigem-iidxio", + CCONFIG_CMD_USAGE_OUT_STDOUT)) { + cconfig_finit(config); + return false; + } + + _vigem_iidxio_config_get(config_out, config); + + cconfig_finit(config); + + return true; +} diff --git a/src/main/vigem-iidxio/config.h b/src/main/vigem-iidxio/config.h new file mode 100644 index 0000000..cdcbcca --- /dev/null +++ b/src/main/vigem-iidxio/config.h @@ -0,0 +1,18 @@ +#ifndef VIGEM_IIDXIO_CONFIG_H +#define VIGEM_IIDXIO_CONFIG_H + +#include + +#include "cconfig/cconfig.h" + +struct vigem_iidxio_config { + bool enable_keylight; + bool relative_analog; + bool enable_cab_light_seq; + char text_16seg[1024 + 1]; + int32_t text_scroll_cycle_time_ms; +}; + +bool vigem_iidxio_config_get(struct vigem_iidxio_config *config_out); + +#endif \ No newline at end of file diff --git a/src/main/vigem-iidxio/main.c b/src/main/vigem-iidxio/main.c new file mode 100644 index 0000000..7617ba6 --- /dev/null +++ b/src/main/vigem-iidxio/main.c @@ -0,0 +1,315 @@ +#include + +#include +#include +#include +#include + +#include + +#include "bemanitools/iidxio.h" + +#include "util/log.h" +#include "util/math.h" +#include "util/thread.h" +#include "util/time.h" + +#include "vigem-iidxio/cab-16seg-sequencer.h" +#include "vigem-iidxio/cab-light-sequencer.h" +#include "vigem-iidxio/config.h" + +#include "vigemstub/helper.h" + +#define ANALOG_FIXED_SENSITIVITY 256 + +static int16_t _convert_analog_to_s16(uint8_t val) +{ + return (int64_t) val * 256; +} + +static int16_t _filter_floor(int32_t value, int16_t floor) { + if (abs(value) < floor) { + return 0; + } + if (value > INT16_MAX) { + value = INT16_MAX; + } + if (value < INT16_MIN) { + value = INT16_MIN; + } + + return value; +} + +static int32_t _convert_relative_analog( + uint8_t val, uint8_t last, int32_t buffered_last, int16_t multiplier) +{ + int16_t delta = get_wrapped_delta_s16(val, last, UINT8_MAX); + + if (delta == 0) { + // ease the stick back to 0 like a real stick would + return buffered_last / 2.f; + } else { + int64_t result = buffered_last; + result += delta * multiplier; + + // we use an i32 to store the buffered value + // so that we can overshoot an i16 by up to 1.5x + // this allows users to stay at the min/max stick positions + // without perfect knob turning + if (result > INT16_MAX * 1.5) { + result = INT16_MAX * 1.5; + } + if (result < INT16_MIN * 1.5) { + result = INT16_MIN * 1.5; + } + + return result; + } +} + +static bool _check_key(uint16_t input, size_t idx_in) +{ + if ((input >> idx_in) & 1) { + return true; + } + + return false; +} + +static uint16_t _check_assign_key(uint16_t input, size_t idx_in, size_t bit_out) +{ + if (_check_key(input, idx_in)) { + return bit_out; + } + + return 0; +} + +int main(int argc, char **argv) +{ + log_to_writer(log_writer_stdout, NULL); + + struct vigem_iidxio_config config; + + if (!vigem_iidxio_config_get(&config)) { + return -1; + } + + iidx_io_set_loggers( + log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + + if (!iidx_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + log_warning("Initializing iidxio failed"); + return -1; + } + + PVIGEM_CLIENT client = vigem_helper_setup(); + + if (!client) { + log_warning("VIGEM client failed to connect"); + + iidx_io_fini(); + + return -1; + } + + PVIGEM_TARGET pad[2]; + + pad[0] = vigem_helper_add_pad(client); + pad[1] = vigem_helper_add_pad(client); + + if (!pad[0] || !pad[1]) { + if (!pad[0]) { + log_warning("vigem_alloc pad 1 failed"); + } + + if (!pad[1]) { + log_warning("vigem_alloc pad 2 failed"); + } + + iidx_io_fini(); + + return -1; + } + + bool loop = true; + + XUSB_REPORT state[2]; + + log_info("vigem init succeeded, beginning poll loop"); + + // Initial output state, turn all lights off + iidx_io_ep1_set_deck_lights(0); + iidx_io_ep1_set_panel_lights(0); + iidx_io_ep1_set_top_lamps(0); + iidx_io_ep1_set_top_neons(false); + + if (config.enable_cab_light_seq) { + vigem_iidxio_cab_light_sequencer_init(); + } + + if (config.text_16seg[0] != '\0') { + vigem_iidxio_cab_16seg_sequencer_init(config.text_16seg, config.text_scroll_cycle_time_ms); + } + + uint8_t turntable_buffered[2] = {0, 0}; + uint8_t turntable_last[2] = {0, 0}; + + while (loop) { + if (!iidx_io_ep2_recv()) { + log_warning("iidxio receiving failed"); + break; + } + + memset(&state[0], 0, sizeof(state[0])); + memset(&state[1], 0, sizeof(state[1])); + + // 14 keys + uint16_t keys = iidx_io_ep2_get_keys(); + + state[0].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P1_1, XUSB_GAMEPAD_A); + state[0].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P1_2, XUSB_GAMEPAD_B); + state[0].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P1_3, XUSB_GAMEPAD_X); + state[0].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P1_4, XUSB_GAMEPAD_Y); + state[0].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P1_5, XUSB_GAMEPAD_LEFT_SHOULDER); + state[0].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P1_6, XUSB_GAMEPAD_RIGHT_SHOULDER); + state[0].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P1_7, XUSB_GAMEPAD_DPAD_RIGHT); + + state[1].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P2_1, XUSB_GAMEPAD_A); + state[1].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P2_2, XUSB_GAMEPAD_B); + state[1].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P2_3, XUSB_GAMEPAD_X); + state[1].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P2_4, XUSB_GAMEPAD_Y); + state[1].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P2_5, XUSB_GAMEPAD_LEFT_SHOULDER); + state[1].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P2_6, XUSB_GAMEPAD_RIGHT_SHOULDER); + state[1].wButtons |= _check_assign_key( + keys, IIDX_IO_KEY_P2_7, XUSB_GAMEPAD_DPAD_RIGHT); + + // Panel buttons + uint8_t panel = iidx_io_ep2_get_panel(); + + state[0].wButtons |= _check_assign_key( + panel, IIDX_IO_PANEL_LIGHT_P1_START, XUSB_GAMEPAD_START); + state[1].wButtons |= _check_assign_key( + panel, IIDX_IO_PANEL_LIGHT_P2_START, XUSB_GAMEPAD_START); + state[0].wButtons |= _check_assign_key( + panel, IIDX_IO_PANEL_LIGHT_VEFX, XUSB_GAMEPAD_BACK); + state[1].wButtons |= _check_assign_key( + panel, IIDX_IO_PANEL_LIGHT_EFFECT, XUSB_GAMEPAD_BACK); + + // System buttons + uint8_t system = iidx_io_ep2_get_sys(); + + state[0].wButtons |= _check_assign_key( + system, IIDX_IO_SYS_TEST, XUSB_GAMEPAD_LEFT_THUMB); + state[0].wButtons |= _check_assign_key( + system, IIDX_IO_SYS_SERVICE, XUSB_GAMEPAD_RIGHT_THUMB); + state[1].wButtons |= _check_assign_key( + system, IIDX_IO_SYS_COIN, XUSB_GAMEPAD_LEFT_THUMB); + + // Turntable + uint8_t turntable[2]; + + turntable[0] = iidx_io_ep2_get_turntable(0); + turntable[1] = iidx_io_ep2_get_turntable(1); + + if (config.relative_analog) { + turntable_buffered[0] = _convert_relative_analog( + turntable[0], turntable_last[0], turntable_buffered[0], ANALOG_FIXED_SENSITIVITY); + turntable_buffered[1] = _convert_relative_analog( + turntable[1], turntable_last[1], turntable_buffered[1], ANALOG_FIXED_SENSITIVITY); + + state[0].sThumbLX = _filter_floor(turntable_buffered[0], ANALOG_FIXED_SENSITIVITY / 2); + state[1].sThumbLX = _filter_floor(turntable_buffered[1], ANALOG_FIXED_SENSITIVITY / 2); + + turntable_last[0] = turntable[0]; + turntable_last[1] = turntable[1]; + } else { + state[0].sThumbLX = _convert_analog_to_s16(turntable[0]); + state[1].sThumbLX = _convert_analog_to_s16(turntable[1]); + } + + vigem_target_x360_update(client, pad[0], state[0]); + vigem_target_x360_update(client, pad[1], state[1]); + + // ----------------------------------------------------------------------------- + + // Light related outputs + + if (config.enable_keylight) { + iidx_io_ep1_set_deck_lights(keys); + iidx_io_ep1_set_panel_lights(panel); + } + + if (config.enable_cab_light_seq) { + bool neon; + uint8_t spots; + + neon = false; + spots = 0; + + vigem_iidxio_cab_light_sequencer_update(&neon, &spots); + + iidx_io_ep1_set_top_neons(neon); + iidx_io_ep1_set_top_lamps(spots); + } + + char buffer_16seg[9]; + + vigem_iidxio_cab_16seg_sequencer_update(buffer_16seg); + iidx_io_ep3_write_16seg(buffer_16seg); + + if (!iidx_io_ep1_send()) { + log_warning("iidxio sending failed"); + break; + } + + if (_check_key(system, IIDX_IO_SYS_TEST) && + _check_key(system, IIDX_IO_SYS_SERVICE)) { + log_info("Test + service pressed, exiting..."); + loop = false; + } + + // avoid banging + Sleep(1); + } + + // Cleanup, turn all lights off + iidx_io_ep1_set_deck_lights(0); + iidx_io_ep1_set_panel_lights(0); + iidx_io_ep1_set_top_lamps(0); + iidx_io_ep1_set_top_neons(false); + + iidx_io_ep1_send(); + // Required to handle iidxio-ezusb specific quirks with flushing 16seg text + iidx_io_ep2_recv(); + iidx_io_ep3_write_16seg(" "); + + Sleep(10); + + for (int i = 0; i < 2; i++) { + vigem_target_remove(client, pad[i]); + vigem_target_free(pad[i]); + } + + vigem_free(client); + + Sleep(1000); + + iidx_io_fini(); + + return 0; +}