1
0
mirror of https://github.com/djhackersdev/bemanitools.git synced 2025-02-20 20:41:10 +01:00

vigem-iidxio: Add vigem implementation with iidxio interface

This uses vigem to create two virtual xbox gamepads and hooks
up the iidxio API to them. It allows you to use anything that
implements the BT5's iidxio API as a game controller, e.g.
iidxio-bio2, iidxio-ezusb1/2 to play any game that supports
xbox game controllers, e.g. lunatic rave, IIDX infinitas.

Further features include setting your own custom 16seg text
from a configuration file or enable some hardcoded light
sequence to make your cabinet look bad ass even when playing
"Big Rigs: Over the Road Racing"
This commit is contained in:
icex2 2020-12-16 22:51:54 +01:00
parent 5658658d3a
commit 41187091b0
9 changed files with 655 additions and 0 deletions

View File

@ -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 $@ $^

View File

@ -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 \

View File

@ -0,0 +1,87 @@
#define LOG_MODULE "vigem-iidx-cab-16seg-sequencer"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#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);
}
}

View File

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

View File

@ -0,0 +1,48 @@
#define LOG_MODULE "vigem-iidx-cab-light-sequencer"
#include <stdbool.h>
#include <stdint.h>
#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;
}
}

View File

@ -0,0 +1,11 @@
#ifndef VIGEM_IIDXIO_CAB_LIGHT_SEQUENCER_H
#define VIGEM_IIDXIO_CAB_LIGHT_SEQUENCER_H
#include <stdbool.h>
#include <stdint.h>
void vigem_iidxio_cab_light_sequencer_init();
void vigem_iidxio_cab_light_sequencer_update(bool* out_neon, uint8_t* out_spots);
#endif

View File

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

View File

@ -0,0 +1,18 @@
#ifndef VIGEM_IIDXIO_CONFIG_H
#define VIGEM_IIDXIO_CONFIG_H
#include <windows.h>
#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

View File

@ -0,0 +1,315 @@
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <ViGEm/Client.h>
#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;
}