295 lines
11 KiB
C++
295 lines
11 KiB
C++
|
#include "bt5api.h"
|
||
|
#include <string>
|
||
|
#include <thread>
|
||
|
#include <windows.h>
|
||
|
#include <external/robin_hood.h>
|
||
|
#include "games/iidx/iidx.h"
|
||
|
#include "hooks/libraryhook.h"
|
||
|
#include "util/logging.h"
|
||
|
#include "util/libutils.h"
|
||
|
#include "avs/game.h"
|
||
|
#include "eamuse.h"
|
||
|
|
||
|
#ifdef __GNUC__
|
||
|
/* Bemanitools is compiled with GCC (MinGW, specifically) as of version 5 */
|
||
|
#define LOG_CHECK_FMT __attribute__(( format(printf, 2, 3) ))
|
||
|
#else
|
||
|
/* Compile it out for MSVC plebs */
|
||
|
#define LOG_CHECK_FMT
|
||
|
#endif
|
||
|
|
||
|
/* An AVS-style logger function. Comes in four flavors: misc, info, warning,
|
||
|
and fatal, with increasing severity. Fatal loggers do not return, they
|
||
|
abort the running process after writing their message to the log.
|
||
|
|
||
|
"module" is an arbitrary short string identifying the source of the log
|
||
|
message. The name of the calling DLL is a good default choice for this
|
||
|
string, although you might want to identify a module within your DLL here
|
||
|
instead.
|
||
|
|
||
|
"fmt" is a printf-style format string. Depending on the context in which
|
||
|
your DLL is running you might end up calling a logger function exported
|
||
|
from libavs, which has its own printf implementation (including a number of
|
||
|
proprietary extensions), so don't use any overly exotic formats. */
|
||
|
|
||
|
typedef void (*log_formatter_t)(const char *module, const char *fmt, ...)
|
||
|
LOG_CHECK_FMT;
|
||
|
|
||
|
/* An API for spawning threads. This API is defined by libavs, although
|
||
|
Bemanitools itself may supply compatible implementations of these functions
|
||
|
to your DLL, depending on the context in which it runs.
|
||
|
|
||
|
NOTE: You may only use the logging functions from a thread where Bemanitools
|
||
|
calls you, or a thread that you create using this API. Failure to observe
|
||
|
this restriction will cause the process to crash. This is a limitation of
|
||
|
libavs itself, not Bemanitools. */
|
||
|
|
||
|
typedef int (*thread_create_t)(int (*proc)(void *), void *ctx,
|
||
|
uint32_t stack_sz, unsigned int priority);
|
||
|
typedef void (*thread_join_t)(int thread_id, int *result);
|
||
|
typedef void (*thread_destroy_t)(int thread_id);
|
||
|
|
||
|
/* The first function that will be called on your DLL. You will be supplied
|
||
|
with four function pointers that may be used to log messages to the game's
|
||
|
log file. See comments in glue.h for further information. */
|
||
|
typedef void (__cdecl *eam_io_set_loggers_t)(log_formatter_t misc, log_formatter_t info,
|
||
|
log_formatter_t warning, log_formatter_t fatal);
|
||
|
static eam_io_set_loggers_t eam_io_set_loggers = nullptr;
|
||
|
|
||
|
|
||
|
/* Initialize your card reader emulation DLL. Thread management functions are
|
||
|
provided to you; you must use these functions to create your own threads if
|
||
|
you want to make use of the logging functions that are provided to
|
||
|
eam_io_set_loggers(). You will also need to pass these thread management
|
||
|
functions on to geninput if you intend to make use of that library.
|
||
|
|
||
|
See glue.h and geninput.h for further details. */
|
||
|
typedef bool (__cdecl *eam_io_init_t)(thread_create_t thread_create, thread_join_t thread_join,
|
||
|
thread_destroy_t thread_destroy);
|
||
|
static eam_io_init_t eam_io_init = nullptr;
|
||
|
|
||
|
/* Shut down your card reader emulation DLL. */
|
||
|
typedef void (__cdecl *eam_io_fini_t)(void);
|
||
|
static eam_io_fini_t eam_io_fini = nullptr;
|
||
|
|
||
|
/* Return the state of the number pad on your reader. This function will be
|
||
|
called frequently. See enum eam_io_keypad_scan_code above for the meaning of
|
||
|
each bit within the return value.
|
||
|
|
||
|
This function will be called even if the running game does not actually have
|
||
|
a number pad on the real cabinet (e.g. Jubeat).
|
||
|
|
||
|
unit_no is either 0 or 1. Games with only a single reader (jubeat, popn,
|
||
|
drummania) will only use unit_no 0. */
|
||
|
typedef uint16_t (__cdecl *eam_io_get_keypad_state_t)(uint8_t unit_no);
|
||
|
static eam_io_get_keypad_state_t eam_io_get_keypad_state = nullptr;
|
||
|
|
||
|
/* Indicate which sensors (front and back) are triggered for a slotted reader
|
||
|
(refer to enum). To emulate non-slotted readers, just set both sensors
|
||
|
to on to indicate the card is in range of the reader. This function
|
||
|
will be called frequently. */
|
||
|
typedef uint8_t (__cdecl *eam_io_get_sensor_state_t)(uint8_t unit_no);
|
||
|
static eam_io_get_sensor_state_t eam_io_get_sensor_state = nullptr;
|
||
|
|
||
|
/* Read a card ID. This function is only called when the return value of
|
||
|
eam_io_get_sensor_state() changes from false to true, so you may take your
|
||
|
time and perform file I/O etc, within reason. You must return exactly eight
|
||
|
bytes into the buffer pointed to by card_id. */
|
||
|
typedef bool (__cdecl *eam_io_read_card_t)(uint8_t unit_no, uint8_t *card_id, uint8_t nbytes);
|
||
|
static eam_io_read_card_t eam_io_read_card = nullptr;
|
||
|
|
||
|
/* Send a command to the card slot. This is called by the game to execute
|
||
|
certain actions on a slotted reader (refer to enum). When emulating
|
||
|
wave pass readers, this is function is never called. */
|
||
|
typedef bool (__cdecl *eam_io_card_slot_cmd_t)(uint8_t unit_no, uint8_t cmd);
|
||
|
static eam_io_card_slot_cmd_t eam_io_card_slot_cmd = nullptr;
|
||
|
|
||
|
/* This function is called frequently. Update your device and states in here */
|
||
|
typedef bool (__cdecl *eam_io_poll_t)(uint8_t unit_no);
|
||
|
static eam_io_poll_t eam_io_poll = nullptr;
|
||
|
|
||
|
/* Return a pointer to an internal configuration API for use by config.exe.
|
||
|
Custom implementations should return NULL. */
|
||
|
typedef const struct eam_io_config_api* (__cdecl *eam_io_get_config_api_t)(void);
|
||
|
static eam_io_get_config_api_t eam_io_get_config_api = nullptr;
|
||
|
|
||
|
bool BT5API_ENABLED = false;
|
||
|
static HMODULE EAMIO_DLL;
|
||
|
static std::string EAMIO_DLL_NAME = "eamio.dll";
|
||
|
static robin_hood::unordered_map<int, std::thread *> BT5API_THREADS;
|
||
|
static robin_hood::unordered_map<int, int> BT5API_THREAD_RESULTS;
|
||
|
static uint8_t BT5API_CARD_STATES[] = {0, 0};
|
||
|
|
||
|
static void bt5api_log(const char *bt5_module, const char *fmt, ...) {
|
||
|
|
||
|
// pre-compute module
|
||
|
std::string module = fmt::format("bt5api:{}", bt5_module != nullptr ? bt5_module : "(null)");
|
||
|
|
||
|
// string format
|
||
|
char buf[1024];
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
const auto result = std::vsnprintf(buf, sizeof(buf), fmt, args);
|
||
|
va_end(args);
|
||
|
|
||
|
// check if format failed, fallback to logging the format string
|
||
|
if (result < 0) {
|
||
|
log_info(module.c_str(), "{}", fmt);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// log it if the buffer was enough
|
||
|
const size_t len = (size_t) result;
|
||
|
if (len < sizeof(buf)) {
|
||
|
log_info(module.c_str(), "{}", buf);
|
||
|
} else {
|
||
|
|
||
|
// allocate a new string and format again
|
||
|
std::string new_buf(len, '\0');
|
||
|
va_start(args, fmt);
|
||
|
std::vsnprintf(new_buf.data(), len + 1, fmt, args);
|
||
|
va_end(args);
|
||
|
|
||
|
// log the result
|
||
|
log_info(module.c_str(), "{}", new_buf);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int bt5api_thread_create(int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority) {
|
||
|
std::thread *thread = new std::thread([proc, ctx]() {
|
||
|
int thread_id = (int) std::hash<std::thread::id>{}(std::this_thread::get_id());
|
||
|
BT5API_THREAD_RESULTS[thread_id] = proc(ctx);
|
||
|
});
|
||
|
|
||
|
int thread_id = static_cast<int>(std::hash<std::thread::id>{}(thread->get_id()));
|
||
|
BT5API_THREADS[thread_id] = thread;
|
||
|
|
||
|
return thread_id;
|
||
|
}
|
||
|
|
||
|
static void bt5api_thread_join(int thread_id, int *result) {
|
||
|
BT5API_THREADS[thread_id]->join();
|
||
|
|
||
|
if (result != nullptr) {
|
||
|
*result = BT5API_THREAD_RESULTS[thread_id];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void bt5api_thread_destroy(int thread_id) {
|
||
|
auto thread_handle = BT5API_THREADS.find(thread_id);
|
||
|
if (thread_handle != BT5API_THREADS.end()) {
|
||
|
if (thread_handle->second->joinable()) {
|
||
|
thread_handle->second->detach();
|
||
|
}
|
||
|
delete thread_handle->second;
|
||
|
BT5API_THREADS.erase(thread_handle);
|
||
|
}
|
||
|
BT5API_THREAD_RESULTS.erase(thread_id);
|
||
|
}
|
||
|
|
||
|
void bt5api_init() {
|
||
|
|
||
|
// check if already initialized
|
||
|
if (EAMIO_DLL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
log_info("bt5api", "initializing");
|
||
|
|
||
|
// load DLL instances
|
||
|
EAMIO_DLL = libutils::try_library(EAMIO_DLL_NAME);
|
||
|
if (!EAMIO_DLL) {
|
||
|
EAMIO_DLL = libutils::try_library("..\\" + EAMIO_DLL_NAME);
|
||
|
}
|
||
|
if (!EAMIO_DLL) {
|
||
|
log_warning("bt5api", "unable to load '{}': 0x{:x}", EAMIO_DLL_NAME, GetLastError());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// load eamio funcs
|
||
|
eam_io_set_loggers = libutils::try_proc<eam_io_set_loggers_t>(EAMIO_DLL, "eam_io_set_loggers");
|
||
|
eam_io_init = libutils::try_proc<eam_io_init_t>(EAMIO_DLL, "eam_io_init");
|
||
|
eam_io_fini = libutils::try_proc<eam_io_fini_t>(EAMIO_DLL, "eam_io_fini");
|
||
|
eam_io_get_keypad_state = libutils::try_proc<eam_io_get_keypad_state_t>(EAMIO_DLL, "eam_io_get_keypad_state");
|
||
|
eam_io_get_sensor_state = libutils::try_proc<eam_io_get_sensor_state_t>(EAMIO_DLL, "eam_io_get_sensor_state");
|
||
|
eam_io_read_card = libutils::try_proc<eam_io_read_card_t>(EAMIO_DLL, "eam_io_read_card");
|
||
|
eam_io_card_slot_cmd = libutils::try_proc<eam_io_card_slot_cmd_t>(EAMIO_DLL, "eam_io_card_slot_cmd");
|
||
|
eam_io_poll = libutils::try_proc<eam_io_poll_t>(EAMIO_DLL, "eam_io_poll");
|
||
|
eam_io_get_config_api = libutils::try_proc<eam_io_get_config_api_t>(EAMIO_DLL, "eam_io_get_config_api");
|
||
|
|
||
|
// initialize
|
||
|
eam_io_set_loggers(&bt5api_log, &bt5api_log, &bt5api_log, &bt5api_log);
|
||
|
eam_io_init(&bt5api_thread_create, &bt5api_thread_join, &bt5api_thread_destroy);
|
||
|
|
||
|
// bt5api workaround for games with 2 card readers (NFCeAmuse cares about this)
|
||
|
if (eamuse_get_game_keypads() > 1) {
|
||
|
bt5api_poll_reader_card(1);
|
||
|
bt5api_poll_reader_card(0);
|
||
|
}
|
||
|
|
||
|
// done
|
||
|
log_info("bt5api", "done initializing");
|
||
|
}
|
||
|
|
||
|
void bt5api_hook(HINSTANCE module) {
|
||
|
libraryhook_enable(module);
|
||
|
|
||
|
// toastertools
|
||
|
libraryhook_hook_proc("iidx_io_ext_get_16seg", games::iidx::get_16seg);
|
||
|
}
|
||
|
|
||
|
void bt5api_poll_reader_card(uint8_t unit_no) {
|
||
|
|
||
|
// check if initialized
|
||
|
if (!EAMIO_DLL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// poll
|
||
|
if (!eam_io_poll(unit_no)) {
|
||
|
log_warning("bt5api", "polling bt5api reader {} returned failure",
|
||
|
static_cast<int>(unit_no));
|
||
|
}
|
||
|
|
||
|
// get sensor state
|
||
|
uint8_t sensor_state = eam_io_get_sensor_state(unit_no);
|
||
|
|
||
|
// check for card in
|
||
|
if (sensor_state > BT5API_CARD_STATES[unit_no]) {
|
||
|
uint8_t card_id[8];
|
||
|
eam_io_read_card(unit_no, card_id, 8);
|
||
|
eamuse_card_insert(unit_no, card_id);
|
||
|
}
|
||
|
|
||
|
// save state
|
||
|
BT5API_CARD_STATES[unit_no] = sensor_state;
|
||
|
}
|
||
|
|
||
|
void bt5api_poll_reader_keypad(uint8_t unit_no) {
|
||
|
|
||
|
// check if initialized
|
||
|
if (!EAMIO_DLL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// poll
|
||
|
if (!eam_io_poll(unit_no)) {
|
||
|
log_warning("bt5api", "polling bt5api reader {} returned failure",
|
||
|
static_cast<int>(unit_no));
|
||
|
}
|
||
|
|
||
|
// get keypad
|
||
|
eamuse_set_keypad_overrides_bt5(unit_no, eam_io_get_keypad_state(unit_no));
|
||
|
}
|
||
|
|
||
|
void bt5api_dispose() {
|
||
|
|
||
|
// check if initialized
|
||
|
if (!EAMIO_DLL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// finish
|
||
|
eam_io_fini();
|
||
|
}
|