diff --git a/src/main/acio/icca.h b/src/main/acio/icca.h index 3586911..4bd0aff 100644 --- a/src/main/acio/icca.h +++ b/src/main/acio/icca.h @@ -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), diff --git a/src/main/aciodrv/device.c b/src/main/aciodrv/device.c index 650131b..d66790f 100644 --- a/src/main/aciodrv/device.c +++ b/src/main/aciodrv/device.c @@ -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++; diff --git a/src/main/aciodrv/device.h b/src/main/aciodrv/device.h index ba48b26..52f5f41 100644 --- a/src/main/aciodrv/device.h +++ b/src/main/aciodrv/device.h @@ -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 diff --git a/src/main/aciodrv/icca.c b/src/main/aciodrv/icca.c index 859a049..098af18 100644 --- a/src/main/aciodrv/icca.c +++ b/src/main/aciodrv/icca.c @@ -3,6 +3,7 @@ #include #include "aciodrv/device.h" +#include "aciodrv/icca.h" #include "util/log.h" @@ -134,4 +135,53 @@ bool aciodrv_icca_read_card( } return true; -} \ No newline at end of file +} + +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; +} diff --git a/src/main/aciodrv/icca.h b/src/main/aciodrv/icca.h index a7cd595..720babf 100644 --- a/src/main/aciodrv/icca.h +++ b/src/main/aciodrv/icca.h @@ -71,4 +71,41 @@ bool aciodrv_icca_read_card( uint8_t node_id, struct ac_io_icca_state *state); -#endif \ No newline at end of file +/** + * 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 diff --git a/src/main/acioemu/icca.c b/src/main/acioemu/icca.c index caafa4f..d058238 100644 --- a/src/main/acioemu/icca.c +++ b/src/main/acioemu/icca.c @@ -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); diff --git a/src/main/aciotest/icca.c b/src/main/aciotest/icca.c index 41fbbbf..a4ccd90 100644 --- a/src/main/aciotest/icca.c +++ b/src/main/aciotest/icca.c @@ -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; } } diff --git a/src/main/eamio-icca/eamio-icca.c b/src/main/eamio-icca/eamio-icca.c index 1cdf9a6..ea59147 100644 --- a/src/main/eamio-icca/eamio-icca.c +++ b/src/main/eamio-icca/eamio-icca.c @@ -9,6 +9,7 @@ #include #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; }