#include "eamuse.h" #include #include #include "avs/game.h" #include "cfg/config.h" #include "games/io.h" #include "rawinput/rawinput.h" #include "games/sdvx/sdvx.h" #include "util/logging.h" #include "util/time.h" #include "util/utils.h" #include "bt5api.h" // state static bool CARD_INSERT[2] = {false, false}; static double CARD_INSERT_TIME[2] = {0, 0}; static double CARD_INSERT_TIMEOUT = 2.0; static char CARD_INSERT_UID[2][8]; static char CARD_INSERT_UID_ENABLE[2] = {false, false}; static int COIN_STOCK = 0; static bool COIN_BLOCK = false; static std::thread *COIN_INPUT_THREAD; static bool COIN_INPUT_THREAD_ACTIVE = false; static uint16_t KEYPAD_STATE[] = {0, 0}; static uint16_t KEYPAD_STATE_OVERRIDES[] = {0, 0}; static uint16_t KEYPAD_STATE_OVERRIDES_BT5[] = {0, 0}; static uint16_t KEYPAD_STATE_OVERRIDES_READER[] = {0, 0}; static uint16_t KEYPAD_STATE_OVERRIDES_OVERLAY[] = {0, 0}; static std::string EAMUSE_GAME_NAME; static ConfigKeypadBindings KEYPAD_BINDINGS {}; // auto card bool AUTO_INSERT_CARD[2] = {false, false}; float AUTO_INSERT_CARD_COOLDOWN = 8.f; // seconds static std::optional AUTO_INSERT_CARD_FIRST_CONSUME_TIME; static bool AUTO_INSERT_CARD_CACHED[2]; static uint8_t AUTO_INSERT_CARD_CACHED_DATA[2][8]; bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) { // get unit index int index = unit_id > 0 && active_count > 1 ? 1 : 0; // check cards cached for auto-insert // explicitly not logging anything in this path to avoid log spam if (AUTO_INSERT_CARD[index] && AUTO_INSERT_CARD_CACHED[index]) { memcpy(card, AUTO_INSERT_CARD_CACHED_DATA[index], 8); return true; } // reader card input 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)); return true; } // get file path std::filesystem::path path; if (!KEYPAD_BINDINGS.card_paths[index].empty()) { path = KEYPAD_BINDINGS.card_paths[index]; } else { path = index > 0 ? "card1.txt" : "card0.txt"; } // call the next function return eamuse_get_card(path, card, index); } bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int index) { // Check if card overrides are present if (!CARD_OVERRIDES[index].empty()) { // Override is present for (int n = 0; n < 16; n++) { char c = CARD_OVERRIDES[index].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); return false; } } // Log info log_info("eamuse", "Inserted card override: {}", CARD_OVERRIDES[index]); // Card is valid, convert and set it. hex2bin(CARD_OVERRIDES[index].c_str(), card); // cache it for auto-insert 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: {}", CARD_OVERRIDES[index]); } // success return true; } // Overrides are not present. Use the standard file reading method. return eamuse_get_card_from_file(path, card, index); } bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card, int index) { // open file std::ifstream f(path); if (!f) { log_warning("eamuse", "{} can not be opened!", path.string()); return false; } // get size f.seekg(0, f.end); auto length = (size_t) f.tellg(); f.seekg(0, f.beg); // check size if (length < 16) { log_warning("eamuse", "{} is too small (must be at least 16 characters)", path.string()); return false; } // read file char buffer[17]; f.read(buffer, 16); buffer[16] = 0; // verify card for (int n = 0; n < 16; n++) { char c = buffer[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", "{} contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)", path.string(), n); return false; } } // info log_info("eamuse", "Inserted {}: {}", path.string(), buffer); // convert hex to bytes hex2bin(buffer, card); // cache it for auto-insert 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); } // success return true; } void eamuse_card_insert(int unit) { CARD_INSERT[unit] = true; CARD_INSERT_TIME[unit] = get_performance_seconds(); } void eamuse_card_insert(int unit, const uint8_t *card) { memcpy(CARD_INSERT_UID[unit], card, 8); CARD_INSERT[unit] = true; CARD_INSERT_TIME[unit] = get_performance_seconds(); CARD_INSERT_UID_ENABLE[unit] = true; } bool eamuse_card_insert_consume(int active_count, int unit_id) { // get unit index int index = unit_id > 0 && active_count > 1 ? 1 : 0; // bt5api if (BT5API_ENABLED) { bt5api_poll_reader_card((uint8_t) index); } // auto insert card if (AUTO_INSERT_CARD[index]) { // reset timer if enough time has passed since last insert if (CARD_INSERT[index] && AUTO_INSERT_CARD_COOLDOWN < (get_performance_seconds() - CARD_INSERT_TIME[index])) { CARD_INSERT[index] = false; } 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; } } // check for card insert auto keypad_buttons = games::get_buttons_keypads(eamuse_get_game()); 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); CARD_INSERT[index] = false; return true; } return false; } bool eamuse_coin_get_block() { return COIN_BLOCK; } void eamuse_coin_set_block(bool block) { COIN_BLOCK = block; } int eamuse_coin_get_stock() { return COIN_STOCK; } void eamuse_coin_set_stock(int amount) { COIN_STOCK = amount; } bool eamuse_coin_consume(int amount) { if (COIN_STOCK < amount) { return false; } else { COIN_STOCK -= amount; return true; } } int eamuse_coin_consume_stock() { int stock = COIN_STOCK; COIN_STOCK = 0; return stock; } int eamuse_coin_add() { return ++COIN_STOCK; } void eamuse_coin_start_thread() { // set active COIN_INPUT_THREAD_ACTIVE = true; // create thread COIN_INPUT_THREAD = new std::thread([]() { auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game()); static bool COIN_INPUT_KEY_STATE = false; while (COIN_INPUT_THREAD_ACTIVE) { // check input key if (overlay_buttons && GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at( games::OverlayButtons::InsertCoin))) { if (!COIN_INPUT_KEY_STATE) { if (COIN_BLOCK) log_info("eamuse", "coin inserted while blocked"); else { log_info("eamuse", "coin insert"); COIN_STOCK++; } } COIN_INPUT_KEY_STATE = true; } else { COIN_INPUT_KEY_STATE = false; } // once every two frames Sleep(1000 / 30); } }); } void eamuse_coin_stop_thread() { COIN_INPUT_THREAD_ACTIVE = false; COIN_INPUT_THREAD->join(); delete COIN_INPUT_THREAD; COIN_INPUT_THREAD = nullptr; } void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state) { // check unit if (unit >= std::size(KEYPAD_STATE_OVERRIDES)) { return; } // set state KEYPAD_STATE_OVERRIDES[unit] = keypad_state; } void eamuse_set_keypad_overrides_bt5(size_t unit, uint16_t keypad_state) { // check unit if (unit >= std::size(KEYPAD_STATE_OVERRIDES_BT5)) { return; } // set state KEYPAD_STATE_OVERRIDES_BT5[unit] = keypad_state; } void eamuse_set_keypad_overrides_reader(size_t unit, uint16_t keypad_state) { // check unit if (unit >= std::size(KEYPAD_STATE_OVERRIDES_READER)) { return; } // set state KEYPAD_STATE_OVERRIDES_READER[unit] = keypad_state; } void eamuse_set_keypad_overrides_overlay(size_t unit, uint16_t keypad_state) { // check unit if (unit >= std::size(KEYPAD_STATE_OVERRIDES_OVERLAY)) { return; } // set state KEYPAD_STATE_OVERRIDES_OVERLAY[unit] = keypad_state; } uint16_t eamuse_get_keypad_state(size_t unit) { // check unit if (unit >= std::size(KEYPAD_STATE)) { return 0; } // reset KEYPAD_STATE[unit] = KEYPAD_STATE_OVERRIDES[unit]; KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_BT5[unit]; KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_READER[unit]; KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_OVERLAY[unit]; // bt5api if (BT5API_ENABLED) { bt5api_poll_reader_keypad((uint8_t) unit); } // get keybinds and get offset for unit auto keypad_buttons = games::get_buttons_keypads(eamuse_get_game()); auto offset = unit * games::KeypadButtons::Size; // parse if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad0 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_0; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad1 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_1; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad2 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_2; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad3 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_3; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad4 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_4; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad5 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_5; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad6 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_6; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad7 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_7; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad8 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_8; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad9 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_9; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad00 + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_00; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::KeypadDecimal + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_DECIMAL; } if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::InsertCard + offset))) { KEYPAD_STATE[unit] |= 1 << EAM_IO_INSERT; } // return state return KEYPAD_STATE[unit]; } std::string eamuse_get_keypad_state_str(size_t unit) { auto state = eamuse_get_keypad_state(unit); std::ostringstream ss; if (state & 1 << EAM_IO_KEYPAD_0) ss << "0"; if (state & 1 << EAM_IO_KEYPAD_1) ss << "1"; if (state & 1 << EAM_IO_KEYPAD_2) ss << "2"; if (state & 1 << EAM_IO_KEYPAD_3) ss << "3"; if (state & 1 << EAM_IO_KEYPAD_4) ss << "4"; if (state & 1 << EAM_IO_KEYPAD_5) ss << "5"; if (state & 1 << EAM_IO_KEYPAD_6) ss << "6"; if (state & 1 << EAM_IO_KEYPAD_7) ss << "7"; if (state & 1 << EAM_IO_KEYPAD_8) ss << "8"; if (state & 1 << EAM_IO_KEYPAD_9) ss << "9"; if (state & 1 << EAM_IO_KEYPAD_00) ss << "00"; if (state & 1 << EAM_IO_KEYPAD_DECIMAL) ss << "00"; if (state & 1 << EAM_IO_INSERT) ss << "00"; return ss.str(); } bool eamuse_keypad_state_naive() { return KEYPAD_BINDINGS.keypads[0].empty() && KEYPAD_BINDINGS.keypads[1].empty(); } void eamuse_set_game(std::string game) { if (EAMUSE_GAME_NAME != game) { EAMUSE_GAME_NAME = std::move(game); eamuse_update_keypad_bindings(); } } void eamuse_update_keypad_bindings() { KEYPAD_BINDINGS = Config::getInstance().getKeypadBindings(EAMUSE_GAME_NAME); } std::string eamuse_get_game() { return EAMUSE_GAME_NAME; } int eamuse_get_game_keypads() { if (avs::game::is_model("JDZ") || avs::game::is_model("KDZ") || avs::game::is_model("LDJ") || avs::game::is_model("JDX") || avs::game::is_model("KDX") || avs::game::is_model("MDX") || avs::game::is_model("J33") || avs::game::is_model("K33") || avs::game::is_model("L33") || avs::game::is_model("M32")) { return 2; } return 1; } int eamuse_get_game_keypads_name() { auto game_name = eamuse_get_game(); if (game_name == "Beatmania IIDX" || game_name == "Dance Dance Revolution" || game_name == "GitaDora") { return 2; } return 1; } void eamuse_autodetect_game() { if (avs::game::is_model("KFC")) eamuse_set_game("Sound Voltex"); else if (avs::game::is_model("JDZ") || avs::game::is_model("KDZ") || avs::game::is_model("LDJ")) eamuse_set_game("Beatmania IIDX"); else if (avs::game::is_model("J44") || avs::game::is_model("K44") || avs::game::is_model("L44")) eamuse_set_game("Jubeat"); else if (avs::game::is_model("KDM")) eamuse_set_game("Dance Evolution"); else if (avs::game::is_model("NBT")) eamuse_set_game("Beatstream"); else if (avs::game::is_model("I36")) eamuse_set_game("Metal Gear"); else if (avs::game::is_model("KBR") || avs::game::is_model("LBR") || avs::game::is_model("MBR")) eamuse_set_game("Reflec Beat"); else if (avs::game::is_model("KBI")) eamuse_set_game("Tenkaichi Shogikai"); else if (avs::game::is_model("K39") || avs::game::is_model("L39") || avs::game::is_model("M39")) eamuse_set_game("Pop'n Music"); else if (avs::game::is_model("KGG")) eamuse_set_game("Steel Chronicle"); else if (avs::game::is_model("JGT")) eamuse_set_game("Road Fighters 3D"); else if (avs::game::is_model("PIX")) eamuse_set_game("Museca"); else if (avs::game::is_model("R66")) eamuse_set_game("Bishi Bashi Channel"); else if (avs::game::is_model("J32") || avs::game::is_model("J33") || avs::game::is_model("K32") || avs::game::is_model("K33") || avs::game::is_model("L32") || avs::game::is_model("L33") || avs::game::is_model("M32")) eamuse_set_game("GitaDora"); else if (avs::game::is_model("JDX") || avs::game::is_model("KDX") || avs::game::is_model("MDX")) eamuse_set_game("Dance Dance Revolution"); else if (avs::game::is_model("PAN")) eamuse_set_game("Nostalgia"); else if (avs::game::is_model("JMA") || avs::game::is_model("KMA") || avs::game::is_model("LMA")) eamuse_set_game("Quiz Magic Academy"); else if (avs::game::is_model("MMD")) eamuse_set_game("FutureTomTom"); else if (avs::game::is_model("KK9")) eamuse_set_game("Mahjong Fight Club"); else if (avs::game::is_model("JMP")) eamuse_set_game("HELLO! Pop'n Music"); else if (avs::game::is_model("KLP")) eamuse_set_game("LovePlus"); else if (avs::game::is_model("NSC")) eamuse_set_game("Scotto"); else if (avs::game::is_model("REC")) eamuse_set_game("DANCERUSH"); else if (avs::game::is_model("KCK") || avs::game::is_model("NCK")) eamuse_set_game("Winning Eleven"); else if (avs::game::is_model("NCG")) eamuse_set_game("Otoca D'or"); else if (avs::game::is_model("LA9")) eamuse_set_game("Charge Machine"); else if (avs::game::is_model("JC9")) eamuse_set_game("Ongaku Paradise"); else if (avs::game::is_model("TBS")) eamuse_set_game("Busou Shinki: Armored Princess Battle Conductor"); else if (avs::game::is_model("UJK")) eamuse_set_game("Chase Chase Jokers"); else if (avs::game::is_model("UKS")) eamuse_set_game("QuizKnock STADIUM"); else { log_warning("eamuse", "unknown game model: {}", avs::game::MODEL); eamuse_set_game("unknown"); } } void eamuse_scard_callback(uint8_t slot_no, card_info_t *cardinfo) { log_info( "scard", "eamuse_scard_callback: slot_no={}, card_type={}", slot_no, cardinfo->card_type); if (cardinfo->card_type > 0) { eamuse_card_insert(slot_no & 1, cardinfo->uid); } }