#include "config.h" #include #include #include #include #include #include #include "build/defs.h" #include "build/resource.h" #include "cfg/config.h" #include "cfg/configurator.h" #include "external/asio/asiolist.h" #include "external/imgui/imgui_internal.h" #include "external/imgui/misc/cpp/imgui_stdlib.h" #include "games/io.h" #include "games/sdvx/sdvx.h" #include "games/popn/popn.h" #include "avs/core.h" #include "avs/ea3.h" #include "avs/game.h" #include "light_match_map.h" #include "launcher/launcher.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 "misc/clipboard.h" #include "util/fileutils.h" #include "util/logging.h" #include "util/resutils.h" #include "util/scope_guard.h" #include "util/time.h" #include "util/sysutils.h" #include "util/utils.h" #ifdef min #undef min #endif static int CALLBACK BrowseCallbackProc( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) { if (uMsg == BFFM_INITIALIZED) { SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, lpData); } return 0; } namespace overlay::windows { // same width as dummy marker const float INDENT = 22.f; const auto PROJECT_URL = "https://spice2x.github.io"; constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f); constexpr ImVec4 TEXT_COLOR_RED(1.f, 0.f, 0.f, 1.f); constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512; std::unique_ptr asio_driver_list; Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) { this->title = "Configuration"; this->toggle_button = games::OverlayButtons::ToggleConfig; this->init_size = overlay::apply_scaling_to_vector(ImVec2(800, 600)); this->size_min = overlay::apply_scaling_to_vector(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) { const auto file_hints = games::get_game_file_hints(game_name); if (file_hints) { for (auto &file_hint_list : *file_hints) { bool matches_all_files = true; for (auto &file_hint : file_hint_list) { const auto matched = (fileutils::file_exists(file_hint) || fileutils::dir_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)); if (!matched) { matches_all_files = false; break; } } if (matches_all_files) { this->games_selected = games_list.size() - 1; this->games_selected_name = game_name; eamuse_set_game(game_name); break; } } } } } // 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::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(); templates_cache_dirty = true; log_misc( "config", "game selection changed from {} to {}", previous_games_selected, this->games_selected); } // tab selection auto tab_selected_new = ConfigTab::CONFIG_TAB_INVALID; if (ImGui::BeginTabBar("Config Tabs", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { const int page_offset = overlay::apply_scaling(cfg::CONFIGURATOR_STANDALONE ? 88 : 110); const int page_offset2 = overlay::apply_scaling(cfg::CONFIGURATOR_STANDALONE ? 65 : 87); if (ImGui::BeginTabItem("Buttons")) { tab_selected_new = ConfigTab::CONFIG_TAB_BUTTONS; ImGui::BeginChild("Buttons", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset2), false); // help text for binding buttons, if the game has one const auto help_text = games::get_buttons_help(this->games_selected_name); if (!help_text.empty()) { ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Button Bindings"); ImGui::Spacing(); ImGui::TextWrapped("%s", help_text.c_str()); ImGui::TextUnformatted(""); } // game buttons this->build_buttons("Game", games::get_buttons(this->games_selected_name)); // keypad buttons ImGui::TextUnformatted(""); this->build_keypad_warning(); 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(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Analogs")) { tab_selected_new = ConfigTab::CONFIG_TAB_ANALOGS; ImGui::BeginChild("Analogs", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset2), false); // help text for binding analog, if the game has one const auto help_text = games::get_analogs_help(this->games_selected_name); if (!help_text.empty()) { ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Analog Bindings"); ImGui::Spacing(); ImGui::TextWrapped("%s", help_text.c_str()); ImGui::TextUnformatted(""); } 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; const auto offset = cfg::CONFIGURATOR_STANDALONE ? page_offset : page_offset2; ImGui::BeginChild("Overlay", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - offset), false); // overlay buttons this->build_buttons("Overlay", games::get_buttons_overlay(this->games_selected_name)); ImGui::EndChild(); // standalone configurator extras if (cfg::CONFIGURATOR_STANDALONE) { 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_offset2), false); this->build_lights("Game", games::get_lights(this->games_selected_name)); ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Presets")) { tab_selected_new = ConfigTab::CONFIG_TAB_PRESETS; ImGui::BeginChild("Presets", ImVec2( 0, ImGui::GetWindowContentRegionMax().y - page_offset2), false); this->build_presets(); ImGui::EndChild(); 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) { log_misc( "config", "tab selection changed from {} to {}", static_cast(this->tab_selected), static_cast(tab_selected_new)); stop_lights_test(); this->tab_selected = tab_selected_new; buttons_many_active = false; buttons_many_index = -1; ImGui::CloseCurrentPopup(); } // disclaimer // note: distribution of modified version of this software without providing source is GPLv3 license violation. ImGui::TextColored( ImVec4(1, 0.5f, 0.5f, 1.f), "spice2x is free & open source; if you paid money for it, you got scammed."); if (cfg::CONFIGURATOR_STANDALONE) { ImGui::SameLine(); if (ImGui::TextLink(PROJECT_URL)) { launch_shell(PROJECT_URL); } } } void Config::build_keypad_warning() { // not checking the -iidxtdj option here, we could do that in the future const bool is_tdj = this->games_selected_name == "Beatmania IIDX"; // not using games::popn::is_pikapika_model() here since that always returns false on 32-bit const bool is_popn = this->games_selected_name == "Pop'n Music" && avs::game::SPEC[0] == 'D'; if (!is_tdj && !is_popn) { return; } ImGui::Indent(INDENT); if (is_tdj) { ImGui::TextColored( ImVec4(1, 0.5f, 0.5f, 1.f), "WARNING: Lightning Model (TDJ) I/O will ignore keypad number input!"); } else if (is_popn) { ImGui::TextColored( ImVec4(1, 0.5f, 0.5f, 1.f), "WARNING: PikaPika Pop-Kun model will ignore keypad number input!"); } ImGui::TextWrapped( "Use Toggle Sub Screen button (Overlay tab) to show the overlay and use your mouse, " "connect using SpiceCompanion app, or connect a touch screen to enter " "the PIN."); ImGui::Unindent(INDENT); ImGui::TextUnformatted(""); } void Config::build_buttons(const std::string &name, std::vector