From 48186245fe5eacd7e2778938ff521b2731596e1b Mon Sep 17 00:00:00 2001 From: bicarus-dev <202771338+bicarus-dev@users.noreply.github.com> Date: Mon, 7 Apr 2025 01:49:07 -0700 Subject: [PATCH] overlay: update UI for card manager (#294) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Link to GitHub Issue, if one exists n/a ## Description of change Change the usage pattern for Card Manager UI. ### Previous behavior Previously, it was a list of cards that you select, and click a button to insert as P1 or P2. This is fine, but users got confused when they pressed `Insert Card` overlay key and got a different card inserted, or when auto-insert didn't work as expected. Additionally, if you play a game without continue (IIDX/DDR for example), then it's cumbersome to bring up the overlay and click on insert card every time. ### New behavior Insert Card overlay now lets you pick a card and *slot* it into P1 or P2 side. From that point on, the slotted card number is used as the override, replacing what was previously passed to `-card0` or `-card1`. This means that `Insert Card` button and auto-insert will pick up the new card and use that instead. ## Compiling 👍 ## Testing Tested 1p-only game (popn) and 2p games (DDR, IIDX). --- src/spice2x/launcher/launcher.cpp | 4 + src/spice2x/misc/eamuse.cpp | 30 +- src/spice2x/misc/eamuse.h | 2 + src/spice2x/overlay/windows/card_manager.cpp | 294 +++++++++++++------ src/spice2x/overlay/windows/card_manager.h | 10 +- 5 files changed, 233 insertions(+), 107 deletions(-) diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index ec936ae..1999f6b 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -124,6 +124,8 @@ std::string LOG_FILE_PATH = ""; int LAUNCHER_ARGC = 0; char **LAUNCHER_ARGV = nullptr; std::unique_ptr> LAUNCHER_OPTIONS; + +std::mutex CARD_OVERRIDES_LOCK; std::string CARD_OVERRIDES[2]; // sub-systems @@ -744,9 +746,11 @@ int main_implementation(int argc, char *argv[]) { SetDllDirectoryW(MODULE_PATH.c_str()); } if (options[launcher::Options::Player1Card].is_active()) { + std::lock_guard lock(CARD_OVERRIDES_LOCK); CARD_OVERRIDES[0] = options[launcher::Options::Player1Card].value_text(); } if (options[launcher::Options::Player2Card].is_active()) { + std::lock_guard lock(CARD_OVERRIDES_LOCK); CARD_OVERRIDES[1] = options[launcher::Options::Player2Card].value_text(); } if (options[launcher::Options::Player1PinMacro].is_active()) { diff --git a/src/spice2x/misc/eamuse.cpp b/src/spice2x/misc/eamuse.cpp index 33422d6..b2f0f22 100644 --- a/src/spice2x/misc/eamuse.cpp +++ b/src/spice2x/misc/eamuse.cpp @@ -66,7 +66,7 @@ bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) { if (CARD_INSERT_UID_ENABLE[index]) { CARD_INSERT_UID_ENABLE[index] = false; memcpy(card, CARD_INSERT_UID[index], 8); - log_info("eamuse", "Inserted card from reader {}: {}", index, bin2hex(card, 8)); + log_info("eamuse", "[P{}] Inserted card from reader: {}", index+1, bin2hex(card, 8)); return true; } @@ -83,35 +83,40 @@ bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) { } bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int index) { - + // do a quick copy under lock + std::unique_lock lock(CARD_OVERRIDES_LOCK); + const auto card_override = CARD_OVERRIDES[index]; + lock.unlock(); + // Check if card overrides are present - if (!CARD_OVERRIDES[index].empty()) { + if (!card_override.empty()) { // Override is present for (int n = 0; n < 16; n++) { - char c = CARD_OVERRIDES[index].c_str()[n]; + char c = card_override.c_str()[n]; bool digit = c >= '0' && c <= '9'; bool character_big = c >= 'A' && c <= 'F'; bool character_small = c >= 'a' && c <= 'f'; if (!digit && !character_big && !character_small) { log_warning("eamuse", "{} card override contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)", - CARD_OVERRIDES[index], n); + card_override, n); return false; } } // Log info - log_info("eamuse", "Inserted card override: {}", CARD_OVERRIDES[index]); + log_info("eamuse", "[P{}] Inserted card override: {}", index+1, card_override); // Card is valid, convert and set it. - hex2bin(CARD_OVERRIDES[index].c_str(), card); + hex2bin(card_override.c_str(), card); // cache it for auto-insert - if (AUTO_INSERT_CARD[index] && !AUTO_INSERT_CARD_CACHED[index]) { + // always overwrite from overrides since override may have changed by user in card manager + if (AUTO_INSERT_CARD[index]) { memcpy(AUTO_INSERT_CARD_CACHED_DATA[index], card, 8); AUTO_INSERT_CARD_CACHED[index] = true; - log_info("eamuse", "Auto card insert - caching this card in memory: {}", CARD_OVERRIDES[index]); + log_info("eamuse", "[P{}] Auto card insert - caching this card override in memory: {}", index+1, card_override); } // success @@ -162,7 +167,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card, } // info - log_info("eamuse", "Inserted {}: {}", path.string(), buffer); + log_info("eamuse", "[P{}] Inserted {}: {}", index+1, path.string(), buffer); // convert hex to bytes hex2bin(buffer, card); @@ -171,7 +176,7 @@ bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card, if (AUTO_INSERT_CARD[index] && !AUTO_INSERT_CARD_CACHED[index]) { memcpy(AUTO_INSERT_CARD_CACHED_DATA[index], card, 8); AUTO_INSERT_CARD_CACHED[index] = true; - log_info("eamuse", "Auto card insert - caching this card in memory: {}", buffer); + log_info("eamuse", "[P{}] Auto card insert - caching this card from file in memory: {}", index+1, buffer); } // success @@ -211,7 +216,6 @@ bool eamuse_card_insert_consume(int active_count, int unit_id) { if (!CARD_INSERT[index]) { eamuse_card_insert(index); // not logging anything here to prevent spam - // log_info("eamuse", "Automatic card insert on {}/{} (-autocard)", unit_id + 1, active_count); return true; } else { return false; @@ -223,7 +227,7 @@ bool eamuse_card_insert_consume(int active_count, int unit_id) { auto offset = unit_id * games::KeypadButtons::Size; if ((CARD_INSERT[index] && fabs(get_performance_seconds() - CARD_INSERT_TIME[index]) < CARD_INSERT_TIMEOUT) || GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::InsertCard + offset))) { - log_info("eamuse", "Card insert on {}/{}", unit_id + 1, active_count); + log_info("eamuse", "[P{}] Card insert on reader (total active count: {})", unit_id+1, active_count); CARD_INSERT[index] = false; return true; } diff --git a/src/spice2x/misc/eamuse.h b/src/spice2x/misc/eamuse.h index 09941c0..4f94bfa 100644 --- a/src/spice2x/misc/eamuse.h +++ b/src/spice2x/misc/eamuse.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "external/scard/scard.h" @@ -30,6 +31,7 @@ enum eam_io_keypad_scan_code { EAM_IO_INSERT = 13, /* SpiceTools Extension */ }; +extern std::mutex CARD_OVERRIDES_LOCK; extern std::string CARD_OVERRIDES[2]; extern bool AUTO_INSERT_CARD[2]; extern float AUTO_INSERT_CARD_COOLDOWN; diff --git a/src/spice2x/overlay/windows/card_manager.cpp b/src/spice2x/overlay/windows/card_manager.cpp index a87bc9f..7aaf1b3 100644 --- a/src/spice2x/overlay/windows/card_manager.cpp +++ b/src/spice2x/overlay/windows/card_manager.cpp @@ -5,9 +5,11 @@ #include "external/rapidjson/document.h" #include "external/rapidjson/prettywriter.h" #include "misc/eamuse.h" +#include "misc/clipboard.h" #include "util/utils.h" #include "util/fileutils.h" #include "cfg/configurator.h" +#include "overlay/imgui/extensions.h" using namespace rapidjson; @@ -31,15 +33,69 @@ namespace overlay::windows { if (fileutils::file_exists(this->config_path)) { this->config_load(); } + + // load -card0 / -card1 + // -card0 / -card1 override + { + std::lock_guard lock(CARD_OVERRIDES_LOCK); + if (!CARD_OVERRIDES[0].empty()) { + const CardEntry card0 = { + .name = "P1 Default (-card0)", + .id = CARD_OVERRIDES[0], + .search_string = "p1 default (-card0)", + .read_only = true, + .color = {0.9f, 0.9f, 0.9f} + }; + card_cmd_overrides[0].emplace(card0); + this->loaded_card[0] = card0; + } + if (eamuse_get_game_keypads() > 1 && !CARD_OVERRIDES[1].empty()) { + const CardEntry card1 = { + .name = "P2 Default (-card1)", + .id = CARD_OVERRIDES[1], + .search_string = "p2 default (-card1)", + .read_only = true, + .color = {0.9f, 0.9f, 0.9f} + }; + card_cmd_overrides[1].emplace(card1); + this->loaded_card[1] = card1; + } + } } CardManager::~CardManager() { } void CardManager::build_content() { - ImGui::SeparatorText("Selected card"); - build_card(); - ImGui::SeparatorText("Available cards"); + ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Active card overrides"); + ImGui::SameLine(); + ImGui::HelpMarker( + "Click to insert card now, or press Insert Card key. Auto Card Insert will also use these cards.\n\n" + "If no override is set, pressing Insert Card will read from card0.txt / card1.txt."); + if (ImGui::BeginTable("CardSetTable", eamuse_get_game_keypads() > 1 ? 2 : 1, ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Player 1"); + if (eamuse_get_game_keypads() > 1) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Player 2"); + } + ImGui::TableNextRow(); + for (size_t i = 0; i < 2; i++) { + if (eamuse_get_game_keypads() > (int)i) { + ImGui::TableNextColumn(); + if (build_card(i) && this->loaded_card[i].has_value()) { + insert_card_over_api(i, this->loaded_card[i].value()); + } + } + } + ImGui::EndTable(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Available cards"); build_card_list(); ImGui::Separator(); ImGui::Spacing(); @@ -48,44 +104,13 @@ namespace overlay::windows { build_card_editor(); } - void CardManager::build_card() { - ImGui::BeginDisabled(this->current_card == nullptr); - // insert P1 button - if (ImGui::Button("Insert P1")) { - const auto card = this->current_card; - uint8_t card_bin[8]; - if (card && card->id.length() == 16 && hex2bin(card->id.c_str(), card_bin)) { - eamuse_card_insert(0, card_bin); - } - } - - // insert P2 button - if (eamuse_get_game_keypads() > 1) { - ImGui::SameLine(); - if (ImGui::Button("Insert P2")) { - const auto card = this->current_card; - uint8_t card_bin[8]; - if (card && card->id.length() == 16 && hex2bin(card->id.c_str(), card_bin)) { - eamuse_card_insert(1, card_bin); - } - } - } - - // edit selected card - ImGui::SameLine(); - if (ImGui::Button("Edit Card")) { - open_card_editor(); - } - - ImGui::EndDisabled(); - - ImGui::Spacing(); - - // card ui - if (this->current_card) { - const auto card = this->current_card; - const ImVec4 color(card->color[0], card->color[1], card->color[2], 1.f); - float bg_luminance = (0.299f * card->color[0] + 0.587 * card->color[1] + 0.114 * card->color[2]); + bool CardManager::build_card(int reader) { + ImGui::PushID(reader); + bool clicked = false; + if (this->loaded_card[reader].has_value()) { + const auto &card = this->loaded_card[reader].value(); + const ImVec4 color(card.color[0], card.color[1], card.color[2], 1.f); + float bg_luminance = (0.299f * card.color[0] + 0.587 * card.color[1] + 0.114 * card.color[2]); // text color ImVec4 text_color; @@ -101,21 +126,25 @@ namespace overlay::windows { ImGui::PushStyleColor(ImGuiCol_Text, text_color); if (ImGui::Button(fmt::format( " {} \n {} {} {} {} ", - card->name.empty() ? "" : card->name.substr(0, 19), - card->id.substr(0, 4).c_str(), - card->id.substr(4, 4).c_str(), - card->id.substr(8, 4).c_str(), - card->id.substr(12, 4).c_str() + card.name.empty() ? "" : card.name.substr(0, 19), + card.id.substr(0, 4).c_str(), + card.id.substr(4, 4).c_str(), + card.id.substr(8, 4).c_str(), + card.id.substr(12, 4).c_str() ).c_str())) { - open_card_editor(); + clicked = true; } ImGui::PopStyleColor(4); } else { ImGui::BeginDisabled(); - ImGui::Button(" \n xxxx xxxx xxxx xxxx "); + ImGui::Button(" (No override set) \n" + " xxxx xxxx xxxx xxxx "); ImGui::EndDisabled(); } + + ImGui::PopID(); + return clicked; } void CardManager::open_card_editor() { @@ -139,13 +168,18 @@ namespace overlay::windows { this->card_buffer, std::size(this->card_buffer), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + ImGui::EndDisabled(); if (this->current_card == nullptr) { ImGui::SameLine(); if (ImGui::Button("Generate")) { generate_ea_card(this->card_buffer); } + } else { + ImGui::SameLine(); + if (ImGui::Button("Copy")) { + clipboard::copy_text(this->current_card->id); + } } - ImGui::EndDisabled(); // name field ImGui::InputTextWithHint("Card Name", "Main Card", @@ -170,6 +204,18 @@ namespace overlay::windows { this->current_card->color[1] = this->color_buffer[1]; this->current_card->color[2] = this->color_buffer[2]; generate_search_string(this->current_card); + + // ensure loaded cards are kept up to date + // note: does not handle cases where multiple cards have the same ID + for (size_t i = 0; i < 2; i++) { + if (this->loaded_card[i].has_value() && + !this->loaded_card[i].value().read_only && + this->loaded_card[i].value().id == this->current_card->id) { + this->loaded_card[i] = *this->current_card; + break; + } + } + } else { // create a new card CardEntry card { @@ -210,14 +256,54 @@ namespace overlay::windows { } } + void CardManager::build_card_selectable(CardEntry &card) { + // generate card name + std::string card_name = ""; + if (card.id.length() == 16) { + card_name += card.id.substr(0, 4); + card_name += " "; + card_name += card.id.substr(4, 4); + card_name += " "; + card_name += card.id.substr(8, 4); + card_name += " "; + card_name += card.id.substr(12, 4); + } else { + card_name += card.id; + } + if (!card.name.empty()) { + card_name += " - "; + card_name += card.name; + } + + ImGui::PushID(&card); + + // color button + ImVec4 color(card.color[0], card.color[1], card.color[2], 1.f); + ImGui::PushStyleColor(ImGuiCol_Button, color); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, color); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, color); + ImGui::SmallButton(" "); + ImGui::PopStyleColor(3); + + ImGui::SameLine(); + + // selectable item + if (ImGui::Selectable(card_name.c_str(), this->current_card == &card)) { + this->current_card = &card; + } + + ImGui::PopID(); + } + void CardManager::build_card_list() { // search for card // // setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below // to return true, making it necessary to provide a callback... - ImGui::SetNextItemWidth(220); + ImGui::SetNextItemWidth(240); if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) { + this->current_card = nullptr; this->search_filter_in_lower_case = strtolower(this->search_filter); } if (!this->search_filter.empty()) { @@ -226,11 +312,49 @@ namespace overlay::windows { this->search_filter.clear(); this->search_filter_in_lower_case.clear(); } - } else { - ImGui::SameLine(); - - // move selected up/down the list - ImGui::BeginDisabled(this->current_card == nullptr); + } + + // toolbar + + // set card as p1/p2 + for (size_t i = 0; i < 2; i++) { + if (eamuse_get_game_keypads() > (int)i) { + if (i != 0) { + ImGui::SameLine(); + } + ImGui::PushID(i); + ImGui::BeginDisabled(this->current_card == nullptr); + if (ImGui::Button(i == 0 ? "Load P1" : "Load P2") && this->current_card) { + this->loaded_card[i] = *this->current_card; + log_info( + "cardmanager", + "[P{}] update override and insert card: {} ({})", + i+1, + this->current_card->id, + this->current_card->name + ); + + // update override + std::lock_guard lock(CARD_OVERRIDES_LOCK); + CARD_OVERRIDES[i] = this->current_card->id; + + // insert card over api + insert_card_over_api(i, this->loaded_card[i].value()); + } + ImGui::EndDisabled(); + ImGui::PopID(); + } + } + + // edit selected card + ImGui::SameLine(); + ImGui::BeginDisabled(this->current_card == nullptr || this->current_card->read_only); + if (ImGui::Button("Edit")) { + open_card_editor(); + } + + // move selected up/down the list + if (this->search_filter.empty()) { ImGui::SameLine(); if (ImGui::Button("Move Up")) { for (auto it = this->cards.begin(); it != this->cards.end(); ++it) { @@ -253,60 +377,37 @@ namespace overlay::windows { } } } - ImGui::EndDisabled(); } + ImGui::EndDisabled(); ImGui::Spacing(); // cards list - // use all available vertical space, minus height of buttons, minus separator + // use all available vertical space, minus height footer (a row of buttons and separator) if (ImGui::BeginChild( "cards", ImVec2(0, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - 8.f))) { + + // -card0 / -card1 override + for (size_t i = 0; i < 2; i++) { + if (card_cmd_overrides[i].has_value()) { + build_card_selectable(card_cmd_overrides[i].value()); + } + } + + // cards from card manager JSON for (auto &card : this->cards) { - - // get card name - std::string card_name = ""; - if (card.id.length() == 16) { - card_name += card.id.substr(0, 4); - card_name += " "; - card_name += card.id.substr(4, 4); - card_name += " "; - card_name += card.id.substr(8, 4); - card_name += " "; - card_name += card.id.substr(12, 4); - } else { - card_name += card.id; - } - if (!card.name.empty()) { - card_name += " - "; - card_name += card.name; - } - + if (!this->search_filter_in_lower_case.empty() && !card.search_string.empty()) { const bool matched = - card.search_string.find(this->search_filter_in_lower_case) != std::string::npos; - + card.search_string.find(this->search_filter_in_lower_case) != std::string::npos; + if (!matched) { continue; } } - // draw entry - ImGui::PushID(&card); - - ImVec4 color(card.color[0], card.color[1], card.color[2], 1.f); - ImGui::PushStyleColor(ImGuiCol_Button, color); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, color); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, color); - ImGui::SmallButton(" "); - ImGui::PopStyleColor(3); - - ImGui::SameLine(); - if (ImGui::Selectable(card_name.c_str(), this->current_card == &card)) { - this->current_card = &card; - } - ImGui::PopID(); + build_card_selectable(card); } } ImGui::EndChild(); @@ -503,4 +604,11 @@ namespace overlay::windows { this->color_buffer[1] = g / 255.f; this->color_buffer[2] = b / 255.f; } + + void CardManager::insert_card_over_api(int reader, CardEntry &card) { + uint8_t card_bin[8]; + if (card.id.length() == 16 && hex2bin(card.id.c_str(), card_bin)) { + eamuse_card_insert(reader, card_bin); + } + } } diff --git a/src/spice2x/overlay/windows/card_manager.h b/src/spice2x/overlay/windows/card_manager.h index e79f0fc..5dee87c 100644 --- a/src/spice2x/overlay/windows/card_manager.h +++ b/src/spice2x/overlay/windows/card_manager.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "overlay/window.h" @@ -10,6 +11,7 @@ namespace overlay::windows { std::string name = "unnamed"; std::string id = "E004010000000000"; std::string search_string = ""; + bool read_only = false; float color[3] {}; }; @@ -29,7 +31,10 @@ namespace overlay::windows { char name_buffer[65] {}; char card_buffer[17] {}; float color_buffer[3] {}; + + std::optional card_cmd_overrides[2]; + std::optional loaded_card[2]; CardEntry *current_card = nullptr; std::string search_filter = ""; @@ -41,10 +46,13 @@ namespace overlay::windows { void generate_search_string(CardEntry *card); void generate_random_color(); - void build_card(); + bool build_card(int reader); void open_card_editor(); void build_card_editor(); void build_card_list(); + void build_card_selectable(CardEntry &card); void build_footer(); + + void insert_card_over_api(int reader, CardEntry &card); }; }