Files
spice2x-r3d/src/spice2x/misc/eamuse.cpp
T
c59d399ab8 Automated PIN Macro after auto-inserting a card (#698)
## Description of change
Adds a way to automatically use the PIN Macro for P1, P2 or both when
Auto Card Insert is used.

### Launcher changes: 
 - Added a checkbox to enable/disable the feature
 - Added a textbox for each player to set what to look for in the logs.

### How it works:
The feature requires Auto Card Insert and PIN Macro to be enabled and
set.
It uses the Auto Card Insert value to enable the feature for P1, P2 or
both, with a "master" checkbox to completely disable the feature. It
then relies on the current PIN Macro code to send the PIN to the games.

The games logs send a message when a new scene is loaded with the name
of the scene. By hooking on the logs, we can see when a PIN is required
and send the relevant macro. Games have different scene names, and
sometimes one for each player, so the field is configurable per game per
player. These could be hard-coded in a lookup table but I didn't want to
recompile the project every time I wanted to test, and I don't have all
the games to get this info from.

The PIN Macro thread is used to hook to the logs and check if the
defined string appears, which triggers the PIN macro just like a button
press would.

Setting nothing for the log trigger disables the feature for that
player. It's useful if someone wants to have Auto Card Insert enabled
for both players but only Auto Pin on 1 player, but is mainly there so
the check won't happen if nothing is set.

## Testing
I used SDVX and DDR to have a single and a two player test with the
feature both enabled and disabled. I also tested multiple combinations
of PIN Macro settings and Auto Card Insert settings with no apparent
issues. The PIN is automatically entered as soon as the game requests
it.

## Discussion

I know that hooking to the logs might not be the best solution. I'm very
open to discussions about this, and understand that this might be a deal
breaker for this feature. I made this to use with my setup and thought
I'd try opening this PR to see if the feature would be useful for
others.

I haven't found another way to make sure we're on the correct screen to
launch the PIN Macro, and sending the macro every time the Auto Card
Insert finishes had sync issues where the end of the PIN would actually
be registered by the game.

It would also be much more convenient for the users to hardcode a lookup
table for every games' log-trigger but I don't have the games currently
so I can't check the logs to see what would be best.

---------

Co-authored-by: Maxime St-Pierre <maxime.stpierre@ticketmaster.com>
2026-05-25 16:00:06 -07:00

743 lines
24 KiB
C++

#include "eamuse.h"
#include <atomic>
#include <fstream>
#include <thread>
#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/precise_timer.h"
#include "util/time.h"
#include "util/utils.h"
#include "overlay/overlay.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<double> AUTO_INSERT_CARD_FIRST_CONSUME_TIME;
static bool AUTO_INSERT_CARD_CACHED[2];
static uint8_t AUTO_INSERT_CARD_CACHED_DATA[2][8];
// pin macro
bool PIN_MACRO_ENABLED = false;
std::string PIN_MACRO_VALUES[2] = {"", ""};
std::string AUTO_PIN_MACRO_TRIGGER[2];
static std::atomic_bool AUTO_PIN_MACRO_REQUEST[2] {false, false};
static bool AUTO_PIN_MACRO_PLAYER_ACTIVE[2] = {false, false};
static std::thread *PIN_MACRO_THREAD = nullptr;
static bool PIN_MACRO_THREAD_ACTIVE = false;
static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = {
games::OverlayButtons::TriggerPinMacroP1,
games::OverlayButtons::TriggerPinMacroP2
};
static bool pin_macro_log_hook(void *, const std::string &data, logger::Style, std::string &) {
for (int unit = 0; unit < 2; unit++) {
if (!AUTO_PIN_MACRO_PLAYER_ACTIVE[unit]) {
continue;
}
if (data.find(AUTO_PIN_MACRO_TRIGGER[unit]) != std::string::npos) {
AUTO_PIN_MACRO_REQUEST[unit].store(true);
}
}
return false;
}
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", "[P{}] Inserted card from reader: {}", index+1, 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) {
// do a quick copy under lock
std::unique_lock<std::mutex> lock(CARD_OVERRIDES_LOCK);
const auto card_override = CARD_OVERRIDES[index];
lock.unlock();
// Check if card overrides are present
if (!card_override.empty()) {
// Override is present
for (int n = 0; n < 16; 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_override, n);
return false;
}
}
// Log info
log_info("eamuse", "[P{}] Inserted card override: {}", index+1, card_override);
// Card is valid, convert and set it.
hex2bin(card_override.c_str(), card);
// cache it for auto-insert
// 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", "[P{}] Auto card insert - caching this card override in memory: {}", index+1, card_override);
}
// 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);
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);
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, n);
return false;
}
}
// info
log_info("eamuse", "[P{}] Inserted {}: {}", index+1, path, 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", "[P{}] Auto card insert - caching this card from file in memory: {}", index+1, 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) {
// some bt5api want to be polled in order.
if (eamuse_get_game_keypads() > 1) {
bt5api_poll_reader_card(1);
bt5api_poll_reader_card(0);
}
else {
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
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", "[P{}] Card insert on reader (total active count: {})", 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;
timeutils::PreciseSleepTimer timer;
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
timer.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_pin_macro_start_thread() {
// set active
PIN_MACRO_THREAD_ACTIVE = true;
// a unit is eligible for auto-trigger only if all the static prerequisites are
// satisfied at startup; precomputing this lets the log hook skip per-line checks
// on values that never change at runtime
for (int unit = 0; unit < 2; unit++) {
AUTO_PIN_MACRO_PLAYER_ACTIVE[unit] =
!AUTO_PIN_MACRO_TRIGGER[unit].empty() &&
!PIN_MACRO_VALUES[unit].empty();
if(!AUTO_PIN_MACRO_TRIGGER[unit].empty() && PIN_MACRO_VALUES[unit].empty()) {
log_warning("eamuse", "Configuration error; Pin Macro empty for P{}.", unit+1);
}
}
// register scene log hook so the macro fires on the per-game trigger string,
// but only if at least one player is eligible
if (AUTO_PIN_MACRO_PLAYER_ACTIVE[0] || AUTO_PIN_MACRO_PLAYER_ACTIVE[1]) {
logger::hook_add(pin_macro_log_hook, nullptr);
log_info("eamuse", "AUTO_PIN_MACRO enabled");
}
// create thread
PIN_MACRO_THREAD = new std::thread([]() {
uint16_t keypad_overrides[] = {
1 << EAM_IO_KEYPAD_0,
1 << EAM_IO_KEYPAD_1,
1 << EAM_IO_KEYPAD_2,
1 << EAM_IO_KEYPAD_3,
1 << EAM_IO_KEYPAD_4,
1 << EAM_IO_KEYPAD_5,
1 << EAM_IO_KEYPAD_6,
1 << EAM_IO_KEYPAD_7,
1 << EAM_IO_KEYPAD_8,
1 << EAM_IO_KEYPAD_9,
};
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()};
std::optional<uint8_t> active_unit = std::nullopt;
timeutils::PreciseSleepTimer timer;
while (PIN_MACRO_THREAD_ACTIVE) {
// wait for key press or auto-trigger
if (!active_unit.has_value()) {
for (int unit = 0; unit < 2; unit++) {
if (PIN_MACRO_VALUES[unit].empty()) {
continue;
}
bool key_press = overlay_buttons &&
(!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) &&
GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(PIN_MACRO_TRIGGER_KEYS[unit]));
bool auto_request = AUTO_PIN_MACRO_REQUEST[unit].exchange(false);
if (auto_request) {
log_info("eamuse", "AUTO_PIN_MACRO_REQUEST detected for P{}", unit+1);
}
if (key_press || auto_request) {
active_unit = unit;
// Reset key index
pin_index[unit] = 0;
break;
}
}
if (!active_unit.has_value()) {
timer.sleep(20);
continue;
}
}
const auto unit = active_unit.value();
// get character from config
if (pin_index[unit] < PIN_MACRO_VALUES[unit].length()) {
// insert character
char pin_char = PIN_MACRO_VALUES[unit].at(pin_index[unit]);
if (pin_char >= '0' && pin_char <= '9') {
int char_index = pin_char - '0';
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
}
pin_index[unit]++;
timer.sleep(100);
// clear
eamuse_set_keypad_overrides(unit, 0);
timer.sleep(50);
// end of PIN
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
active_unit = std::nullopt;
timer.sleep(120);
}
continue;
}
timer.sleep(200);
}
});
}
void eamuse_pin_macro_stop_thread() {
PIN_MACRO_THREAD_ACTIVE = false;
if (PIN_MACRO_THREAD != nullptr) {
PIN_MACRO_THREAD->join();
delete PIN_MACRO_THREAD;
PIN_MACRO_THREAD = nullptr;
}
if (AUTO_PIN_MACRO_PLAYER_ACTIVE[0] || AUTO_PIN_MACRO_PLAYER_ACTIVE[1]) {
logger::hook_remove(pin_macro_log_hook, 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 if (avs::game::is_model("VFG"))
eamuse_set_game("Mahjong Fight Girl");
else if (avs::game::is_model("XIF"))
eamuse_set_game("Polaris Chord");
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_misc("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);
}
}
bool eamuse_scanned_card_peek_noninvasive(size_t unit, char card_uid[8]) {
const int unit_index = unit > 0 ? 1 : 0;
if (!CARD_INSERT_UID_ENABLE[unit_index]) {
return false;
}
memcpy(card_uid, CARD_INSERT_UID[unit_index], 8);
return true;
}
void eamuse_scanned_card_clear(size_t unit) {
const int unit_index = unit > 0 ? 1 : 0;
CARD_INSERT_UID_ENABLE[unit_index] = false;
}