spicetools/overlay/windows/kfcontrol.cpp

1450 lines
69 KiB
C++

#include "kfcontrol.h"
#include <windows.h>
#include <commdlg.h>
#include "cfg/configurator.h"
#include "misc/eamuse.h"
#include "games/io.h"
#include "games/sdvx/io.h"
#include "games/shared/lcdhandle.h"
#include "launcher/launcher.h"
#include "launcher/shutdown.h"
#include "overlay/imgui/extensions.h"
#include "api/controller.h"
#include "rawinput/rawinput.h"
#include "external/rapidjson/document.h"
#include "external/rapidjson/prettywriter.h"
#include "util/netutils.h"
#include "util/fileutils.h"
#include "util/time.h"
#include "util/utils.h"
using namespace GameAPI;
using namespace rapidjson;
namespace overlay::windows {
KFControl::KFControl(SpiceOverlay *overlay) : Window(overlay) {
this->title = "KFControl";
this->init_size = ImVec2(400, 316);
this->size_min = ImVec2(100, 200);
this->init_pos = ImVec2(0, 0);
this->config_load();
if (cfg::CONFIGURATOR_STANDALONE && cfg::CONFIGURATOR_TYPE == cfg::ConfigType::KFControl) {
this->active = true;
this->flags |= ImGuiWindowFlags_NoResize;
this->flags |= ImGuiWindowFlags_NoMove;
this->flags |= ImGuiWindowFlags_NoTitleBar;
this->flags |= ImGuiWindowFlags_NoCollapse;
this->flags |= ImGuiWindowFlags_NoDecoration;
eamuse_set_game("Sound Voltex");
for (auto &button : *games::get_buttons("Sound Voltex")) {
button.clearBindings();
}
for (auto &analog : *games::get_analogs("Sound Voltex")) {
analog.clearBindings();
}
this->worker_start();
}
}
KFControl::~KFControl() {
worker_running = false;
worker->join();
}
void KFControl::config_save() {
// create document
Document doc;
doc.SetObject();
auto &alloc = doc.GetAllocator();
// save settings
doc.AddMember("poll_delay", poll_delay, alloc);
doc.AddMember("vol_deadzone", vol_deadzone, alloc);
doc.AddMember("vol_timeout", vol_timeout, alloc);
doc.AddMember("vol_mouse", vol_mouse, alloc);
doc.AddMember("vol_mouse_sensitivity", vol_mouse_sensitivity, alloc);
doc.AddMember("start_click", start_click, alloc);
doc.AddMember("kp_profiles", kp_profiles, alloc);
doc.AddMember("vol_sound", vol_sound, alloc);
doc.AddMember("vol_headphone", vol_headphone, alloc);
doc.AddMember("vol_external", vol_external, alloc);
doc.AddMember("vol_woofer", vol_woofer, alloc);
doc.AddMember("vol_mute", vol_mute, alloc);
doc.AddMember("icca_timeout", icca_timeout, alloc);
doc.AddMember("icca_file", StringRef(icca_file), alloc);
doc.AddMember("light_wing_left_up_r", light_wing_left_up.x, alloc);
doc.AddMember("light_wing_left_up_g", light_wing_left_up.y, alloc);
doc.AddMember("light_wing_left_up_b", light_wing_left_up.z, alloc);
doc.AddMember("light_wing_left_low_r", light_wing_left_low.x, alloc);
doc.AddMember("light_wing_left_low_g", light_wing_left_low.y, alloc);
doc.AddMember("light_wing_left_low_b", light_wing_left_low.z, alloc);
doc.AddMember("light_wing_right_up_r", light_wing_right_up.x, alloc);
doc.AddMember("light_wing_right_up_g", light_wing_right_up.y, alloc);
doc.AddMember("light_wing_right_up_b", light_wing_right_up.z, alloc);
doc.AddMember("light_wing_right_low_r", light_wing_right_low.x, alloc);
doc.AddMember("light_wing_right_low_g", light_wing_right_low.y, alloc);
doc.AddMember("light_wing_right_low_b", light_wing_right_low.z, alloc);
doc.AddMember("light_woofer_r", light_woofer.x, alloc);
doc.AddMember("light_woofer_g", light_woofer.y, alloc);
doc.AddMember("light_woofer_b", light_woofer.z, alloc);
doc.AddMember("light_controller_r", light_controller.x, alloc);
doc.AddMember("light_controller_g", light_controller.y, alloc);
doc.AddMember("light_controller_b", light_controller.z, alloc);
doc.AddMember("light_generator_r", light_generator.x, alloc);
doc.AddMember("light_generator_g", light_generator.y, alloc);
doc.AddMember("light_generator_b", light_generator.z, alloc);
doc.AddMember("lcd_bri", games::shared::LCD_BRI, alloc);
doc.AddMember("lcd_con", games::shared::LCD_CON, alloc);
doc.AddMember("lcd_bl", games::shared::LCD_BL, alloc);
doc.AddMember("lcd_red", games::shared::LCD_RED, alloc);
doc.AddMember("lcd_green", games::shared::LCD_GREEN, alloc);
doc.AddMember("lcd_blue", games::shared::LCD_BLUE, alloc);
doc.AddMember("coin_block", eamuse_coin_get_block(), alloc);
doc.AddMember("light_buttons", light_buttons, alloc);
doc.AddMember("light_hue_preview", light_hue_preview, alloc);
doc.AddMember("light_hue_func", (int) light_hue_func, alloc);
doc.AddMember("light_hue_disable", light_hue_disable, alloc);
doc.AddMember("light_wing_left_up_hue_amp", light_wing_left_up_hue_amp, alloc);
doc.AddMember("light_wing_left_up_hue_per", light_wing_left_up_hue_per, alloc);
doc.AddMember("light_wing_left_low_hue_amp", light_wing_left_low_hue_amp, alloc);
doc.AddMember("light_wing_left_low_hue_per", light_wing_left_low_hue_per, alloc);
doc.AddMember("light_wing_right_up_hue_amp", light_wing_right_up_hue_amp, alloc);
doc.AddMember("light_wing_right_up_hue_per", light_wing_right_up_hue_per, alloc);
doc.AddMember("light_wing_right_low_hue_amp", light_wing_right_low_hue_amp, alloc);
doc.AddMember("light_wing_right_low_hue_per", light_wing_right_low_hue_per, alloc);
doc.AddMember("light_woofer_hue_amp", light_woofer_hue_amp, alloc);
doc.AddMember("light_woofer_hue_per", light_woofer_hue_per, alloc);
doc.AddMember("light_controller_hue_amp", light_controller_hue_amp, alloc);
doc.AddMember("light_controller_hue_per", light_controller_hue_per, alloc);
doc.AddMember("light_generator_hue_amp", light_generator_hue_amp, alloc);
doc.AddMember("light_generator_hue_per", light_generator_hue_per, alloc);
doc.AddMember("scan_service", scan_service, alloc);
doc.AddMember("scan_test", scan_test, alloc);
doc.AddMember("scan_coin_mech", scan_coin_mech, alloc);
doc.AddMember("scan_bt_a", scan_bt_a, alloc);
doc.AddMember("scan_bt_b", scan_bt_b, alloc);
doc.AddMember("scan_bt_c", scan_bt_c, alloc);
doc.AddMember("scan_bt_d", scan_bt_d, alloc);
doc.AddMember("scan_fx_l", scan_fx_l, alloc);
doc.AddMember("scan_fx_r", scan_fx_r, alloc);
doc.AddMember("scan_start", scan_start, alloc);
doc.AddMember("scan_headphone", scan_headphone, alloc);
doc.AddMember("scan_vol_l_left", scan_vol_l_left, alloc);
doc.AddMember("scan_vol_l_right", scan_vol_l_right, alloc);
doc.AddMember("scan_vol_r_left", scan_vol_r_left, alloc);
doc.AddMember("scan_vol_r_right", scan_vol_r_right, alloc);
doc.AddMember("scan_icca", scan_icca, alloc);
doc.AddMember("scan_coin", scan_coin, alloc);
doc.AddMember("scan_kp_0", scan_kp_0, alloc);
doc.AddMember("scan_kp_1", scan_kp_1, alloc);
doc.AddMember("scan_kp_2", scan_kp_2, alloc);
doc.AddMember("scan_kp_3", scan_kp_3, alloc);
doc.AddMember("scan_kp_4", scan_kp_4, alloc);
doc.AddMember("scan_kp_5", scan_kp_5, alloc);
doc.AddMember("scan_kp_6", scan_kp_6, alloc);
doc.AddMember("scan_kp_7", scan_kp_7, alloc);
doc.AddMember("scan_kp_8", scan_kp_8, alloc);
doc.AddMember("scan_kp_9", scan_kp_9, alloc);
doc.AddMember("scan_kp_00", scan_kp_00, alloc);
doc.AddMember("scan_kp_decimal", scan_kp_decimal, alloc);
// convert to JSON
StringBuffer buf;
PrettyWriter<StringBuffer> writer(buf);
writer.SetIndent(' ', 2);
doc.Accept(writer);
buf.Put('\r');
buf.Put('\n');
// write to file
log_info("kfcontrol", "saving config to: {}", config_path.string());
fileutils::text_write(config_path, buf.GetString());
}
template<class T>
static void load_val(T &node, const std::string &name, float &value) {
auto it = node.FindMember(name.c_str());
if (it != node.MemberEnd() && it->value.IsFloat()) {
value = it->value.GetFloat();
}
}
template<class T>
static void load_val(T &node, const std::string &name, int &value) {
auto it = node.FindMember(name.c_str());
if (it != node.MemberEnd() && it->value.IsInt()) {
value = it->value.GetInt();
}
}
static std::string key_format(int vsc) {
if (vsc == 0) return "Disabled";
auto vk = MapVirtualKey(vsc, MAPVK_VSC_TO_VK_EX);
auto ch = MapVirtualKey(vk, MAPVK_VK_TO_CHAR);
char keyName[128];
if (GetKeyNameText((LONG) (vsc << 16), keyName, sizeof(keyName))) {
return fmt::format("Scan: %d - VK: {} - Key: {}", vk, keyName);
} else if (ch) {
return fmt::format("Scan: %d - VK: {} - Char: '{}'", vk, ch);
} else {
return fmt::format("Scan: %d - VK: {}", vk);
}
}
void KFControl::config_load() {
this->config_path = std::filesystem::path(_wgetenv(L"APPDATA"));
this->config_path /= fmt::format(L"spicetools_kfcontrol_{}.json", config_profile);
// check if config missing
if (!fileutils::file_exists(config_path)) {
return;
}
// read config file
std::string config = fileutils::text_read(config_path);
if (config.empty()) {
return;
}
// parse document
log_info("kfcontrol", "loading config from: {}", config_path.string());
Document doc;
if (doc.Parse(config.c_str()).HasParseError()) {
log_warning("kfcontrol", "unable to parse config: {}", doc.GetParseError());
}
// verify root is a dict
if (!doc.IsObject()) {
log_warning("kfcontrol", "config not a dict");
return;
}
// read settings
load_val(doc, "poll_delay", poll_delay);
auto vol_deadzone_it = doc.FindMember("vol_deadzone");
if (vol_deadzone_it != doc.MemberEnd() && vol_deadzone_it->value.IsFloat()) {
vol_deadzone = vol_deadzone_it->value.GetFloat();
}
auto vol_timeout_it = doc.FindMember("vol_timeout");
if (vol_timeout_it != doc.MemberEnd() && vol_deadzone_it->value.IsUint64()) {
vol_timeout = vol_timeout_it->value.GetUint64();
}
auto vol_mouse_it = doc.FindMember("vol_mouse");
if (vol_mouse_it != doc.MemberEnd() && vol_mouse_it->value.IsBool()) {
vol_mouse = vol_mouse_it->value.GetBool();
}
load_val(doc, "vol_mouse_sensitivity", vol_mouse_sensitivity);
auto start_click_it = doc.FindMember("start_click");
if (start_click_it != doc.MemberEnd() && start_click_it->value.IsBool()) {
start_click = start_click_it->value.GetBool();
}
auto kp_profiles_it = doc.FindMember("kp_profiles");
if (kp_profiles_it != doc.MemberEnd() && kp_profiles_it->value.IsBool()) {
kp_profiles = kp_profiles_it->value.GetBool();
}
auto vol_sound_it = doc.FindMember("vol_sound");
if (vol_sound_it != doc.MemberEnd() && vol_sound_it->value.IsFloat()) {
vol_sound = vol_sound_it->value.GetFloat();
}
auto vol_headphone_it = doc.FindMember("vol_headphone");
if (vol_headphone_it != doc.MemberEnd() && vol_headphone_it->value.IsFloat()) {
vol_headphone = vol_headphone_it->value.GetFloat();
}
auto vol_external_it = doc.FindMember("vol_external");
if (vol_external_it != doc.MemberEnd() && vol_external_it->value.IsFloat()) {
vol_external = vol_external_it->value.GetFloat();
}
auto vol_woofer_it = doc.FindMember("vol_woofer");
if (vol_woofer_it != doc.MemberEnd() && vol_woofer_it->value.IsFloat()) {
vol_woofer = vol_woofer_it->value.GetFloat();
}
auto vol_mute_it = doc.FindMember("vol_mute");
if (vol_mute_it != doc.MemberEnd() && vol_mute_it->value.IsBool()) {
vol_mute = vol_mute_it->value.GetBool();
}
auto icca_timeout_it = doc.FindMember("icca_timeout");
if (icca_timeout_it != doc.MemberEnd() && icca_timeout_it->value.IsUint64()) {
icca_timeout = icca_timeout_it->value.GetUint64();
}
auto icca_file_it = doc.FindMember("icca_file");
if (icca_file_it != doc.MemberEnd() && icca_file_it->value.IsString()) {
if (icca_file_it->value.GetStringLength() < sizeof(icca_file)) {
strcpy(icca_file, icca_file_it->value.GetString());
}
}
auto coin_timeout_it = doc.FindMember("coin_timeout");
if (coin_timeout_it != doc.MemberEnd() && coin_timeout_it->value.IsUint64()) {
coin_timeout = coin_timeout_it->value.GetUint64();
}
auto light_wing_left_up_r_it = doc.FindMember("light_wing_left_up_r");
if (light_wing_left_up_r_it != doc.MemberEnd() && light_wing_left_up_r_it->value.IsFloat()) {
light_wing_left_up.x = light_wing_left_up_r_it->value.GetFloat();
}
auto light_wing_left_up_g_it = doc.FindMember("light_wing_left_up_g");
if (light_wing_left_up_g_it != doc.MemberEnd() && light_wing_left_up_g_it->value.IsFloat()) {
light_wing_left_up.y = light_wing_left_up_g_it->value.GetFloat();
}
auto light_wing_left_up_b_it = doc.FindMember("light_wing_left_up_b");
if (light_wing_left_up_b_it != doc.MemberEnd() && light_wing_left_up_b_it->value.IsFloat()) {
light_wing_left_up.z = light_wing_left_up_b_it->value.GetFloat();
}
auto light_wing_left_low_r_it = doc.FindMember("light_wing_left_low_r");
if (light_wing_left_low_r_it != doc.MemberEnd() && light_wing_left_low_r_it->value.IsFloat()) {
light_wing_left_low.x = light_wing_left_low_r_it->value.GetFloat();
}
auto light_wing_left_low_g_it = doc.FindMember("light_wing_left_low_g");
if (light_wing_left_low_g_it != doc.MemberEnd() && light_wing_left_low_g_it->value.IsFloat()) {
light_wing_left_low.y = light_wing_left_low_g_it->value.GetFloat();
}
auto light_wing_left_low_b_it = doc.FindMember("light_wing_left_low_b");
if (light_wing_left_low_b_it != doc.MemberEnd() && light_wing_left_low_b_it->value.IsFloat()) {
light_wing_left_low.z = light_wing_left_low_b_it->value.GetFloat();
}
auto light_wing_right_up_r_it = doc.FindMember("light_wing_right_up_r");
if (light_wing_right_up_r_it != doc.MemberEnd() && light_wing_right_up_r_it->value.IsFloat()) {
light_wing_right_up.x = light_wing_right_up_r_it->value.GetFloat();
}
auto light_wing_right_up_g_it = doc.FindMember("light_wing_right_up_g");
if (light_wing_right_up_g_it != doc.MemberEnd() && light_wing_right_up_g_it->value.IsFloat()) {
light_wing_right_up.y = light_wing_right_up_g_it->value.GetFloat();
}
auto light_wing_right_up_b_it = doc.FindMember("light_wing_right_up_b");
if (light_wing_right_up_b_it != doc.MemberEnd() && light_wing_right_up_b_it->value.IsFloat()) {
light_wing_right_up.z = light_wing_right_up_b_it->value.GetFloat();
}
auto light_wing_right_low_r_it = doc.FindMember("light_wing_right_low_r");
if (light_wing_right_low_r_it != doc.MemberEnd() && light_wing_right_low_r_it->value.IsFloat()) {
light_wing_right_low.x = light_wing_right_low_r_it->value.GetFloat();
}
auto light_wing_right_low_g_it = doc.FindMember("light_wing_right_low_g");
if (light_wing_right_low_g_it != doc.MemberEnd() && light_wing_right_low_g_it->value.IsFloat()) {
light_wing_right_low.y = light_wing_right_low_g_it->value.GetFloat();
}
auto light_wing_right_low_b_it = doc.FindMember("light_wing_right_low_b");
if (light_wing_right_low_b_it != doc.MemberEnd() && light_wing_right_low_b_it->value.IsFloat()) {
light_wing_right_low.z = light_wing_right_low_b_it->value.GetFloat();
}
auto light_woofer_r_it = doc.FindMember("light_woofer_r");
if (light_woofer_r_it != doc.MemberEnd() && light_woofer_r_it->value.IsFloat()) {
light_woofer.x = light_woofer_r_it->value.GetFloat();
}
auto light_woofer_g_it = doc.FindMember("light_woofer_g");
if (light_woofer_g_it != doc.MemberEnd() && light_woofer_g_it->value.IsFloat()) {
light_woofer.y = light_woofer_g_it->value.GetFloat();
}
auto light_woofer_b_it = doc.FindMember("light_woofer_b");
if (light_woofer_b_it != doc.MemberEnd() && light_woofer_b_it->value.IsFloat()) {
light_woofer.z = light_woofer_b_it->value.GetFloat();
}
auto light_controller_r_it = doc.FindMember("light_controller_r");
if (light_controller_r_it != doc.MemberEnd() && light_controller_r_it->value.IsFloat()) {
light_controller.x = light_controller_r_it->value.GetFloat();
}
auto light_controller_g_it = doc.FindMember("light_controller_g");
if (light_controller_g_it != doc.MemberEnd() && light_controller_g_it->value.IsFloat()) {
light_controller.y = light_controller_g_it->value.GetFloat();
}
auto light_controller_b_it = doc.FindMember("light_controller_b");
if (light_controller_b_it != doc.MemberEnd() && light_controller_b_it->value.IsFloat()) {
light_controller.z = light_controller_b_it->value.GetFloat();
}
auto light_generator_r_it = doc.FindMember("light_generator_r");
if (light_generator_r_it != doc.MemberEnd() && light_generator_r_it->value.IsFloat()) {
light_generator.x = light_generator_r_it->value.GetFloat();
}
auto light_generator_g_it = doc.FindMember("light_generator_g");
if (light_generator_g_it != doc.MemberEnd() && light_generator_g_it->value.IsFloat()) {
light_generator.y = light_generator_g_it->value.GetFloat();
}
auto light_generator_b_it = doc.FindMember("light_generator_b");
if (light_generator_b_it != doc.MemberEnd() && light_generator_b_it->value.IsFloat()) {
light_generator.z = light_generator_b_it->value.GetFloat();
}
auto lcd_bri_it = doc.FindMember("lcd_bri");
if (lcd_bri_it != doc.MemberEnd() && lcd_bri_it->value.IsUint()) {
games::shared::LCD_BRI = lcd_bri_it->value.GetUint();
}
auto lcd_con_it = doc.FindMember("lcd_con");
if (lcd_con_it != doc.MemberEnd() && lcd_con_it->value.IsUint()) {
games::shared::LCD_CON = lcd_con_it->value.GetUint();
}
auto lcd_bl_it = doc.FindMember("lcd_bl");
if (lcd_bl_it != doc.MemberEnd() && lcd_bl_it->value.IsUint()) {
games::shared::LCD_BL = lcd_bl_it->value.GetUint();
}
auto lcd_red_it = doc.FindMember("lcd_red");
if (lcd_red_it != doc.MemberEnd() && lcd_red_it->value.IsUint()) {
games::shared::LCD_RED = lcd_red_it->value.GetUint();
}
auto lcd_green_it = doc.FindMember("lcd_green");
if (lcd_green_it != doc.MemberEnd() && lcd_green_it->value.IsUint()) {
games::shared::LCD_GREEN = lcd_green_it->value.GetUint();
}
auto lcd_blue_it = doc.FindMember("lcd_blue");
if (lcd_blue_it != doc.MemberEnd() && lcd_blue_it->value.IsUint()) {
games::shared::LCD_BLUE = lcd_blue_it->value.GetUint();
}
auto coin_block_it = doc.FindMember("coin_block");
if (coin_block_it != doc.MemberEnd() && coin_block_it->value.IsBool()) {
eamuse_coin_set_block(coin_block_it->value.GetBool());
}
auto light_buttons_it = doc.FindMember("light_buttons");
if (light_buttons_it != doc.MemberEnd() && light_buttons_it->value.IsBool()) {
light_buttons = light_buttons_it->value.GetBool();
}
auto light_hue_preview_it = doc.FindMember("light_hue_preview");
if (light_hue_preview_it != doc.MemberEnd() && light_hue_preview_it->value.IsBool()) {
light_hue_preview = light_hue_preview_it->value.GetBool();
}
auto light_hue_disable_it = doc.FindMember("light_hue_disable");
if (light_hue_disable_it != doc.MemberEnd() && light_hue_disable_it->value.IsBool()) {
light_hue_disable = light_hue_disable_it->value.GetBool();
}
auto light_hue_func_it = doc.FindMember("light_hue_func");
if (light_hue_func_it != doc.MemberEnd() && light_hue_func_it->value.IsUint()) {
auto val = light_hue_func_it->value.GetUint();
if (val < (int) HueFunc::Count) {
light_hue_func = (HueFunc) val;
}
}
load_val(doc, "light_wing_left_up_hue_amp", light_wing_left_up_hue_amp);
load_val(doc, "light_wing_left_up_hue_per", light_wing_left_up_hue_per);
load_val(doc, "light_wing_left_low_hue_amp", light_wing_left_low_hue_amp);
load_val(doc, "light_wing_left_low_hue_per", light_wing_left_low_hue_per);
load_val(doc, "light_wing_right_up_hue_amp", light_wing_right_up_hue_amp);
load_val(doc, "light_wing_right_up_hue_per", light_wing_right_up_hue_per);
load_val(doc, "light_wing_right_low_hue_amp", light_wing_right_low_hue_amp);
load_val(doc, "light_wing_right_low_hue_per", light_wing_right_low_hue_per);
load_val(doc, "light_woofer_hue_amp", light_woofer_hue_amp);
load_val(doc, "light_woofer_hue_per", light_woofer_hue_per);
load_val(doc, "light_controller_hue_amp", light_controller_hue_amp);
load_val(doc, "light_controller_hue_per", light_controller_hue_per);
load_val(doc, "light_generator_hue_amp", light_generator_hue_amp);
load_val(doc, "light_generator_hue_per", light_generator_hue_per);
load_val(doc, "scan_service", scan_service);
load_val(doc, "scan_test", scan_test);
load_val(doc, "scan_coin_mech", scan_coin_mech);
load_val(doc, "scan_bt_a", scan_bt_a);
load_val(doc, "scan_bt_b", scan_bt_b);
load_val(doc, "scan_bt_c", scan_bt_c);
load_val(doc, "scan_bt_d", scan_bt_d);
load_val(doc, "scan_fx_l", scan_fx_l);
load_val(doc, "scan_fx_r", scan_fx_r);
load_val(doc, "scan_start", scan_start);
load_val(doc, "scan_headphone", scan_headphone);
load_val(doc, "scan_vol_l_left", scan_vol_l_left);
load_val(doc, "scan_vol_l_right", scan_vol_l_right);
load_val(doc, "scan_vol_r_left", scan_vol_r_left);
load_val(doc, "scan_vol_r_right", scan_vol_r_right);
load_val(doc, "scan_icca", scan_icca);
load_val(doc, "scan_coin", scan_coin);
load_val(doc, "scan_kp_0", scan_kp_0);
load_val(doc, "scan_kp_1", scan_kp_1);
load_val(doc, "scan_kp_2", scan_kp_2);
load_val(doc, "scan_kp_3", scan_kp_3);
load_val(doc, "scan_kp_4", scan_kp_4);
load_val(doc, "scan_kp_5", scan_kp_5);
load_val(doc, "scan_kp_6", scan_kp_6);
load_val(doc, "scan_kp_7", scan_kp_7);
load_val(doc, "scan_kp_8", scan_kp_8);
load_val(doc, "scan_kp_9", scan_kp_9);
load_val(doc, "scan_kp_00", scan_kp_00);
load_val(doc, "scan_kp_decimal", scan_kp_decimal);
}
ImVec4 KFControl::hsv_transform(ImVec4 col, float hue, float sat, float val) {
ImVec4 hsv;
ImGui::ColorConvertRGBtoHSV(col.x, col.y, col.z, hsv.x, hsv.y, hsv.z);
hsv.x += hue;
if (hsv.x < 0.f) {
hsv.x = 1.f + hsv.x;
}
if (hsv.x > 1.f) {
hsv.x = fmodf(hsv.x, 1.f);
}
hsv.y *= sat;
hsv.z *= val;
ImGui::ColorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, col.x, col.y, col.z);
return col;
}
ImVec4 KFControl::hue_shift(ImVec4 col, float amp, float per, uint64_t ms) {
if (light_hue_disable) return col;
if (fabs(amp) < 0.001f) return col;
if (per < 0.001f) per = 0.001f;
uint64_t per_ms = per * 1000;
auto per_cur = (ms % per_ms) / (float) per_ms;
auto amp_cur = 0.f;
switch (light_hue_func) {
default:
case HueFunc::Sine:
amp_cur = sinf(per_cur * (M_PI * 2)) * amp;
break;
case HueFunc::Cosine:
amp_cur = cosf(per_cur * (M_PI * 2)) * amp;
break;
case HueFunc::Absolute:
if (per_cur < 0.5f)
amp_cur = per_cur * 2 * amp - amp * 0.5f;
else
amp_cur = (1.f - (per_cur - 0.5f) * 2) * amp - amp * 0.5f;
break;
case HueFunc::Linear:
amp_cur = per_cur * amp - amp * 0.5f;
break;
}
return hsv_transform(col, amp_cur);
}
void KFControl::build_content() {
auto time_cur = get_system_milliseconds();
// if standalone then fullscreen window
if (cfg::CONFIGURATOR_STANDALONE) {
ImGui::SetWindowPos(ImVec2(0, 0));
ImGui::SetWindowSize(ImGui::GetIO().DisplaySize);
}
// get IO pointers
auto buttons = games::get_buttons("Sound Voltex");
auto analogs = games::get_analogs("Sound Voltex");
//auto lights = games::get_lights("Sound Voltex");
// get button states
auto service_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::Service));
auto test_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::Test));
auto coin_mech_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::CoinMech));
auto bt_a_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::BT_A));
auto bt_b_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::BT_B));
auto bt_c_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::BT_C));
auto bt_d_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::BT_D));
auto fx_l_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::FX_L));
auto fx_r_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::FX_R));
auto start_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::Start));
auto headphone_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::Headphone));
// get analog states
auto vol_l_state = Analogs::getState(RI_MGR, analogs->at(games::sdvx::Analogs::VOL_L));
auto vol_r_state = Analogs::getState(RI_MGR, analogs->at(games::sdvx::Analogs::VOL_R));
// tab bar
if (!ImGui::BeginTabBar("KFControl")) {
return;
}
// display API state
if (ImGui::BeginTabItem("API")) {
if (API_CONTROLLER == nullptr) {
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "Warning: No API server running!");
ImGui::HelpMarker("Please specify \"-api [PORT]\" / \"-apipass [PASS] \" command line arguments.");
ImGui::Separator();
} else {
std::vector<api::ClientState> client_states;
API_CONTROLLER->obtain_client_states(&client_states);
// show ip addresses
auto ip_addresses = netutils::get_local_addresses();
if (!ip_addresses.empty()) {
ImGui::SetNextItemOpen(true, ImGuiCond_Once);
if (ImGui::TreeNode("Local IP-Addresses")) {
for (auto &adr : ip_addresses) {
ImGui::BulletText("%s", adr.c_str());
}
ImGui::TreePop();
}
}
// client count
ImGui::Text("Connected clients: %u", (unsigned int) client_states.size());
// iterate clients
for (auto &client : client_states) {
auto address = API_CONTROLLER->get_ip_address(client.address);
if (ImGui::TreeNode(("Client @ " + address).c_str())) {
if (client.password.empty()) {
ImGui::Text("No password set.");
} else {
ImGui::Text("Password set.");
}
if (ImGui::TreeNode("Modules")) {
for (auto &module : client.modules) {
if (ImGui::TreeNode(module->name.c_str())) {
ImGui::Text("Password force: %i", module->password_force);
ImGui::TreePop();
}
}
ImGui::TreePop();
}
ImGui::TreePop();
}
}
}
ImGui::EndTabItem();
}
// display input states
if (ImGui::BeginTabItem("Input")) {
// text view
ImGui::Text("Service: %s", service_state ? "ON" : "OFF");
ImGui::Text("Test: %s", test_state ? "ON" : "OFF");
ImGui::Text("Coin Mech: %s", coin_mech_state ? "ON" : "OFF");
ImGui::Text("BT-A: %s", bt_a_state ? "ON" : "OFF");
ImGui::Text("BT-B: %s", bt_b_state ? "ON" : "OFF");
ImGui::Text("BT-C: %s", bt_c_state ? "ON" : "OFF");
ImGui::Text("BT-D: %s", bt_d_state ? "ON" : "OFF");
ImGui::Text("FX-L: %s", fx_l_state ? "ON" : "OFF");
ImGui::Text("FX-R: %s", fx_r_state ? "ON" : "OFF");
ImGui::Text("Start: %s", start_state ? "ON" : "OFF");
ImGui::Text("Headphone: %s", headphone_state ? "ON" : "OFF");
// labeled knobs
ImGui::Knob(vol_l_state, 39.f);
ImGui::SameLine();
ImGui::Knob(vol_r_state, 39.f);
ImGui::Text("VOL-L VOL-R");
// prepare controller view
auto draw = ImGui::GetWindowDrawList();
float bt_space = 50;
ImVec2 bt_a_pos(175 + 0 * bt_space, 140);
ImVec2 bt_b_pos(175 + 1 * bt_space, 140);
ImVec2 bt_c_pos(175 + 2 * bt_space, 140);
ImVec2 bt_d_pos(175 + 3 * bt_space, 140);
ImVec2 fx_l_pos((bt_a_pos.x + bt_b_pos.x) * 0.5f, bt_a_pos.y + bt_space);
ImVec2 fx_r_pos((bt_c_pos.x + bt_d_pos.x) * 0.5f, bt_d_pos.y + bt_space);
ImVec2 start_pos((bt_b_pos.x + bt_c_pos.x) * 0.5f, bt_b_pos.y - bt_space);
ImVec2 vol_l_pos(bt_a_pos.x - bt_space * 1.00f, bt_a_pos.y - bt_space * 1.25f);
ImVec2 vol_r_pos(bt_d_pos.x + bt_space * 0.25f, bt_d_pos.y - bt_space * 1.25f);
ImColor col_off(1.f, 1.f, 1.f, 1.f);
ImColor col_on(1.f, 0.3f, 0.3f, 1.f);
// abcd buttons
draw->AddRectFilled(
ImVec2(bt_a_pos.x - 15, bt_a_pos.y - 15),
ImVec2(bt_a_pos.x + 15, bt_a_pos.y + 15),
bt_a_state ? col_on : col_off);
draw->AddRectFilled(
ImVec2(bt_b_pos.x - 15, bt_b_pos.y - 15),
ImVec2(bt_b_pos.x + 15, bt_b_pos.y + 15),
bt_b_state ? col_on : col_off);
draw->AddRectFilled(
ImVec2(bt_c_pos.x - 15, bt_c_pos.y - 15),
ImVec2(bt_c_pos.x + 15, bt_c_pos.y + 15),
bt_c_state ? col_on : col_off);
draw->AddRectFilled(
ImVec2(bt_d_pos.x - 15, bt_d_pos.y - 15),
ImVec2(bt_d_pos.x + 15, bt_d_pos.y + 15),
bt_d_state ? col_on : col_off);
// fx buttons
draw->AddRectFilled(
ImVec2(fx_l_pos.x - 20, fx_l_pos.y - 10),
ImVec2(fx_l_pos.x + 20, fx_l_pos.y + 10),
fx_l_state ? col_on : col_off);
draw->AddRectFilled(
ImVec2(fx_r_pos.x - 20, fx_r_pos.y - 10),
ImVec2(fx_r_pos.x + 20, fx_r_pos.y + 10),
fx_r_state ? col_on : col_off);
// start button
draw->AddRectFilled(
ImVec2(start_pos.x - 10, start_pos.y - 10),
ImVec2(start_pos.x + 10, start_pos.y + 10),
start_state ? col_on : col_off);
// knobs
ImGui::Knob(vol_l_state, bt_space * 0.75f, 2.f,
vol_l_pos.x, vol_l_pos.y);
ImGui::Knob(vol_r_state, bt_space * 0.75f, 2.f,
vol_r_pos.x, vol_r_pos.y);
// end tab
ImGui::EndTabItem();
}
// display light states
if (ImGui::BeginTabItem("Lights")) {
// set up columns
ImGui::Columns(3, "LightColumns", false);
ImGui::SetColumnWidth(0, 60);
ImGui::SetColumnWidth(1, 60);
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Hue Amp");
ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Hue Per");
ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Light Color");
ImGui::NextColumn();
ImGui::Separator();
// lock data
worker_m.lock();
// wing left up light
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue00", (float *) &light_wing_left_up_hue_amp, 0.001f, 0.f, 16.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue01", (float *) &light_wing_left_up_hue_per, 0.001f, 0.001f, 60.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(150);
if (light_hue_preview) {
auto shift = hue_shift(light_wing_left_up,
light_wing_left_up_hue_amp,
light_wing_left_up_hue_per,
time_cur);
ImGui::ColorEdit3("Wing Left Up", (float *) &shift);
} else {
ImGui::ColorEdit3("Wing Left Up", (float *) &light_wing_left_up);
}
ImGui::NextColumn();
// wing left low light
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue10", (float *) &light_wing_left_low_hue_amp, 0.001f, 0.f, 16.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue11", (float *) &light_wing_left_low_hue_per, 0.001f, 0.001f, 60.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(150);
if (light_hue_preview) {
auto shift = hue_shift(light_wing_left_low,
light_wing_left_low_hue_amp,
light_wing_left_low_hue_per,
time_cur);
ImGui::ColorEdit3("Wing Left Low", (float *) &shift);
} else {
ImGui::ColorEdit3("Wing Left Low", (float *) &light_wing_left_low);
}
ImGui::NextColumn();
// wing right up light
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue20", (float *) &light_wing_right_up_hue_amp, 0.001f, 0.f, 16.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue21", (float *) &light_wing_right_up_hue_per, 0.001f, 0.001f, 60.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(150);
if (light_hue_preview) {
auto shift = hue_shift(light_wing_right_up,
light_wing_right_up_hue_amp,
light_wing_right_up_hue_per,
time_cur);
ImGui::ColorEdit3("Wing Right Up", (float *) &shift);
} else {
ImGui::ColorEdit3("Wing Right Up", (float *) &light_wing_right_up);
}
ImGui::NextColumn();
// wing right low light
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue30", (float *) &light_wing_right_low_hue_amp, 0.001f, 0.f, 16.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue31", (float *) &light_wing_right_low_hue_per, 0.001f, 0.001f, 60.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(150);
if (light_hue_preview) {
auto shift = hue_shift(light_wing_right_low,
light_wing_right_low_hue_amp,
light_wing_right_low_hue_per,
time_cur);
ImGui::ColorEdit3("Wing Right Low", (float *) &shift);
} else {
ImGui::ColorEdit3("Wing Right Low", (float *) &light_wing_right_low);
}
ImGui::NextColumn();
// woofer light
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue40", (float *) &light_woofer_hue_amp, 0.001f, 0.f, 16.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue41", (float *) &light_woofer_hue_per, 0.001f, 0.001f, 60.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(150);
if (light_hue_preview) {
auto shift = hue_shift(light_woofer,
light_woofer_hue_amp,
light_woofer_hue_per,
time_cur);
ImGui::ColorEdit3("Woofer", (float *) &shift);
} else {
ImGui::ColorEdit3("Woofer", (float *) &light_woofer);
}
ImGui::NextColumn();
// controller light
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue50", (float *) &light_controller_hue_amp, 0.001f, 0.f, 16.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue51", (float *) &light_controller_hue_per, 0.001f, 0.001f, 60.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(150);
if (light_hue_preview) {
auto shift = hue_shift(light_controller,
light_controller_hue_amp,
light_controller_hue_per,
time_cur);
ImGui::ColorEdit3("Controller", (float *) &shift);
} else {
ImGui::ColorEdit3("Controller", (float *) &light_controller);
}
ImGui::NextColumn();
// generator light
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue60", (float *) &light_generator_hue_amp, 0.001f, 0.f, 16.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(50);
ImGui::DragFloat("###hue61", (float *) &light_generator_hue_per, 0.001f, 0.001f, 60.f, "%.3f", 2.f);
ImGui::NextColumn();
ImGui::SetNextItemWidth(150);
if (light_hue_preview) {
auto shift = hue_shift(light_generator,
light_generator_hue_amp,
light_generator_hue_per,
time_cur);
ImGui::ColorEdit3("Generator", (float *) &shift);
} else {
ImGui::ColorEdit3("Generator", (float *) &light_generator);
}
ImGui::NextColumn();
// clean
worker_m.unlock();
ImGui::Columns();
// settings
ImGui::Text("Animation Settings");
const char *HUE_FUNCS[] = {"Sine", "Cosine", "Absolute", "Linear", "Count"};
if (ImGui::BeginCombo("Hue Function", HUE_FUNCS[(int) light_hue_func])) {
for (int i = 0; i < (int) HueFunc::Count; i++) {
bool selected = i == (int) light_hue_func;
if (ImGui::Selectable(HUE_FUNCS[i], selected)) {
light_hue_func = (HueFunc) i;
}
if (selected) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::Checkbox("Hue Preview", &light_hue_preview);
ImGui::SameLine();
ImGui::Checkbox("Hue Disable", &light_hue_disable);
// end tab
ImGui::EndTabItem();
}
// display volume states
if (ImGui::BeginTabItem("Volumes")) {
ImGui::Text(
"Sound Amplifier\n"
"---------------\n");
ImGui::Text("Sound");
ImGui::SliderFloat("###Sound", &vol_sound, 0.f, 1.f, "%.2f");
ImGui::Text("Headphone");
ImGui::SliderFloat("###Headphone", &vol_headphone, 0.f, 1.f, "%.2f");
ImGui::Text("External");
ImGui::SliderFloat("###External", &vol_external, 0.f, 1.f, "%.2f");
ImGui::Text("Woofer");
ImGui::SliderFloat("###Woofer", &vol_woofer, 0.f, 1.f, "%.2f");
ImGui::Dummy(ImVec2(0.f, 5.f));
ImGui::Separator();
ImGui::Text("Settings");
ImGui::Checkbox("Mute", &vol_mute);
ImGui::EndTabItem();
}
// keys
if (ImGui::BeginTabItem("Keys")) {
ImGui::Text("Input Configuration");
if (ImGui::BeginChild("Buttons")) {
ImGui::SliderInt("Service", &scan_service, 0, 127, key_format(scan_service).c_str());
ImGui::SliderInt("Test", &scan_test, 0, 127, key_format(scan_test).c_str());
ImGui::SliderInt("Coin Mech", &scan_coin_mech, 0, 127, key_format(scan_coin_mech).c_str());
ImGui::SliderInt("BT-A", &scan_bt_a, 0, 127, key_format(scan_bt_a).c_str());
ImGui::SliderInt("BT-B", &scan_bt_b, 0, 127, key_format(scan_bt_b).c_str());
ImGui::SliderInt("BT-C", &scan_bt_c, 0, 127, key_format(scan_bt_c).c_str());
ImGui::SliderInt("BT-D", &scan_bt_d, 0, 127, key_format(scan_bt_d).c_str());
ImGui::SliderInt("FX-L", &scan_fx_l, 0, 127, key_format(scan_fx_l).c_str());
ImGui::SliderInt("FX-R", &scan_fx_r, 0, 127, key_format(scan_fx_r).c_str());
ImGui::SliderInt("Start", &scan_start, 0, 127, key_format(scan_start).c_str());
ImGui::SliderInt("Headphone", &scan_headphone, 0, 127, key_format(scan_headphone).c_str());
ImGui::SliderInt("VOL-L Left", &scan_vol_l_left, 0, 127, key_format(scan_vol_l_left).c_str());
ImGui::SliderInt("VOL-L Right", &scan_vol_l_right, 0, 127, key_format(scan_vol_l_right).c_str());
ImGui::SliderInt("VOL-R Left", &scan_vol_r_left, 0, 127, key_format(scan_vol_r_left).c_str());
ImGui::SliderInt("VOL-R Right", &scan_vol_r_right, 0, 127, key_format(scan_vol_r_right).c_str());
ImGui::SliderInt("Card Swipe", &scan_icca, 0, 127, key_format(scan_icca).c_str());
ImGui::SliderInt("Coin Insert", &scan_coin, 0, 127, key_format(scan_coin).c_str());
ImGui::SliderInt("Keypad 0", &scan_kp_0, 0, 127, key_format(scan_kp_0).c_str());
ImGui::SliderInt("Keypad 1", &scan_kp_1, 0, 127, key_format(scan_kp_1).c_str());
ImGui::SliderInt("Keypad 2", &scan_kp_2, 0, 127, key_format(scan_kp_2).c_str());
ImGui::SliderInt("Keypad 3", &scan_kp_3, 0, 127, key_format(scan_kp_3).c_str());
ImGui::SliderInt("Keypad 4", &scan_kp_4, 0, 127, key_format(scan_kp_4).c_str());
ImGui::SliderInt("Keypad 5", &scan_kp_5, 0, 127, key_format(scan_kp_5).c_str());
ImGui::SliderInt("Keypad 6", &scan_kp_6, 0, 127, key_format(scan_kp_6).c_str());
ImGui::SliderInt("Keypad 7", &scan_kp_7, 0, 127, key_format(scan_kp_7).c_str());
ImGui::SliderInt("Keypad 8", &scan_kp_8, 0, 127, key_format(scan_kp_8).c_str());
ImGui::SliderInt("Keypad 9", &scan_kp_9, 0, 127, key_format(scan_kp_9).c_str());
ImGui::SliderInt("Keypad 00", &scan_kp_00, 0, 127, key_format(scan_kp_00).c_str());
ImGui::SliderInt("Keypad Dec", &scan_kp_decimal, 0, 127, key_format(scan_kp_decimal).c_str());
}
ImGui::EndChild();
ImGui::EndTabItem();
}
// opt
if (ImGui::BeginTabItem("Opt")) {
ImGui::Text("Tweaking");
ImGui::SameLine();
ImGui::HelpMarker("Change these settings on your own risk.\nReset if you messed up.");
ImGui::DragFloat("Knob Deadzone", &vol_deadzone, 0.0001f, 0.f, 1.f, "%.4f");
ImGui::DragFloat("Mouse Sensitivity", &vol_mouse_sensitivity, 1.f, 0.f, 0.f, "%.0f");
ImGui::DragInt("Knob Timeout (ms)", (int*) &vol_timeout, 1, 0, 2000);
ImGui::DragInt("Card Timeout (ms)", (int*) &icca_timeout, 1, 0, 2000);
ImGui::DragInt("Coin Timeout (ms)", (int*) &coin_timeout, 1, 0, 2000);
ImGui::DragInt("Poll Delay (ms)", (int*) &poll_delay, 0.01f, 0, 10);
ImGui::Separator();
ImGui::Text("Card Swipe Write ID to File");
ImGui::InputText("###icca_file", icca_file, sizeof(icca_file));
ImGui::SameLine();
if (ImGui::Button("...")) {
std::thread thread([this] {
// open dialog to get path
auto ofn_path = std::make_unique<wchar_t[]>(sizeof(icca_file));
OPENFILENAMEW ofn {};
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = nullptr;
ofn.lpstrFilter = L"";
ofn.lpstrFile = ofn_path.get();
ofn.nMaxFile = sizeof(icca_file);
ofn.Flags = OFN_EXPLORER;
ofn.lpstrDefExt = L"txt";
// check for success
if (GetSaveFileNameW(&ofn)) {
// update path
std::string path = std::filesystem::path(ofn_path.get()).string();
if (path.length() < sizeof(icca_file)) {
strcpy(icca_file, path.c_str());
}
} else {
auto error = CommDlgExtendedError();
if (error) {
log_warning("kfcontrol", "failed to get save file name: {}", error);
} else {
log_warning("kfcontrol", "failed to get save file name");
}
}
});
thread.detach();
}
ImGui::Separator();
ImGui::Checkbox("Button Lights", &light_buttons);
ImGui::SameLine();
ImGui::HelpMarker("Lights up buttons when pressed.");
ImGui::Checkbox("Keypad Profile Switch", &kp_profiles);
ImGui::SameLine();
ImGui::HelpMarker("Changes the profile on number press.\nDiscards unsaved settings.");
ImGui::EndTabItem();
}
// misc
if (ImGui::BeginTabItem("Misc")) {
/*
* LCD Settings
*/
ImGui::Text("LCD Settings");
int lcd_bri = games::shared::LCD_BRI;
int lcd_con = games::shared::LCD_CON;
int lcd_bl = games::shared::LCD_BL;
int lcd_red = games::shared::LCD_RED;
int lcd_green = games::shared::LCD_GREEN;
int lcd_blue = games::shared::LCD_BLUE;
if (ImGui::SliderInt("Brightness", &lcd_bri, 0, 255)) {
games::shared::LCD_BRI = lcd_bri;
}
if (ImGui::SliderInt("Contrast", &lcd_con, 0, 255)) {
games::shared::LCD_CON = lcd_con;
}
if (ImGui::SliderInt("Black Value", &lcd_bl, 0, 255)) {
games::shared::LCD_BL = lcd_bl;
}
if (ImGui::SliderInt("Red Value", &lcd_red, 0, 255)) {
games::shared::LCD_RED = lcd_red;
}
if (ImGui::SliderInt("Green Value", &lcd_green, 0, 255)) {
games::shared::LCD_GREEN = lcd_green;
}
if (ImGui::SliderInt("Blue Value", &lcd_blue, 0, 255)) {
games::shared::LCD_BLUE = lcd_blue;
}
// settings
ImGui::Separator();
ImGui::Text("Settings");
auto block = eamuse_coin_get_block();
if (ImGui::Checkbox("Coin Blocker", &block)) {
eamuse_coin_set_block(block);
}
ImGui::SameLine();
ImGui::Checkbox("Knobs Mouse", &vol_mouse);
ImGui::SameLine();
ImGui::Checkbox("Start Click", &start_click);
// save/load
ImGui::Separator();
ImGui::Text("Configuration");
if (ImGui::Button("Save")) {
config_save();
}
ImGui::SameLine();
if (ImGui::Button("Load")) {
config_load();
}
ImGui::SameLine();
if (ImGui::Button("Reset")) {
fileutils::text_write(config_path, "");
launcher::restart();
}
ImGui::SameLine();
ImGui::Checkbox("Overlay", &OVERLAY->hotkeys_enable);
ImGui::SameLine();
ImGui::SetNextItemWidth(80);
if (ImGui::InputInt("Profile", &config_profile, 1, 0)) {
if (config_profile < 0) config_profile = 0;
this->config_load();
}
ImGui::EndTabItem();
}
// about
if (ImGui::BeginTabItem("About")) {
ImGui::Text(
"Description\n"
"-----------\n");
ImGui::Text(
//"------------------------------------------------------"
"This tool allows you to remotely control your\n"
"KFChicken powered cabinet.");
ImGui::Text(
//"------------------------------------------------------"
"Buttons, knobs, card swipes and coins are mapped to\n"
"your keyboard using the SendInput Windows API. Using\n"
"this you can play K-Shoot MANIA and USC using your\n"
"legit Sound Voltex cabinet.\n");
ImGui::Text(
//"------------------------------------------------------"
"You can also use this to receive remote input from\n"
"the SpiceCompanion app.\n");
ImGui::Dummy(ImVec2(0, 5));
ImGui::Text(
"Basic Usage\n"
"-----------\n");
ImGui::Text(
//"------------------------------------------------------"
"Start KFControl using the '-api [PORT]' and\n"
"'-apipass [PASS]' parameters and point your\n"
"cabinet running KFChicken to this computer\n"
"using the specified settings.");
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
void KFControl::worker_start() {
worker_running = true;
this->worker.reset(new std::thread(&KFControl::worker_func, this));
}
void KFControl::worker_func() {
// get IO pointers
auto buttons = games::get_buttons("Sound Voltex");
auto analogs = games::get_analogs("Sound Voltex");
auto lights = games::get_lights("Sound Voltex");
// button states
bool service_old = false;
bool test_old = false;
bool coin_mech_old = false;
bool bt_a_old = false;
bool bt_b_old = false;
bool bt_c_old = false;
bool bt_d_old = false;
bool fx_l_old = false;
bool fx_r_old = false;
bool start_old = false;
bool start_click_old = false;
bool headphone_old = false;
bool kp_0_old = false;
bool kp_1_old = false;
bool kp_2_old = false;
bool kp_3_old = false;
bool kp_4_old = false;
bool kp_5_old = false;
bool kp_6_old = false;
bool kp_7_old = false;
bool kp_8_old = false;
bool kp_9_old = false;
bool kp_00_old = false;
bool kp_decimal_old = false;
// volume states
float vol_l_old = Analogs::getState(RI_MGR, analogs->at(games::sdvx::Analogs::VOL_L));
float vol_r_old = Analogs::getState(RI_MGR, analogs->at(games::sdvx::Analogs::VOL_R));
int vol_l_trigger = 0;
int vol_r_trigger = 0;
uint64_t vol_l_time = 0;
uint64_t vol_r_time = 0;
// icca/coin states
bool icca_old = false;
bool coin_old = false;
uint64_t icca_time = 0;
uint64_t coin_time = 0;
// main loop
while (worker_running) {
uint64_t time_cur = get_system_milliseconds();
// update button states
auto service_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::Service));
worker_button_check(service_state, &service_old, scan_service);
auto test_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::Test));
worker_button_check(test_state, &test_old, scan_test);
auto coin_mech_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::CoinMech));
worker_button_check(coin_mech_state, &coin_mech_old, scan_coin_mech);
auto bt_a_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::BT_A));
worker_button_check(bt_a_state, &bt_a_old, scan_bt_a);
auto bt_b_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::BT_B));
worker_button_check(bt_b_state, &bt_b_old, scan_bt_b);
auto bt_c_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::BT_C));
worker_button_check(bt_c_state, &bt_c_old, scan_bt_c);
auto bt_d_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::BT_D));
worker_button_check(bt_d_state, &bt_d_old, scan_bt_d);
auto fx_l_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::FX_L));
worker_button_check(fx_l_state, &fx_l_old, scan_fx_l);
auto fx_r_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::FX_R));
worker_button_check(fx_r_state, &fx_r_old, scan_fx_r);
auto start_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::Start));
worker_button_check(start_state, &start_old, scan_start);
auto headphone_state = Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::Headphone));
worker_button_check(headphone_state, &headphone_old, scan_headphone);
// mouse click on start button
if (start_click) {
if (start_state) {
if (!start_click_old) {
start_click_old = true;
worker_mouse_click(true);
}
} else {
if (start_click_old) {
start_click_old = false;
worker_mouse_click(false);
}
}
}
// get analog states
auto vol_l_state = Analogs::getState(RI_MGR, analogs->at(games::sdvx::Analogs::VOL_L));
auto vol_r_state = Analogs::getState(RI_MGR, analogs->at(games::sdvx::Analogs::VOL_R));
auto vol_l_diff = vol_l_state - vol_l_old;
auto vol_r_diff = vol_r_state - vol_r_old;
auto vol_l_delta = time_cur - vol_l_time;
auto vol_r_delta = time_cur - vol_r_time;
// wrap around
if (vol_l_diff > 0.5f) {
vol_l_diff = -vol_l_old - (1 - vol_l_state);
}
if (vol_l_diff < -0.5f) {
vol_l_diff = (1 - vol_l_old) + vol_l_state;
}
if (vol_r_diff > 0.5f) {
vol_r_diff = -vol_r_old - (1 - vol_r_state);
}
if (vol_r_diff < -0.5f) {
vol_r_diff = (1 - vol_r_old) + vol_r_state;
}
// manual volume controls
if (Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::VOL_L_Left))) {
vol_l_diff += vol_deadzone * -2;
}
if (Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::VOL_L_Right))) {
vol_l_diff += vol_deadzone * 2;
}
if (Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::VOL_R_Left))) {
vol_r_diff += vol_deadzone * -2;
}
if (Buttons::getState(RI_MGR, buttons->at(games::sdvx::Buttons::VOL_R_Right))) {
vol_r_diff += vol_deadzone * 2;
}
// left knob
if (std::abs(vol_l_diff) > vol_deadzone || vol_l_delta < vol_timeout) {
if (std::abs(vol_l_diff) > vol_deadzone) {
vol_l_time = time_cur;
}
vol_l_old = vol_l_state;
if (vol_l_diff < 0 && vol_l_trigger != scan_vol_l_left) {
if (vol_l_trigger != 0) worker_button_set(vol_l_trigger, false);
vol_l_trigger = scan_vol_l_left;
worker_button_set(vol_l_trigger, true);
} else if (vol_l_diff > 0 && vol_l_trigger != scan_vol_l_right) {
if (vol_l_trigger != 0) worker_button_set(vol_l_trigger, false);
vol_l_trigger = scan_vol_l_right;
worker_button_set(vol_l_trigger, true);
}
if (vol_mouse) {
worker_mouse_move(vol_l_diff * vol_mouse_sensitivity, 0);
}
} else {
vol_l_time = 0;
if (vol_l_trigger != 0) {
worker_button_set(vol_l_trigger, false);
vol_l_trigger = 0;
}
}
// right knob
if (std::abs(vol_r_diff) > vol_deadzone || vol_r_delta < vol_timeout) {
if (std::abs(vol_r_diff) > vol_deadzone) {
vol_r_time = time_cur;
}
vol_r_old = vol_r_state;
if (vol_r_diff < 0 && vol_r_trigger != scan_vol_r_left) {
if (vol_r_trigger != 0) worker_button_set(vol_r_trigger, false);
vol_r_trigger = scan_vol_r_left;
worker_button_set(vol_r_trigger, true);
} else if (vol_r_diff > 0 && vol_r_trigger != scan_vol_r_right) {
if (vol_r_trigger != 0) worker_button_set(vol_r_trigger, false);
vol_r_trigger = scan_vol_r_right;
worker_button_set(vol_r_trigger, true);
}
if (vol_mouse) {
worker_mouse_move(0, vol_r_diff * vol_mouse_sensitivity);
}
} else {
vol_r_time = 0;
if (vol_r_trigger != 0) {
worker_button_set(vol_r_trigger, false);
vol_r_trigger = 0;
}
}
// update icca
if (eamuse_card_insert_consume(1, 0)) {
icca_time = time_cur;
// write card ID to file
if (strlen(icca_file) > 0) {
uint8_t card_data[8];
if (eamuse_get_card(1, 0, card_data)) {
auto card_hex = bin2hex(card_data, sizeof(card_data));
fileutils::text_write(icca_file, card_hex);
log_info("kfcontrol", "wrote {} to file", card_hex);
}
}
}
if (time_cur - icca_time < icca_timeout) {
if (!icca_old) {
worker_button_set(scan_icca, true);
icca_old = true;
}
} else if (icca_old) {
worker_button_set(scan_icca, false);
icca_old = false;
}
// update coin
if (eamuse_coin_consume_stock()) {
coin_time = time_cur;
}
if (time_cur - coin_time < coin_timeout) {
if (!coin_old) {
worker_button_set(scan_coin, true);
coin_old = true;
}
} else if (icca_old) {
worker_button_set(scan_coin, false);
coin_old = false;
}
// light hue shifts
auto hue_wing_left_up = hue_shift(
light_wing_left_up,
light_wing_left_up_hue_amp,
light_wing_left_up_hue_per,
time_cur);
auto hue_wing_left_low = hue_shift(
light_wing_left_low,
light_wing_left_low_hue_amp,
light_wing_left_low_hue_per,
time_cur);
auto hue_wing_right_up = hue_shift(
light_wing_right_up,
light_wing_right_up_hue_amp,
light_wing_right_up_hue_per,
time_cur);
auto hue_wing_right_low = hue_shift(
light_wing_right_low,
light_wing_right_low_hue_amp,
light_wing_right_low_hue_per,
time_cur);
auto hue_woofer = hue_shift(
light_woofer,
light_woofer_hue_amp,
light_woofer_hue_per,
time_cur);
auto hue_controller = hue_shift(
light_controller,
light_controller_hue_amp,
light_controller_hue_per,
time_cur);
auto hue_generator = hue_shift(
light_generator,
light_generator_hue_amp,
light_generator_hue_per,
time_cur);
// update lights
worker_m.lock();
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_LEFT_UP_R), hue_wing_left_up.x);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_LEFT_UP_G), hue_wing_left_up.y);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_LEFT_UP_B), hue_wing_left_up.z);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_LEFT_LOW_R), hue_wing_left_low.x);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_LEFT_LOW_G), hue_wing_left_low.y);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_LEFT_LOW_B), hue_wing_left_low.z);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_RIGHT_UP_R), hue_wing_right_up.x);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_RIGHT_UP_G), hue_wing_right_up.y);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_RIGHT_UP_B), hue_wing_right_up.z);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_RIGHT_LOW_R), hue_wing_right_low.x);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_RIGHT_LOW_G), hue_wing_right_low.y);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WING_RIGHT_LOW_B), hue_wing_right_low.z);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WOOFER_R), hue_woofer.x);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WOOFER_G), hue_woofer.y);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::WOOFER_B), hue_woofer.z);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::CONTROLLER_R), hue_controller.x);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::CONTROLLER_G), hue_controller.y);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::CONTROLLER_B), hue_controller.z);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::GENERATOR_R), hue_generator.x);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::GENERATOR_G), hue_generator.y);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::GENERATOR_B), hue_generator.z);
if (vol_mute) {
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::VOLUME_SOUND), 0.f);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::VOLUME_HEADPHONE), 0.f);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::VOLUME_EXTERNAL), 0.f);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::VOLUME_WOOFER), 0.f);
} else {
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::VOLUME_SOUND), vol_sound);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::VOLUME_HEADPHONE), vol_headphone);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::VOLUME_EXTERNAL), vol_external);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::VOLUME_WOOFER), vol_woofer);
}
worker_m.unlock();
// button lights
if (light_buttons) {
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::BT_A), bt_a_state ? 1.f : 0.f);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::BT_B), bt_b_state ? 1.f : 0.f);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::BT_C), bt_c_state ? 1.f : 0.f);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::BT_D), bt_d_state ? 1.f : 0.f);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::FX_L), fx_l_state ? 1.f : 0.f);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::FX_R), fx_r_state ? 1.f : 0.f);
Lights::writeLight(RI_MGR, lights->at(games::sdvx::Lights::START), start_state ? 1.f : 0.f);
}
// update devices
RI_MGR->devices_flush_output();
// check keypad for input
auto keypad_state = eamuse_get_keypad_state(0);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_0), &kp_0_old, scan_kp_0, 0);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_1), &kp_1_old, scan_kp_1, 1);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_2), &kp_2_old, scan_kp_2, 2);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_3), &kp_3_old, scan_kp_3, 3);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_4), &kp_4_old, scan_kp_4, 4);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_5), &kp_5_old, scan_kp_5, 5);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_6), &kp_6_old, scan_kp_6, 6);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_7), &kp_7_old, scan_kp_7, 7);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_8), &kp_8_old, scan_kp_8, 8);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_9), &kp_9_old, scan_kp_9, 9);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_00), &kp_00_old, scan_kp_00);
worker_button_check(keypad_state & (1 << EAM_IO_KEYPAD_DECIMAL), &kp_decimal_old, scan_kp_decimal);
// slow down
std::this_thread::sleep_for(std::chrono::milliseconds(poll_delay));
}
}
void KFControl::worker_button_check(bool state_new, bool *state_old, int scan, int profile) {
// check for trigger
if (state_new != *state_old) {
// set button state
if (scan != 0) {
worker_button_set(scan, state_new);
}
// switch profile
if (kp_profiles && profile >= 0 && state_new) {
config_profile = profile;
config_load();
}
}
// update old state
*state_old = state_new;
}
void KFControl::worker_button_set(int scan, bool state) {
INPUT ip;
ip.type = INPUT_KEYBOARD;
ip.ki.wScan = scan;
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;
ip.ki.wVk = 0;
ip.ki.dwFlags = KEYEVENTF_SCANCODE | (state ? 0 : KEYEVENTF_KEYUP);
SendInput(1, &ip, sizeof(INPUT));
}
void KFControl::worker_mouse_click(bool state) {
INPUT ip;
ip.type = INPUT_MOUSE;
ip.mi.dwFlags = state ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
SendInput(1, &ip, sizeof(INPUT));
}
void KFControl::worker_mouse_move(int dx, int dy) {
if (dx == 0 && dy == 0) return;
INPUT ip;
ip.type = INPUT_MOUSE;
ip.mi.dx = dx;
ip.mi.dy = dy;
ip.mi.mouseData = 0;
ip.mi.dwFlags = MOUSEEVENTF_MOVE;
ip.mi.dwExtraInfo = 0;
SendInput(1, &ip, sizeof(INPUT));
}
}