1
0
mirror of https://github.com/djhackersdev/bemanitools.git synced 2025-02-17 19:19:16 +01:00

aciodrv: Add wavepass support to ICCA (#1)

Tested with:
aciotest / eamiotest:
 - DDR Slotted: Node 1: type 3, flag 0, version 1.1.0, product ICCA, build date: Oct 26 2005 13:55:03
 - DDR Slotted: Node 2: type 3, flag 0, version 1.1.0, product ICCA, build date: Oct 26 2005 13:55:03
(tested with just 1, as well as a pair)

aciotest / eamiotest / IIDX10,12,27:
 - IIDX Wavepass: Node 1: type 3, flag 0, version 1.5.1, product ICCB, build date: Apr 12 2010 09:29:00
 - IIDX Wavepass: Node 2: type 3, flag 0, version 1.5.1, product ICCB, build date: Apr 12 2010 09:29:00
(IIDX13 errors on card-in, probably issue with how eamio is being used as previously eamio-icca had no use on it)

aciotest / eamiotest / sdvx1-5:
 - SDVX Wavepass: Node 1: type 3, flag 0, version 1.5.1, product ICCB, build date: Apr 12 2010 09:29:00

aciotest:
 - Jubeat Wavepass:  Node 1: type 3, flag 0, version 1.7.3, product ICCC, build date: Oct 05 2012 20:26:53
 - Museca Wavepass:  Node 2: type 3, flag 0, version 1.7.4, product ICCC, build date: Feb 27 2013 16:44:51
This commit is contained in:
Will Xyen 2021-03-17 10:34:30 -07:00
parent 609c19b0ca
commit f9b37c7a72
8 changed files with 262 additions and 68 deletions

View File

@ -29,6 +29,14 @@ enum ac_io_icca_sensor_state {
AC_IO_ICCA_SENSOR_MASK_BACK_ON = (1 << 5)
};
enum ac_io_icca_status_code {
AC_IO_ICCA_STATUS_FAULT = 0x00,
AC_IO_ICCA_STATUS_IDLE = 0x01,
AC_IO_ICCA_STATUS_GOT_UID = 0x02,
AC_IO_ICCA_STATUS_IDLE_NEW = 0x04,
AC_IO_ICCA_STATUS_BUSY_NEW = 0x01,
};
enum ac_io_icca_keypad_mask {
AC_IO_ICCA_KEYPAD_MASK_EMPTY = (1 << 0),
AC_IO_ICCA_KEYPAD_MASK_3 = (1 << 1),

View File

@ -18,7 +18,7 @@
struct aciodrv_device_ctx {
HANDLE fd;
char node_products[ACIO_MAX_NODES_PER_PORT][ACIO_NODE_PRODUCT_CODE_LEN];
struct aciodrv_device_node_version node_versions[ACIO_MAX_NODES_PER_PORT];
uint8_t msg_counter;
uint8_t node_count;
};
@ -233,7 +233,7 @@ static uint8_t aciodrv_device_enum_nodes(struct aciodrv_device_ctx *device)
return msg.cmd.count;
}
static bool aciodrv_device_get_version(struct aciodrv_device_ctx *device, uint8_t node_id, char product[4])
static bool aciodrv_device_get_version(struct aciodrv_device_ctx *device, uint8_t node_id, struct aciodrv_device_node_version *version)
{
struct ac_io_message msg;
@ -266,7 +266,10 @@ static bool aciodrv_device_get_version(struct aciodrv_device_ctx *device, uint8_
msg.cmd.version.date,
msg.cmd.version.time);
memcpy(product, msg.cmd.version.product_code, 4);
memcpy(version->product, msg.cmd.version.product_code, ACIO_NODE_PRODUCT_CODE_LEN);
version->major = msg.cmd.version.major;
version->minor = msg.cmd.version.minor;
version->revision = msg.cmd.version.revision;
return true;
}
@ -325,7 +328,7 @@ struct aciodrv_device_ctx *aciodrv_device_open_path(const char *port_path, int b
for (uint8_t i = 0; i < device->node_count; i++) {
if (!aciodrv_device_get_version(
device, i + 1, device->node_products[i])) {
device, i + 1, &device->node_versions[i])) {
aciodrv_device_close(device);
return NULL;
}
@ -353,10 +356,19 @@ bool aciodrv_device_get_node_product_ident(struct aciodrv_device_ctx *device, ui
return false;
}
memcpy(product, device->node_products[node_id], ACIO_NODE_PRODUCT_CODE_LEN);
memcpy(product, device->node_versions[node_id].product, ACIO_NODE_PRODUCT_CODE_LEN);
return true;
}
const struct aciodrv_device_node_version *aciodrv_device_get_node_product_version(struct aciodrv_device_ctx *device, uint8_t node_id)
{
if (device->node_count == 0 || node_id > device->node_count) {
return NULL;
}
return &device->node_versions[node_id];
}
bool aciodrv_send_and_recv(struct aciodrv_device_ctx *device, struct ac_io_message *msg, int max_resp_size)
{
msg->cmd.seq_no = device->msg_counter++;

View File

@ -11,6 +11,13 @@
struct aciodrv_device_ctx;
struct aciodrv_device_node_version {
char product[ACIO_NODE_PRODUCT_CODE_LEN];
uint8_t major;
uint8_t minor;
uint8_t revision;
};
/**
* Open an ACIO device connected to a serial port.
*
@ -45,6 +52,15 @@ uint8_t aciodrv_device_get_node_count(struct aciodrv_device_ctx *device);
*/
bool aciodrv_device_get_node_product_ident(struct aciodrv_device_ctx *device, uint8_t node_id, char product[ACIO_NODE_PRODUCT_CODE_LEN]);
/**
* Get the product version of an enumerated node.
*
* @param device Context of opened device
* @param node_id Id of the node. Needs to be in range of the total node count.
* @return Pointer to the version struct
*/
const struct aciodrv_device_node_version *aciodrv_device_get_node_product_version(struct aciodrv_device_ctx *device, uint8_t node_id);
/**
* Send a message to the ACIO bus and receive an answer.
* Use this to implement the protocol for each type of device that can be

View File

@ -3,6 +3,7 @@
#include <string.h>
#include "aciodrv/device.h"
#include "aciodrv/icca.h"
#include "util/log.h"
@ -134,4 +135,53 @@ bool aciodrv_icca_read_card(
}
return true;
}
}
bool aciodrv_icca_is_slotted(
struct aciodrv_device_ctx *device,
uint8_t node_id)
{
struct aciodrv_device_node_version *version;
version = aciodrv_device_get_node_product_version(device, node_id);
// current heuristic is to check if version >= 1.5
if (version) {
if (version->major == 1) {
if (version->minor >= 5) {
return false;
}
}
}
return true;
}
bool aciodrv_icca_poll_felica(
struct aciodrv_device_ctx *device,
uint8_t node_id)
{
struct ac_io_message msg;
log_assert(device);
msg.addr = node_id + 1;
msg.cmd.code = ac_io_u16(AC_IO_ICCA_CMD_POLL_FELICA);
msg.cmd.nbytes = 4;
/* buffer size of data we expect */
msg.cmd.count = 1;
// additional data, not sure
msg.cmd.raw[1] = 0x03;
msg.cmd.raw[2] = 0xFF;
msg.cmd.raw[3] = 0xFF;
if (!aciodrv_send_and_recv(
device,
&msg,
offsetof(struct ac_io_message, cmd.raw) + msg.cmd.count)) {
log_warning("Reading card of node %d failed", node_id + 1);
return false;
}
return true;
}

View File

@ -71,4 +71,41 @@ bool aciodrv_icca_read_card(
uint8_t node_id,
struct ac_io_icca_state *state);
#endif
/**
* Uses some heruistics based on the product ident to determine if the detected
* device is a slotted (true) or wavepass reader (false).
*
* This function will also return false if the provided node_id is invalid.
*
* @param device Context of opened device
* @param node_id Id of the node to query (0 based).
* @return True on slotted, false on wavepass.
* @note This module is supposed to be used in combination with the common
* device driver foundation.
* @see driver.h
*/
bool aciodrv_icca_is_slotted(
struct aciodrv_device_ctx *device,
uint8_t node_id);
/**
* Polls the felica chip on wavepass readers. This will cause the state of the
* reader to be AC_IO_ICCA_STATUS_BUSY_NEW for a few polls, before returning
* either: AC_IO_ICCA_STATUS_IDLE_NEW or AC_IO_ICCA_STATUS_GOT_UID.
*
* The user should take care to call this every so often to actually get new
* felica UIDs, instead of just old NFC idents. The game seems to do it every
* 5 or so polls after the last AC_IO_ICCA_STATUS_BUSY_NEW poll.
*
* @param device Context of opened device
* @param node_id Id of the node to query (0 based).
* @return True on success, false on error.
* @note This module is supposed to be used in combination with the common
* device driver foundation.
* @see driver.h
*/
bool aciodrv_icca_poll_felica(
struct aciodrv_device_ctx *device,
uint8_t node_id);
#endif

View File

@ -29,13 +29,6 @@ enum ac_io_icca_flag {
AC_IO_ICCA_FLAG_SOLENOID = 0x40
};
enum ac_io_icca_status_code {
AC_IO_ICCA_STATUS_FAULT = 0x00,
AC_IO_ICCA_STATUS_IDLE = 0x01,
AC_IO_ICCA_STATUS_GOT_UID = 0x02,
AC_IO_ICCA_STATUS_IDLE_NEW = 0x04
};
static void ac_io_emu_icca_cmd_send_version(
struct ac_io_emu_icca *icca, const struct ac_io_message *req);

View File

@ -5,11 +5,25 @@
#include "aciodrv/icca.h"
struct icca_handler_ctx {
bool init;
bool slotted_reader;
uint8_t last_poll;
};
bool aciotest_icca_handler_init(
struct aciodrv_device_ctx *device, uint8_t node_id, void **ctx)
{
*ctx = malloc(sizeof(uint32_t));
*((uint32_t *) *ctx) = 0;
*ctx = malloc(sizeof(struct icca_handler_ctx));
struct icca_handler_ctx *icca_ctx = (struct icca_handler_ctx*)*ctx;
icca_ctx->init = false;
icca_ctx->slotted_reader = true;
icca_ctx->last_poll = 0;
icca_ctx->slotted_reader = aciodrv_icca_is_slotted(device, node_id);
return aciodrv_icca_init(device, node_id);
}
@ -17,13 +31,17 @@ bool aciotest_icca_handler_init(
bool aciotest_icca_handler_update(
struct aciodrv_device_ctx *device, uint8_t node_id, void *ctx)
{
if (*((uint32_t *) ctx) == 0) {
*((uint32_t *) ctx) = 1;
struct icca_handler_ctx *icca_ctx = (struct icca_handler_ctx*)ctx;
/* eject cards that were left in the reader */
if (!aciodrv_icca_set_state(
device, node_id, AC_IO_ICCA_SLOT_STATE_EJECT, NULL)) {
return false;
if (icca_ctx->init == false) {
icca_ctx->init = true;
if (icca_ctx->slotted_reader) {
/* eject cards that were left in the reader */
if (!aciodrv_icca_set_state(
device, node_id, AC_IO_ICCA_SLOT_STATE_EJECT, NULL)) {
return false;
}
}
}
@ -60,33 +78,48 @@ bool aciotest_icca_handler_update(
state.key_events[0],
state.key_events[1]);
/* eject card with "empty" key */
if (state.key_state & AC_IO_ICCA_KEYPAD_MASK_EMPTY) {
if (!aciodrv_icca_set_state(
device, node_id, AC_IO_ICCA_SLOT_STATE_EJECT, NULL)) {
return false;
}
}
/* allow new card to be inserted when slot is clear */
if (!(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_BACK_ON) &&
!(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_FRONT_ON)) {
if (!aciodrv_icca_set_state(
device, node_id, AC_IO_ICCA_SLOT_STATE_OPEN, NULL)) {
return false;
}
}
/* lock the card when fully inserted */
if ((state.sensor_state & AC_IO_ICCA_SENSOR_MASK_BACK_ON) &&
(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_FRONT_ON)) {
if (!aciodrv_icca_set_state(
device, node_id, AC_IO_ICCA_SLOT_STATE_CLOSE, NULL)) {
return false;
if (icca_ctx->slotted_reader) {
/* eject card with "empty" key */
if (state.key_state & AC_IO_ICCA_KEYPAD_MASK_EMPTY) {
if (!aciodrv_icca_set_state(
device, node_id, AC_IO_ICCA_SLOT_STATE_EJECT, NULL)) {
return false;
}
}
if (!aciodrv_icca_read_card(device, node_id, NULL)) {
return false;
/* allow new card to be inserted when slot is clear */
if (!(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_BACK_ON) &&
!(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_FRONT_ON)) {
if (!aciodrv_icca_set_state(
device, node_id, AC_IO_ICCA_SLOT_STATE_OPEN, NULL)) {
return false;
}
}
/* lock the card when fully inserted */
if ((state.sensor_state & AC_IO_ICCA_SENSOR_MASK_BACK_ON) &&
(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_FRONT_ON)) {
if (!aciodrv_icca_set_state(
device, node_id, AC_IO_ICCA_SLOT_STATE_CLOSE, NULL)) {
return false;
}
if (!aciodrv_icca_read_card(device, node_id, NULL)) {
return false;
}
}
} else {
// wavepass reader, see eamio-icca for why this is done this way
if (state.status_code != AC_IO_ICCA_STATUS_BUSY_NEW) {
++icca_ctx->last_poll;
}
if (icca_ctx->last_poll >= 5) {
if (!aciodrv_icca_poll_felica(device, node_id)) {
return false;
}
icca_ctx->last_poll = 0;
}
}

View File

@ -9,6 +9,7 @@
#include <stdio.h>
#include "aciomgr/manager.h"
#include "aciodrv/device.h"
#include "aciodrv/icca.h"
#include "bemanitools/eamio.h"
@ -44,6 +45,10 @@ static struct aciomgr_port_dispatcher *acio_manager_ctx;
static int32_t icca_node_id[NUMBER_OF_EMULATED_READERS];
static bool icca_is_slotted[NUMBER_OF_EMULATED_READERS];
static int32_t icca_poll_counter[NUMBER_OF_EMULATED_READERS];
void eam_io_set_loggers(
log_formatter_t misc,
log_formatter_t info,
@ -103,7 +108,7 @@ bool eam_io_init(
acio_manager_ctx = aciomgr_port_init(config_icc.port, config_icc.baud);
if (acio_manager_ctx == NULL) {
log_warning("Opening acio device on COM1 failed");
log_warning("Opening acio device on %s failed", config_icc.port);
return false;
}
@ -129,15 +134,15 @@ bool eam_io_init(
icca_node_id[i] = nid;
icca_is_slotted[i] = true;
icca_poll_counter[i] = 0;
icca_is_slotted[i] = aciodrv_icca_is_slotted(device, nid);
if (!aciodrv_icca_init(device, icca_node_id[i])) {
log_warning("Initializing icca %d failed", i);
// if we have at least 1 valid reader, don't fail
// (ex: for games that expect only 1 reader)
if (i > 0) {
aciomgr_port_checkin(acio_manager_ctx);
return false;
}
icca_node_id[i] = INVALID_NODE_ID;
continue;
}
break;
@ -145,6 +150,11 @@ bool eam_io_init(
}
}
if (icca_node_id[0] == INVALID_NODE_ID) {
log_warning("No ICC readers detected");
return false;
}
aciomgr_port_checkin(acio_manager_ctx);
return true;
}
@ -173,13 +183,24 @@ uint8_t eam_io_get_sensor_state(uint8_t unit_no)
{
uint8_t sensors = 0;
if ((eam_io_icca_state[unit_no].sensor_state &
AC_IO_ICCA_SENSOR_MASK_BACK_ON) > 0) {
sensors |= (1 << EAM_IO_SENSOR_BACK);
}
if ((eam_io_icca_state[unit_no].sensor_state &
AC_IO_ICCA_SENSOR_MASK_FRONT_ON) > 0) {
sensors |= (1 << EAM_IO_SENSOR_FRONT);
if (icca_is_slotted[unit_no]) {
if ((eam_io_icca_state[unit_no].sensor_state &
AC_IO_ICCA_SENSOR_MASK_BACK_ON) > 0) {
sensors |= (1 << EAM_IO_SENSOR_BACK);
}
if ((eam_io_icca_state[unit_no].sensor_state &
AC_IO_ICCA_SENSOR_MASK_FRONT_ON) > 0) {
sensors |= (1 << EAM_IO_SENSOR_FRONT);
}
} else {
// wavepass readers always report (EAM_IO_SENSOR_BACK + EAM_IO_SENSOR_FRONT) + type
// but because we can't report status_code back directly
// and libacio actually just ignores the sensor_state other then the type
// we just return this state like we're a slotted reader so the emulation takes card of it
if (eam_io_icca_state[unit_no].status_code == AC_IO_ICCA_STATUS_GOT_UID) {
sensors |= (1 << EAM_IO_SENSOR_BACK);
sensors |= (1 << EAM_IO_SENSOR_FRONT);
}
}
return sensors;
@ -202,26 +223,31 @@ bool eam_io_card_slot_cmd(uint8_t unit_no, uint8_t cmd)
return true;
}
// ignore these for wavepass
if (!icca_is_slotted[unit_no]) {
return true;
}
struct aciodrv_device_ctx *device = aciomgr_port_checkout(acio_manager_ctx);
bool response = false;
switch (cmd) {
case EAM_IO_CARD_SLOT_CMD_CLOSE:
response = aciodrv_icca_set_state(
device, unit_no, AC_IO_ICCA_SLOT_STATE_CLOSE, NULL);
device, icca_node_id[unit_no], AC_IO_ICCA_SLOT_STATE_CLOSE, NULL);
case EAM_IO_CARD_SLOT_CMD_OPEN:
response = aciodrv_icca_set_state(
device, unit_no, AC_IO_ICCA_SLOT_STATE_OPEN, NULL);
device, icca_node_id[unit_no], AC_IO_ICCA_SLOT_STATE_OPEN, NULL);
case EAM_IO_CARD_SLOT_CMD_EJECT:
response = aciodrv_icca_set_state(
device, unit_no, AC_IO_ICCA_SLOT_STATE_EJECT, NULL);
device, icca_node_id[unit_no], AC_IO_ICCA_SLOT_STATE_EJECT, NULL);
case EAM_IO_CARD_SLOT_CMD_READ:
response = aciodrv_icca_read_card(device, unit_no, NULL) &&
response = aciodrv_icca_read_card(device, icca_node_id[unit_no], NULL) &&
aciodrv_icca_get_state(
device, unit_no, &eam_io_icca_state[unit_no]);
device, icca_node_id[unit_no], &eam_io_icca_state[unit_no]);
default:
break;
@ -240,10 +266,29 @@ bool eam_io_poll(uint8_t unit_no)
bool response = aciodrv_icca_get_state(
aciomgr_port_checkout(acio_manager_ctx),
unit_no,
icca_node_id[unit_no],
&eam_io_icca_state[unit_no]);
aciomgr_port_checkin(acio_manager_ctx);
if (response && !icca_is_slotted[unit_no]) {
// we handle wavepass a bit differently to handle polling felica
if (eam_io_icca_state[unit_no].status_code != AC_IO_ICCA_STATUS_BUSY_NEW) {
++icca_poll_counter[unit_no];
}
// we must manually call this every few polls to actually update the felica state
// we don't do it every poll, since card polling isn't that time sensitive of an operation
// libacio does it every 5ish polls after the last AC_IO_ICCA_STATUS_BUSY_NEW message
if (icca_poll_counter[unit_no] >= 5) {
response = aciodrv_icca_poll_felica(
aciomgr_port_checkout(acio_manager_ctx),
icca_node_id[unit_no]);
aciomgr_port_checkin(acio_manager_ctx);
icca_poll_counter[unit_no] = 0;
}
}
return response;
}