spicetools/misc/eamuse.cpp

571 lines
18 KiB
C++
Raw Permalink Normal View History

2024-08-28 15:10:34 +00:00
#include "eamuse.h"
#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/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<double> 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);
}
}