Boils down to: - Include headers - Reduce boiler plate with helpers - Swap out explicit usages with core API layer and ensure the right API is configured beforehand
512 lines
12 KiB
C
512 lines
12 KiB
C
#include <windows.h>
|
|
|
|
#include <ctype.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "bemanitools/eamio.h"
|
|
|
|
#include "core/log.h"
|
|
|
|
#include "eamio/eam-impl.h"
|
|
|
|
#include "geninput/hid-mgr.h"
|
|
|
|
#include "util/defs.h"
|
|
#include "util/fs.h"
|
|
#include "util/hex.h"
|
|
#include "util/mem.h"
|
|
#include "util/str.h"
|
|
|
|
#define EAM_SENSOR_COOLDOWN 1000
|
|
|
|
struct eam_unit {
|
|
char *card_path;
|
|
struct hid_stub *hid;
|
|
size_t keypad_ctls[EAM_IO_KEYPAD_COUNT];
|
|
size_t sensor_ctl;
|
|
bool bound_ctls;
|
|
uint8_t drive_no;
|
|
uint32_t sensor_time;
|
|
bool sensor_hot;
|
|
};
|
|
|
|
struct eam {
|
|
/* This lock protects drive_no and sensor_time, which get read/written
|
|
respectively by eam_impl_notify_hotplug, which gets called by the Win32
|
|
message pump thread. */
|
|
|
|
CRITICAL_SECTION lock;
|
|
struct eam_unit units[EAM_UNIT_COUNT];
|
|
bool autogen;
|
|
bool alt_10k;
|
|
bool mux;
|
|
};
|
|
|
|
static const uint32_t eam_keypad_usages[EAM_IO_KEYPAD_COUNT + 1] = {
|
|
/* [EAM_KEYPAD_0] = */ 0x00070062,
|
|
/* [EAM_KEYPAD_1] = */ 0x00070059,
|
|
/* [EAM_KEYPAD_4] = */ 0x0007005C,
|
|
/* [EAM_KEYPAD_7] = */ 0x0007005F,
|
|
/* [EAM_KEYPAD_00] = */ 0x00070058, /* Keypad ENTER */
|
|
/* [EAM_KEYPAD_2] = */ 0x0007005A,
|
|
/* [EAM_KEYPAD_5] = */ 0x0007005D,
|
|
/* [EAM_KEYPAD_8] = */ 0x00070060,
|
|
/* [EAM_KEYPAD_DECIMAL] = */ 0x00070063,
|
|
/* [EAM_KEYPAD_3] = */ 0x0007005B,
|
|
/* [EAM_KEYPAD_6] = */ 0x0007005E,
|
|
/* [EAM_KEYPAD_9] = */ 0x00070061,
|
|
/* Sensor = */ 0x00070057};
|
|
|
|
static const uint32_t eam_keypad_usages_alt[EAM_IO_KEYPAD_COUNT + 1] = {
|
|
/* [EAM_KEYPAD_0] = */ 0x00070027,
|
|
/* [EAM_KEYPAD_1] = */ 0x0007001E,
|
|
/* [EAM_KEYPAD_4] = */ 0x00070021,
|
|
/* [EAM_KEYPAD_7] = */ 0x00070024,
|
|
/* [EAM_KEYPAD_00] = */ 0x0007002D, /* - and _ */
|
|
/* [EAM_KEYPAD_2] = */ 0x0007001F,
|
|
/* [EAM_KEYPAD_5] = */ 0x00070022,
|
|
/* [EAM_KEYPAD_8] = */ 0x00070025,
|
|
/* [EAM_KEYPAD_DECIMAL] = */ 0x0007002E, /* + and = */
|
|
/* [EAM_KEYPAD_3] = */ 0x00070020,
|
|
/* [EAM_KEYPAD_6] = */ 0x00070023,
|
|
/* [EAM_KEYPAD_9] = */ 0x00070026,
|
|
/* Sensor = */ 0x0007002A /* Backspace */
|
|
};
|
|
|
|
static uint8_t eam_impl_get_active_unit(void);
|
|
static void eam_impl_bind_keypad(struct eam *eam, uint8_t unit_no);
|
|
static bool eam_impl_autogen(struct eam_unit *unit, uint8_t *card_id);
|
|
|
|
struct eam *eam_impl_create(void)
|
|
{
|
|
struct eam *eam;
|
|
struct eam_unit *unit;
|
|
size_t btn_no;
|
|
uint8_t unit_no;
|
|
|
|
eam = xmalloc(sizeof(*eam));
|
|
|
|
InitializeCriticalSection(&eam->lock);
|
|
|
|
for (unit_no = 0; unit_no < lengthof(eam->units); unit_no++) {
|
|
unit = &eam->units[unit_no];
|
|
|
|
unit->card_path = NULL;
|
|
unit->hid = NULL;
|
|
|
|
for (btn_no = 0; btn_no < lengthof(unit->keypad_ctls); btn_no++) {
|
|
unit->keypad_ctls[btn_no] = (size_t) -1;
|
|
}
|
|
|
|
unit->sensor_ctl = (size_t) -1;
|
|
unit->bound_ctls = false;
|
|
unit->drive_no = (uint8_t) -1;
|
|
unit->sensor_time = 0;
|
|
}
|
|
|
|
eam->autogen = false;
|
|
eam->alt_10k = false;
|
|
eam->mux = false;
|
|
|
|
return eam;
|
|
}
|
|
|
|
static uint8_t eam_impl_get_active_unit(void)
|
|
{
|
|
return GetKeyState(VK_NUMLOCK) & 0x0001;
|
|
}
|
|
|
|
bool eam_impl_get_autogen(struct eam *eam)
|
|
{
|
|
return eam->autogen;
|
|
}
|
|
|
|
void eam_impl_set_autogen(struct eam *eam, bool autogen)
|
|
{
|
|
eam->autogen = autogen != false;
|
|
}
|
|
|
|
bool eam_impl_get_alt_10k(struct eam *eam)
|
|
{
|
|
return eam->alt_10k;
|
|
}
|
|
|
|
void eam_impl_set_alt_10k(struct eam *eam, bool alt_10k)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < lengthof(eam->units); i++) {
|
|
eam->units[i].bound_ctls = false;
|
|
}
|
|
|
|
eam->alt_10k = alt_10k != false;
|
|
}
|
|
|
|
struct hid_stub *eam_impl_get_keypad_device(struct eam *eam, uint8_t unit_no)
|
|
{
|
|
log_assert(unit_no < lengthof(eam->units));
|
|
|
|
return eam->units[unit_no].hid;
|
|
}
|
|
|
|
void eam_impl_set_keypad_device(
|
|
struct eam *eam, uint8_t unit_no, struct hid_stub *hid)
|
|
{
|
|
log_assert(unit_no < lengthof(eam->units));
|
|
|
|
eam->units[unit_no].hid = hid;
|
|
eam->units[unit_no].bound_ctls = false;
|
|
|
|
eam->mux = hid != NULL && eam->units[0].hid == eam->units[1].hid;
|
|
}
|
|
|
|
const char *eam_impl_get_card_path(struct eam *eam, uint8_t unit_no)
|
|
{
|
|
log_assert(unit_no < lengthof(eam->units));
|
|
|
|
return eam->units[unit_no].card_path;
|
|
}
|
|
|
|
void eam_impl_set_card_path(struct eam *eam, uint8_t unit_no, const char *path)
|
|
{
|
|
log_assert(unit_no < lengthof(eam->units));
|
|
|
|
EnterCriticalSection(&eam->lock);
|
|
|
|
free(eam->units[unit_no].card_path);
|
|
|
|
if (path != NULL) {
|
|
eam->units[unit_no].card_path = str_dup(path);
|
|
|
|
if (isalpha(path[0]) && path[1] == ':') {
|
|
eam->units[unit_no].drive_no = toupper(path[0]) - 'A';
|
|
} else {
|
|
eam->units[unit_no].drive_no = (uint8_t) -1;
|
|
}
|
|
} else {
|
|
eam->units[unit_no].card_path = NULL;
|
|
eam->units[unit_no].drive_no = (uint8_t) -1;
|
|
}
|
|
|
|
LeaveCriticalSection(&eam->lock);
|
|
}
|
|
|
|
uint16_t eam_impl_get_keypad_state(struct eam *eam, uint8_t unit_no)
|
|
{
|
|
struct eam_unit *unit;
|
|
uint16_t result;
|
|
int32_t value;
|
|
size_t i;
|
|
|
|
log_assert(unit_no < lengthof(eam->units));
|
|
|
|
if (eam->mux && eam_impl_get_active_unit() != unit_no) {
|
|
return 0;
|
|
}
|
|
|
|
eam_impl_bind_keypad(eam, unit_no);
|
|
|
|
unit = &eam->units[unit_no];
|
|
result = 0;
|
|
|
|
if (unit->bound_ctls) {
|
|
for (i = 0; i < lengthof(unit->keypad_ctls); i++) {
|
|
if (unit->keypad_ctls[i] == (size_t) -1) {
|
|
continue;
|
|
}
|
|
|
|
if (!hid_stub_get_value(unit->hid, unit->keypad_ctls[i], &value)) {
|
|
continue;
|
|
}
|
|
|
|
if (value) {
|
|
result |= 1 << i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void eam_impl_bind_keypad(struct eam *eam, uint8_t unit_no)
|
|
{
|
|
const uint32_t *usages;
|
|
struct hid_control *controls;
|
|
struct eam_unit *unit;
|
|
size_t ncontrols;
|
|
size_t control_no;
|
|
size_t btn_no;
|
|
|
|
unit = &eam->units[unit_no];
|
|
|
|
if (unit->bound_ctls || unit->hid == NULL ||
|
|
!hid_stub_is_attached(unit->hid)) {
|
|
return;
|
|
}
|
|
|
|
if (eam->alt_10k) {
|
|
usages = eam_keypad_usages_alt;
|
|
} else {
|
|
usages = eam_keypad_usages;
|
|
}
|
|
|
|
/* Set flag first: don't try again if this fails for whatever reason. */
|
|
|
|
unit->bound_ctls = true;
|
|
|
|
hid_mgr_lock();
|
|
|
|
if (!hid_stub_get_controls(unit->hid, NULL, &ncontrols)) {
|
|
goto size_fail;
|
|
}
|
|
|
|
controls = xmalloc(ncontrols * sizeof(*controls));
|
|
|
|
if (!hid_stub_get_controls(unit->hid, controls, &ncontrols)) {
|
|
goto content_fail;
|
|
}
|
|
|
|
for (control_no = 0; control_no < ncontrols; control_no++) {
|
|
for (btn_no = 0; btn_no < EAM_IO_KEYPAD_COUNT; btn_no++) {
|
|
if (controls[control_no].usage == usages[btn_no]) {
|
|
unit->keypad_ctls[btn_no] = control_no;
|
|
}
|
|
}
|
|
|
|
if (controls[control_no].usage == usages[EAM_IO_KEYPAD_COUNT]) {
|
|
unit->sensor_ctl = control_no;
|
|
}
|
|
}
|
|
|
|
free(controls);
|
|
|
|
hid_mgr_unlock();
|
|
|
|
return;
|
|
|
|
content_fail:
|
|
free(controls);
|
|
|
|
size_fail:
|
|
hid_mgr_unlock();
|
|
}
|
|
|
|
bool eam_impl_get_sensor_state(struct eam *eam, uint8_t unit_no)
|
|
{
|
|
struct eam_unit *unit;
|
|
uint32_t now;
|
|
int32_t value;
|
|
bool result;
|
|
|
|
log_assert(unit_no < lengthof(eam->units));
|
|
|
|
unit = &eam->units[unit_no];
|
|
result = false;
|
|
|
|
eam_impl_bind_keypad(eam, unit_no);
|
|
|
|
if (!unit->bound_ctls) {
|
|
return false;
|
|
}
|
|
|
|
if (unit->sensor_ctl == (size_t) -1) {
|
|
return false;
|
|
}
|
|
|
|
if (!hid_stub_get_value(unit->hid, unit->sensor_ctl, &value)) {
|
|
return false;
|
|
}
|
|
|
|
now = GetTickCount();
|
|
|
|
EnterCriticalSection(&eam->lock);
|
|
|
|
/* Bump cooldown as long as sensor button is held
|
|
(cooldown timer might also be set by a USB hotplug event) */
|
|
|
|
if (value != 0 && (!eam->mux || eam_impl_get_active_unit() == unit_no)) {
|
|
unit->sensor_time = now + EAM_SENSOR_COOLDOWN;
|
|
unit->sensor_hot = true;
|
|
}
|
|
|
|
if (unit->sensor_hot) {
|
|
if ((int32_t) (unit->sensor_time - now) > 0) {
|
|
result = true;
|
|
} else {
|
|
unit->sensor_time = 0;
|
|
unit->sensor_hot = false;
|
|
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&eam->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
uint8_t eam_impl_read_card(
|
|
struct eam *eam, uint8_t unit_no, uint8_t *card_id, uint8_t nbytes)
|
|
{
|
|
char line[128];
|
|
struct eam_unit *unit;
|
|
size_t len;
|
|
FILE *f;
|
|
|
|
log_assert(unit_no < lengthof(eam->units));
|
|
log_assert(card_id != NULL);
|
|
log_assert(nbytes == EAM_CARD_NBYTES);
|
|
|
|
unit = &eam->units[unit_no];
|
|
|
|
if (unit->card_path == NULL) {
|
|
goto path_fail;
|
|
}
|
|
|
|
f = fopen(unit->card_path, "r");
|
|
|
|
if (f == NULL) {
|
|
if (eam->autogen) {
|
|
if (!eam_impl_autogen(unit, card_id)) {
|
|
log_warning(
|
|
"Unit %d: Failed to generate card ID into %s",
|
|
unit_no,
|
|
unit->card_path);
|
|
|
|
goto fopen_fail;
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
log_warning(
|
|
"Unit %d: Card file at %s not present",
|
|
unit_no,
|
|
unit->card_path);
|
|
|
|
goto fopen_fail;
|
|
}
|
|
}
|
|
|
|
if (fgets(line, sizeof(line), f) == NULL) {
|
|
log_warning("%s: fgets() failed", unit->card_path);
|
|
|
|
goto fgets_fail;
|
|
}
|
|
|
|
str_trim(line);
|
|
len = strlen(line);
|
|
|
|
if (len != 2 * EAM_CARD_NBYTES) {
|
|
log_warning(
|
|
"%s: Expected %u chars (got %u)",
|
|
unit->card_path,
|
|
2 * EAM_CARD_NBYTES,
|
|
(unsigned int) len);
|
|
|
|
goto len_fail;
|
|
}
|
|
|
|
if (!hex_decode(card_id, nbytes, line, len)) {
|
|
log_warning("%s: Invalid hex [%s]", unit->card_path, card_id);
|
|
|
|
goto decode_fail;
|
|
}
|
|
|
|
log_misc(
|
|
"Unit %d: Loaded card ID [%s] from file %s",
|
|
unit_no,
|
|
line,
|
|
unit->card_path);
|
|
|
|
fclose(f);
|
|
|
|
if (card_id[0] == 0xe0 && card_id[1] == 0x04) {
|
|
return EAM_IO_CARD_ISO15696;
|
|
} else {
|
|
return EAM_IO_CARD_FELICA;
|
|
}
|
|
|
|
decode_fail:
|
|
len_fail:
|
|
fgets_fail:
|
|
fclose(f);
|
|
|
|
fopen_fail:
|
|
path_fail:
|
|
return EAM_IO_CARD_NONE;
|
|
}
|
|
|
|
static bool eam_impl_autogen(struct eam_unit *unit, uint8_t *card_id)
|
|
{
|
|
char hex[2 * EAM_CARD_NBYTES + 1];
|
|
FILE *f;
|
|
size_t i;
|
|
|
|
f = fopen(unit->card_path, "w");
|
|
|
|
if (f == NULL) {
|
|
return false;
|
|
}
|
|
|
|
srand(GetTickCount());
|
|
|
|
card_id[0] = 0xE0;
|
|
card_id[1] = 0x04;
|
|
card_id[2] = 0x01;
|
|
card_id[3] = 0x00;
|
|
|
|
for (i = 4; i < 8; i++) {
|
|
/* LSBit entropy of typical LFSR RNGs is usually poor */
|
|
card_id[i] = rand() >> 7;
|
|
}
|
|
|
|
hex_encode_uc(card_id, EAM_CARD_NBYTES, hex, sizeof(hex));
|
|
fwrite(hex, sizeof(hex) - 1, 1, f);
|
|
|
|
fclose(f);
|
|
|
|
log_info(
|
|
"Generated random card ID [%s] into file %s", hex, unit->card_path);
|
|
|
|
return true;
|
|
}
|
|
|
|
void eam_impl_notify_hotplug(struct eam *eam, uint8_t drive_no)
|
|
{
|
|
struct eam_unit *unit;
|
|
uint8_t unit_no;
|
|
|
|
EnterCriticalSection(&eam->lock);
|
|
|
|
for (unit_no = 0; unit_no < lengthof(eam->units); unit_no++) {
|
|
if (eam->units[unit_no].drive_no == drive_no) {
|
|
/* MMSYSTEM timeGetTime() is overkill, we don't exactly need super
|
|
accurate timestamps here. */
|
|
|
|
unit = &eam->units[unit_no];
|
|
|
|
unit->sensor_time = GetTickCount() + EAM_SENSOR_COOLDOWN;
|
|
unit->sensor_hot = true;
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&eam->lock);
|
|
}
|
|
|
|
void eam_impl_destroy(struct eam *eam)
|
|
{
|
|
int8_t unit_no;
|
|
|
|
for (unit_no = lengthof(eam->units) - 1; unit_no >= 0; unit_no--) {
|
|
free(eam->units[unit_no].card_path);
|
|
}
|
|
|
|
DeleteCriticalSection(&eam->lock);
|
|
|
|
free(eam);
|
|
}
|