#include "config.h" #include #include #include #include #include "build/defs.h" #include "build/resource.h" #include "cfg/config.h" #include "cfg/configurator.h" #include "external/imgui/imgui_internal.h" #include "games/io.h" #include "avs/core.h" #include "avs/ea3.h" #include "avs/game.h" #include "launcher/launcher.h" #include "launcher/shutdown.h" #include "launcher/options.h" #include "misc/eamuse.h" #include "overlay/imgui/extensions.h" #include "rawinput/piuio.h" #include "rawinput/rawinput.h" #include "rawinput/touch.h" #include "util/fileutils.h" #include "util/logging.h" #include "util/resutils.h" #include "util/time.h" #include "util/utils.h" #ifdef min #undef min #endif namespace overlay::windows { const auto PROJECT_URL = "https://spice2x.github.io"; Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) { this->title = "Configuration"; this->toggle_button = games::OverlayButtons::ToggleConfig; this->init_size = ImVec2(800, 600); this->size_min = ImVec2(100, 200); this->init_pos = ImVec2(0, 0); if (cfg::CONFIGURATOR_STANDALONE && cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) { this->active = true; this->flags |= ImGuiWindowFlags_NoResize; this->flags |= ImGuiWindowFlags_NoMove; this->flags |= ImGuiWindowFlags_NoTitleBar; this->flags |= ImGuiWindowFlags_NoCollapse; this->flags |= ImGuiWindowFlags_NoDecoration; } this->flags |= ImGuiWindowFlags_MenuBar; // build game list auto &game_names = games::get_games(); for (auto &game_name : game_names) { this->games_names.push_back(game_name.c_str()); auto &game = this->games_list.emplace_back(game_name); auto buttons = games::get_buttons(game_name); auto analogs = games::get_analogs(game_name); auto lights = games::get_lights(game_name); if (buttons) { for (auto &item : *buttons) { game.addItems(item); } } if (analogs) { for (auto &item : *analogs) { game.addItems(item); } } if (lights) { for (auto &item : *lights) { game.addItems(item); } } // default to currently running game if (!cfg::CONFIGURATOR_STANDALONE && game_name == eamuse_get_game()) { this->games_selected = games_list.size() - 1; this->games_selected_name = game_name; } // standalone configurator should look for file hints if (cfg::CONFIGURATOR_STANDALONE) { auto file_hints = games::get_game_file_hints(game_name); if (file_hints) { for (auto &file_hint : *file_hints) { if (fileutils::file_exists(file_hint) || fileutils::file_exists(std::filesystem::path("modules") / file_hint) || fileutils::file_exists(std::filesystem::path("contents") / file_hint) || fileutils::file_exists(MODULE_PATH / file_hint)) { this->games_selected = games_list.size() - 1; this->games_selected_name = game_name; eamuse_set_game(game_name); } } } } } // configurator fallback to detected game name if (cfg::CONFIGURATOR_STANDALONE && this->games_selected == -1) { for (size_t i = 0; i < games_names.size(); i++) { if (games_names[i] == eamuse_get_game()) { this->games_selected = i; } } } // add games to the config and window auto &config = ::Config::getInstance(); for (auto &game : games_list) { config.addGame(game); if (!config.getStatus()) { log_warning("config", "failure adding game: {}", game.getGameName()); } } // read card numbers read_card(); } Config::~Config() { } void Config::inc_buttons_many_index(int index_max) { if (this->buttons_many_index == index_max) { this->buttons_many_index = -1; } else { this->buttons_many_index += 1; } } void Config::read_card(int player) { // check if a game is selected if (this->games_selected_name.empty()) { return; } // iterate bindings auto bindings = ::Config::getInstance().getKeypadBindings(this->games_selected_name); for (int p = 0; p < 2; ++p) { if (player < 0 || player == p) { // get path std::filesystem::path path; if (!bindings.card_paths[p].empty()) { path = bindings.card_paths[p]; } else { path = p > 0 ? "card1.txt" : "card0.txt"; } // open file std::ifstream f(path); if (!f || !f.is_open()) { this->keypads_card_number[p][0] = 0; continue; } // get file size f.seekg(0, f.end); auto length = (size_t) f.tellg(); f.seekg(0, f.beg); // read file contents f.read(this->keypads_card_number[p], 16); this->keypads_card_number[p][length < 16 ? length : 16] = 0; f.close(); } } } void Config::write_card(int player) { // get path auto bindings = ::Config::getInstance().getKeypadBindings(this->games_selected_name); std::filesystem::path path; if (!bindings.card_paths[player].empty()) { path = bindings.card_paths[player]; } else { path = player > 0 ? "card1.txt" : "card0.txt"; } // write file std::ofstream f(path); if (f) { f.write(this->keypads_card_number[player], strlen(this->keypads_card_number[player])); f.close(); } } void Config::build_content() { // if standalone then fullscreen window if (cfg::CONFIGURATOR_STANDALONE) { ImGui::SetWindowPos(ImVec2(0, 0)); ImGui::SetWindowSize(ImGui::GetIO().DisplaySize); } // toolbar/menu int previous_games_selected = this->games_selected; this->build_menu(&this->games_selected); // remember selected game name if (this->games_selected >= 0 && this->games_selected < (int) games_list.size()) { this->games_selected_name = games_list.at(games_selected).getGameName(); // standalone configurator applies selected game if (cfg::CONFIGURATOR_STANDALONE) { eamuse_set_game(games_selected_name); } } else { // invalid selection this->games_selected_name = ""; } // display launcher if no game is selected if (this->games_selected_name.empty()) { this->build_launcher(); return; } // selected game changed if (previous_games_selected != this->games_selected) { read_card(); } // tab selection auto tab_selected_new = ConfigTab::CONFIG_TAB_INVALID; if (ImGui::BeginTabBar("Config Tabs", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { const int page_offset = cfg::CONFIGURATOR_STANDALONE ? 88 : 106; const int page_offset2 = cfg::CONFIGURATOR_STANDALONE ? 65 : 83; if (ImGui::BeginTabItem("Buttons")) { tab_selected_new = ConfigTab::CONFIG_TAB_BUTTONS; ImGui::BeginChild("Buttons", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset), false); // game buttons this->build_buttons("Game", games::get_buttons(this->games_selected_name)); // keypad buttons ImGui::TextUnformatted(""); if (this->games_selected_name == "Beatmania IIDX") { ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8); ImGui::TextColored( ImVec4(1, 0.5f, 0.5f, 1.f), "WARNING: Lightning Model (TDJ) I/O will ignore the keypad!"); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8); ImGui::TextWrapped( "Use Toggle Sub Screen button to show the overlay and use your mouse, " "connect using SpiceCompanion app, or connect a touch screen to enter " "the PIN."); ImGui::TextUnformatted(""); } auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name); auto keypad_count = eamuse_get_game_keypads_name(); if (keypad_count == 1) { this->build_buttons("Keypad", keypad_buttons, 0, games::KeypadButtons::Size - 1); } else if (keypad_count >= 2) { this->build_buttons("Keypad", keypad_buttons); } ImGui::EndChild(); // page selector this->build_page_selector(&this->buttons_page); // bind many ImGui::SameLine(); if (ImGui::Checkbox("Bind Many", &buttons_many_active)) { buttons_many_index = -1; buttons_many_delay = 0; } ImGui::SameLine(); ImGui::HelpMarker("Immediately query for the next button after binding one."); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Analogs")) { tab_selected_new = ConfigTab::CONFIG_TAB_ANALOGS; ImGui::BeginChild("Analogs", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset2), false); this->build_analogs("Game", games::get_analogs(this->games_selected_name)); ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Overlay")) { tab_selected_new = ConfigTab::CONFIG_TAB_OVERLAY; ImGui::BeginChild("Overlay", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset), false); // overlay buttons this->build_buttons("Overlay", games::get_buttons_overlay(this->games_selected_name)); ImGui::EndChild(); // page selector this->build_page_selector(&this->buttons_page); // standalone configurator extras if (cfg::CONFIGURATOR_STANDALONE) { ImGui::SameLine(); ImGui::Checkbox("Enable Overlay in Config", &OVERLAY->hotkeys_enable); } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Lights")) { tab_selected_new = ConfigTab::CONFIG_TAB_LIGHTS; ImGui::BeginChild("Lights", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset), false); this->build_lights("Game", games::get_lights(this->games_selected_name)); ImGui::EndChild(); this->build_page_selector(&this->lights_page); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Cards")) { tab_selected_new = ConfigTab::CONFIG_TAB_CARDS; ImGui::BeginChild("Cards", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset2), false); this->build_cards(); ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Patches")) { tab_selected_new = ConfigTab::CONFIG_TAB_PATCHES; // initialization static std::once_flag initialized; static bool failure = false; std::call_once(initialized, [this] { if (cfg::CONFIGURATOR_STANDALONE) { // verify game is set, otherwise set failure flag if (strlen(avs::game::MODEL) != 3 || (strlen(avs::game::DEST) != 1) || (strlen(avs::game::SPEC) != 1) || (strlen(avs::game::REV) != 1) || (strlen(avs::game::EXT) != 10) || (strcmp(avs::game::MODEL, "000") == 0) || (strcmp(avs::game::EXT, "0000000000") == 0)) { failure = true; } } }); // display tab contents ImGui::BeginChild("Patches", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset2), false); if (failure) { ImGui::TextColored(ImVec4(0.7f, 0.f, 0.f, 1.f), "Unable to detect the game version.\n" "Try to open Patch Manager using the game overlay."); } else { // allocate patch manager if (!patch_manager) { patch_manager.reset(new PatchManager(overlay)); } // display patch manager this->patch_manager->build_content(); } ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("API")) { tab_selected_new = ConfigTab::CONFIG_TAB_API; // API options list ImGui::BeginChild("ApiTab", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset2), false); auto options = games::get_options(this->games_selected_name); for (auto category : launcher::get_categories(launcher::Options::OptionsCategory::API)) { this->build_options(options, category); } ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Options")) { tab_selected_new = ConfigTab::CONFIG_TAB_OPTIONS; // options list ImGui::BeginChild("Options", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset), false); auto options = games::get_options(this->games_selected_name); for (auto category : launcher::get_categories(launcher::Options::OptionsCategory::Basic)) { this->build_options(options, category); } ImGui::EndChild(); // hidden options checkbox ImGui::Checkbox("Show Hidden Options", &this->options_show_hidden); if (!cfg::CONFIGURATOR_STANDALONE && this->options_dirty) { ImGui::SameLine(); if (ImGui::Button("Restart Game")) { launcher::restart(); } ImGui::SameLine(); ImGui::HelpMarker("You need to restart the game to apply the changed settings."); } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Advanced")) { tab_selected_new = ConfigTab::CONFIG_TAB_ADVANCED; // advanced options list ImGui::BeginChild("AdvancedOptions", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset), false); auto options = games::get_options(this->games_selected_name); for (auto category : launcher::get_categories(launcher::Options::OptionsCategory::Advanced)) { this->build_options(options, category); } ImGui::EndChild(); // hidden options checkbox ImGui::Checkbox("Show Hidden Options", &this->options_show_hidden); if (!cfg::CONFIGURATOR_STANDALONE && this->options_dirty) { ImGui::SameLine(); if (ImGui::Button("Restart Game")) { launcher::restart(); } ImGui::SameLine(); ImGui::HelpMarker("You need to restart the game to apply the changed settings."); } // reset configuration button ImGui::SameLine(); if (ImGui::Button("Reset Configuration")) { ImGui::OpenPopup("Reset Config"); } if (ImGui::BeginPopupModal("Reset Config", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1.f), "Do you really want to reset your configuration for all games?\n" "Warning: This can't be reverted!"); if (ImGui::Button("Yes")) { ::Config::getInstance().createConfigFile(); launcher::restart(); } ImGui::SameLine(); if (ImGui::Button("Nope")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Development")) { tab_selected_new = ConfigTab::CONFIG_TAB_DEV; // dev options list ImGui::BeginChild("DevOptions", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset), false); auto options = games::get_options(this->games_selected_name); for (auto category : launcher::get_categories(launcher::Options::OptionsCategory::Dev)) { this->build_options(options, category); } ImGui::EndChild(); // hidden options checkbox ImGui::Checkbox("Show Hidden Options", &this->options_show_hidden); if (!cfg::CONFIGURATOR_STANDALONE && this->options_dirty) { ImGui::SameLine(); if (ImGui::Button("Restart Game")) { launcher::restart(); } ImGui::SameLine(); ImGui::HelpMarker("You need to restart the game to apply the changed settings."); } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Search")) { tab_selected_new = ConfigTab::CONFIG_TAB_SEARCH; ImGui::BeginChild("SearchOptions", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset), false); // search from all options ImGui::Spacing(); ImGui::SetNextItemWidth(420.f); if (ImGui::InputTextWithHint( "", "Type here to search in options..", &this->search_filter, ImGuiInputTextFlags_EscapeClearsAll)) { this->search_filter_in_lower_case = strtolower(this->search_filter); } if (!this->search_filter.empty()) { ImGui::SameLine(); if (ImGui::Button("Clear")) { this->search_filter.clear(); this->search_filter_in_lower_case.clear(); } } ImGui::Spacing(); // draw all options auto options = games::get_options(this->games_selected_name); if (!this->search_filter.empty()) { for (auto category : launcher::get_categories(launcher::Options::OptionsCategory::Everything)) { this->build_options( options, category, const_cast(&this->search_filter_in_lower_case)); } } ImGui::EndChild(); // hidden options checkbox ImGui::Checkbox("Show Hidden Options", &this->options_show_hidden); if (!cfg::CONFIGURATOR_STANDALONE && this->options_dirty) { ImGui::SameLine(); if (ImGui::Button("Restart Game")) { launcher::restart(); } ImGui::SameLine(); ImGui::HelpMarker("You need to restart the game to apply the changed settings."); } ImGui::EndTabItem(); } ImGui::EndTabBar(); } // did tab selection change? if (this->tab_selected != tab_selected_new) { this->tab_selected = tab_selected_new; this->buttons_page = 0; this->lights_page = 0; buttons_many_active = false; buttons_many_index = -1; ImGui::CloseCurrentPopup(); } // disclaimer ImGui::TextColored( ImVec4(1, 0.5f, 0.5f, 1.f), "Do NOT stream or upload game data anywhere public! Support arcades when you can. Thanks."); } void Config::build_buttons(const std::string &name, std::vector