524 lines
15 KiB
C
524 lines
15 KiB
C
#define LOG_MODULE "acioemu-icca"
|
|
|
|
#include "acioemu/icca.h"
|
|
|
|
#include <windows.h> /* for _BitScanForward */
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "acio/icca.h"
|
|
|
|
#include "acioemu/emu.h"
|
|
#include "acioemu/icca.h"
|
|
|
|
#include "bemanitools/eamio.h"
|
|
|
|
#include "util/crc.h"
|
|
#include "util/time.h"
|
|
|
|
enum ac_io_icca_subcmd {
|
|
AC_IO_ICCA_SUBCMD_CARD_SLOT_CLOSE = 0x00,
|
|
AC_IO_ICCA_SUBCMD_CARD_SLOT_OPEN = 0x11,
|
|
AC_IO_ICCA_SUBCMD_CARD_SLOT_EJECT = 0x12,
|
|
};
|
|
|
|
enum ac_io_icca_flag {
|
|
AC_IO_ICCA_FLAG_FRONT_SENSOR = 0x10,
|
|
AC_IO_ICCA_FLAG_REAR_SENSOR = 0x20,
|
|
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);
|
|
|
|
static void ac_io_emu_icca_send_state(
|
|
struct ac_io_emu_icca *icca,
|
|
const struct ac_io_message *req,
|
|
uint64_t delay_us,
|
|
bool encrypted);
|
|
|
|
static void ac_io_emu_icca_send_empty(
|
|
struct ac_io_emu_icca *icca, const struct ac_io_message *req);
|
|
|
|
static void ac_io_emu_icca_send_status(
|
|
struct ac_io_emu_icca *icca,
|
|
const struct ac_io_message *req,
|
|
uint8_t status);
|
|
|
|
static void ac_io_emu_icca_cipher(
|
|
struct ac_io_emu_icca *icca, uint8_t *data, size_t length);
|
|
|
|
static void ac_io_emu_icca_cipher_set_key(
|
|
struct ac_io_emu_icca *icca, const struct ac_io_message *req);
|
|
|
|
void ac_io_emu_icca_init(
|
|
struct ac_io_emu_icca *icca, struct ac_io_emu *emu, uint8_t unit_no)
|
|
{
|
|
memset(icca, 0, sizeof(*icca));
|
|
icca->emu = emu;
|
|
icca->unit_no = unit_no;
|
|
// queue must be started
|
|
icca->fault = true;
|
|
|
|
// default to 1.6.0
|
|
icca->version = v160;
|
|
}
|
|
|
|
void ac_io_emu_icca_set_version(
|
|
struct ac_io_emu_icca *icca, enum ac_io_emu_icca_version version)
|
|
{
|
|
icca->version = version;
|
|
}
|
|
|
|
void ac_io_emu_icca_dispatch_request(
|
|
struct ac_io_emu_icca *icca, const struct ac_io_message *req)
|
|
{
|
|
uint16_t cmd_code;
|
|
uint64_t delay_us;
|
|
|
|
cmd_code = ac_io_u16(req->cmd.code);
|
|
|
|
switch (cmd_code) {
|
|
case AC_IO_CMD_GET_VERSION:
|
|
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
|
|
ac_io_emu_icca_cmd_send_version(icca, req);
|
|
|
|
break;
|
|
|
|
case AC_IO_CMD_START_UP:
|
|
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
|
|
icca->detected_new_reader = false;
|
|
ac_io_emu_icca_send_status(icca, req, 0x00);
|
|
|
|
break;
|
|
|
|
case AC_IO_CMD_CLEAR:
|
|
log_misc("AC_IO_ICCA_CMD_CLEAR(%d)", req->addr);
|
|
ac_io_emu_icca_send_status(icca, req, 0x00);
|
|
|
|
break;
|
|
|
|
case AC_IO_CMD_KEEPALIVE:
|
|
ac_io_emu_icca_send_empty(icca, req);
|
|
|
|
break;
|
|
|
|
case AC_IO_ICCA_CMD_QUEUE_LOOP_START:
|
|
log_misc("AC_IO_CMD_QUEUE_LOOP_START(%d)", req->addr);
|
|
// queue started, reset error state
|
|
icca->fault = false;
|
|
ac_io_emu_icca_send_status(icca, req, 0x00);
|
|
icca->polling_started = true;
|
|
|
|
break;
|
|
|
|
case AC_IO_CMD_UNKN_00FF:
|
|
log_misc("AC_IO_CMD_UNKN_00FF(%d)", req->addr);
|
|
ac_io_emu_icca_send_status(icca, req, 0x00);
|
|
|
|
break;
|
|
|
|
case AC_IO_ICCA_CMD_UNKN_0120:
|
|
log_misc("AC_IO_ICCA_CMD_UNKN_0120(%d)", req->addr);
|
|
ac_io_emu_icca_send_status(icca, req, 0x00);
|
|
|
|
break;
|
|
|
|
case AC_IO_ICCA_CMD_BEGIN_KEYPAD:
|
|
log_misc("AC_IO_ICCA_CMD_BEGIN_KEYPAD(%d)", req->addr);
|
|
ac_io_emu_icca_send_status(icca, req, 0x00);
|
|
icca->keypad_started = true;
|
|
|
|
break;
|
|
|
|
case AC_IO_ICCA_CMD_ENGAGE:
|
|
ac_io_emu_icca_send_state(icca, req, 0, false);
|
|
|
|
break;
|
|
|
|
case AC_IO_ICCA_CMD_SET_SLOT_STATE: {
|
|
struct ac_io_icca_misc *misc =
|
|
(struct ac_io_icca_misc *) &req->cmd.raw;
|
|
uint8_t cmd;
|
|
|
|
switch (misc->subcmd) {
|
|
case AC_IO_ICCA_SUBCMD_CARD_SLOT_CLOSE:
|
|
cmd = EAM_IO_CARD_SLOT_CMD_CLOSE;
|
|
break;
|
|
|
|
case AC_IO_ICCA_SUBCMD_CARD_SLOT_OPEN:
|
|
cmd = EAM_IO_CARD_SLOT_CMD_OPEN;
|
|
break;
|
|
|
|
case AC_IO_ICCA_SUBCMD_CARD_SLOT_EJECT:
|
|
cmd = EAM_IO_CARD_SLOT_CMD_EJECT;
|
|
icca->engaged = false;
|
|
break;
|
|
|
|
case 3:
|
|
cmd = EAM_IO_CARD_SLOT_CMD_READ;
|
|
break;
|
|
|
|
default:
|
|
cmd = 0xFF;
|
|
log_warning(
|
|
"Unhandled slot command %X, node %d",
|
|
misc->subcmd,
|
|
icca->unit_no);
|
|
break;
|
|
}
|
|
|
|
if (cmd != 0xFF) {
|
|
if (!eam_io_card_slot_cmd(icca->unit_no, cmd)) {
|
|
log_warning(
|
|
"Eamio failed to handle slot cmd %d for node %d",
|
|
cmd,
|
|
icca->unit_no);
|
|
}
|
|
}
|
|
|
|
/* response with current slot state */
|
|
ac_io_emu_icca_send_status(icca, req, misc->subcmd);
|
|
|
|
break;
|
|
}
|
|
|
|
case AC_IO_ICCA_CMD_POLL_ENCRYPTED:
|
|
case AC_IO_ICCA_CMD_POLL:
|
|
delay_us = time_get_elapsed_us(
|
|
time_get_counter() - icca->time_counter_last_poll);
|
|
|
|
/* emulating delay implemented by hardware. do not delay messages
|
|
* that exceed a certain threshold. */
|
|
if (delay_us > 16000) {
|
|
delay_us = 0;
|
|
}
|
|
|
|
icca->time_counter_last_poll = time_get_counter();
|
|
bool encrypted = cmd_code == AC_IO_ICCA_CMD_POLL_ENCRYPTED;
|
|
ac_io_emu_icca_send_state(icca, req, delay_us, encrypted);
|
|
|
|
break;
|
|
|
|
case AC_IO_ICCA_CMD_POLL_FELICA:
|
|
icca->detected_new_reader = true;
|
|
|
|
if (icca->version == v170) {
|
|
ac_io_emu_icca_send_empty(icca, req);
|
|
} else {
|
|
ac_io_emu_icca_send_status(icca, req, 0x01);
|
|
}
|
|
|
|
break;
|
|
|
|
case AC_IO_ICCA_CMD_KEY_EXCHANGE:
|
|
icca->detected_new_reader = true;
|
|
log_misc("AC_IO_ICCA_CMD_KEY_EXCHANGE(%d)", req->addr);
|
|
ac_io_emu_icca_cipher_set_key(icca, req);
|
|
|
|
break;
|
|
|
|
default:
|
|
log_warning(
|
|
"Unknown ACIO message %04x on ICCA node, addr=%d",
|
|
cmd_code,
|
|
req->addr);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ac_io_emu_icca_cmd_send_version(
|
|
struct ac_io_emu_icca *icca, const struct ac_io_message *req)
|
|
{
|
|
struct ac_io_message resp;
|
|
|
|
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
|
resp.cmd.code = req->cmd.code;
|
|
resp.cmd.seq_no = req->cmd.seq_no;
|
|
resp.cmd.nbytes = sizeof(resp.cmd.version);
|
|
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_ICCA);
|
|
|
|
resp.cmd.version.flag = 0x00;
|
|
resp.cmd.version.major = 0x01;
|
|
|
|
if (icca->version == v150) {
|
|
log_warning(
|
|
"ICCA v1.5.0 emulation requested, please remove this log once implemented");
|
|
resp.cmd.version.minor = 0x05;
|
|
} else if (icca->version == v160) {
|
|
resp.cmd.version.minor = 0x06;
|
|
} else if (icca->version == v170) {
|
|
resp.cmd.version.minor = 0x07;
|
|
} else {
|
|
// probably log invalid version here
|
|
log_warning(
|
|
"Unknown ICCA version: %d emulation requested",
|
|
icca->version);
|
|
}
|
|
|
|
resp.cmd.version.revision = 0x00;
|
|
|
|
memcpy(
|
|
resp.cmd.version.product_code,
|
|
"ICCA",
|
|
sizeof(resp.cmd.version.product_code));
|
|
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
|
|
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
|
|
|
|
ac_io_emu_response_push(icca->emu, &resp, 0);
|
|
}
|
|
|
|
static void ac_io_emu_icca_send_empty(
|
|
struct ac_io_emu_icca *icca, const struct ac_io_message *req)
|
|
{
|
|
struct ac_io_message resp;
|
|
|
|
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
|
resp.cmd.code = req->cmd.code;
|
|
resp.cmd.seq_no = req->cmd.seq_no;
|
|
resp.cmd.nbytes = 0;
|
|
|
|
ac_io_emu_response_push(icca->emu, &resp, 0);
|
|
}
|
|
|
|
static void ac_io_emu_icca_send_status(
|
|
struct ac_io_emu_icca *icca,
|
|
const struct ac_io_message *req,
|
|
uint8_t status)
|
|
{
|
|
struct ac_io_message resp;
|
|
|
|
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
|
resp.cmd.code = req->cmd.code;
|
|
resp.cmd.seq_no = req->cmd.seq_no;
|
|
resp.cmd.nbytes = sizeof(resp.cmd.status);
|
|
resp.cmd.status = status;
|
|
|
|
ac_io_emu_response_push(icca->emu, &resp, 0);
|
|
}
|
|
|
|
static void ac_io_emu_icca_send_state(
|
|
struct ac_io_emu_icca *icca,
|
|
const struct ac_io_message *req,
|
|
uint64_t delay_us,
|
|
bool encrypted)
|
|
{
|
|
struct ac_io_message resp;
|
|
struct ac_io_icca_state *body;
|
|
unsigned long bit;
|
|
uint8_t event;
|
|
uint16_t keypad;
|
|
uint16_t keypad_rise;
|
|
uint8_t sensor_state;
|
|
bool card_full_insert;
|
|
|
|
if (!eam_io_poll(icca->unit_no)) {
|
|
log_warning("Polling eamio failed");
|
|
}
|
|
|
|
memset(&resp, 0, sizeof(resp));
|
|
|
|
keypad = eam_io_get_keypad_state(icca->unit_no);
|
|
sensor_state = eam_io_get_sensor_state(icca->unit_no);
|
|
|
|
keypad_rise = keypad & (icca->last_keypad ^ keypad);
|
|
card_full_insert = sensor_state & (1 << EAM_IO_SENSOR_FRONT) &&
|
|
sensor_state & (1 << EAM_IO_SENSOR_BACK);
|
|
|
|
if (sensor_state != icca->last_sensor) {
|
|
if (card_full_insert) {
|
|
if (!eam_io_card_slot_cmd(
|
|
icca->unit_no, EAM_IO_CARD_SLOT_CMD_READ)) {
|
|
log_warning(
|
|
"EAM_IO_CARD_SLOT_CMD_READ to unit %d failed",
|
|
icca->unit_no);
|
|
}
|
|
|
|
icca->card_result =
|
|
eam_io_read_card(icca->unit_no, icca->uid, sizeof(icca->uid));
|
|
|
|
// fault if sensor says to read but we got no card
|
|
icca->fault = (icca->card_result == EAM_IO_CARD_NONE);
|
|
} else {
|
|
icca->fault = false;
|
|
}
|
|
}
|
|
|
|
icca->last_sensor = sensor_state;
|
|
icca->last_keypad = keypad;
|
|
|
|
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
|
resp.cmd.code = req->cmd.code;
|
|
resp.cmd.seq_no = req->cmd.seq_no;
|
|
resp.cmd.nbytes = sizeof(struct ac_io_icca_state);
|
|
|
|
body = (struct ac_io_icca_state *) &resp.cmd.raw;
|
|
|
|
if (icca->fault) {
|
|
body->status_code = AC_IO_ICCA_STATUS_FAULT;
|
|
} else if (card_full_insert) {
|
|
body->status_code = AC_IO_ICCA_STATUS_GOT_UID;
|
|
} else {
|
|
if (icca->detected_new_reader && icca->version == v170) {
|
|
// else we get a power failure?
|
|
body->status_code = 0;
|
|
} else if (icca->detected_new_reader) {
|
|
body->status_code = AC_IO_ICCA_STATUS_IDLE_NEW;
|
|
} else {
|
|
body->status_code = AC_IO_ICCA_STATUS_IDLE;
|
|
}
|
|
}
|
|
|
|
body->sensor_state = 0;
|
|
|
|
if (sensor_state & (1 << EAM_IO_SENSOR_FRONT)) {
|
|
body->sensor_state |= AC_IO_ICCA_FLAG_FRONT_SENSOR;
|
|
}
|
|
|
|
if (sensor_state & (1 << EAM_IO_SENSOR_BACK)) {
|
|
body->sensor_state |= AC_IO_ICCA_FLAG_REAR_SENSOR;
|
|
}
|
|
|
|
if (icca->engaged) {
|
|
body->sensor_state |= AC_IO_ICCA_FLAG_SOLENOID;
|
|
}
|
|
|
|
memcpy(body->uid, icca->uid, sizeof(body->uid));
|
|
body->card_type = 0;
|
|
if (body->status_code == AC_IO_ICCA_STATUS_GOT_UID) {
|
|
if (icca->detected_new_reader) {
|
|
// sensor_state actually refers to cardtype for wavepass readers
|
|
// EAM_IO_CARD_ISO15696 = 1 -> 0
|
|
// EAM_IO_CARD_FELICA = 2 -> 1
|
|
body->card_type = icca->card_result - 1;
|
|
body->sensor_state = icca->card_result - 1;
|
|
}
|
|
}
|
|
|
|
if (keypad_rise) {
|
|
if (icca->key_events[0]) {
|
|
event = (icca->key_events[0] + 0x10) & 0xF0;
|
|
} else {
|
|
event = 0x00;
|
|
}
|
|
|
|
_BitScanForward(&bit, keypad_rise);
|
|
event |= 0x80 | bit;
|
|
|
|
icca->key_events[1] = icca->key_events[0];
|
|
icca->key_events[0] = event;
|
|
}
|
|
|
|
// this doesn't seem to be an error code. If this is not set to 0x03
|
|
// on slotted readers (only?), the game throws an unknown status error
|
|
// and on SDVX4 it hangs on the thank you screen
|
|
if (icca->keypad_started || icca->detected_new_reader) {
|
|
body->keypad_started = 0x03;
|
|
} else {
|
|
body->keypad_started = 0x00;
|
|
}
|
|
body->key_events[0] = icca->key_events[0];
|
|
body->key_events[1] = icca->key_events[1];
|
|
body->key_state = ac_io_u16(keypad);
|
|
|
|
// replace status code if polling hasn't started
|
|
// this fixes SDVX IC CARD boot errors
|
|
if (!icca->polling_started) {
|
|
body->status_code = AC_IO_ICCA_STATUS_FAULT;
|
|
}
|
|
|
|
if (encrypted) {
|
|
size_t base_state_sz = sizeof(struct ac_io_icca_state);
|
|
resp.cmd.nbytes = base_state_sz + 2;
|
|
|
|
if (!icca->cipher_started) {
|
|
log_warning("No crypto keys set for unit %d", icca->unit_no);
|
|
} else {
|
|
uint16_t crc = crc16_msb(resp.cmd.raw, base_state_sz, 0);
|
|
|
|
resp.cmd.raw[base_state_sz + 0] = (crc >> 8) & 0xFF;
|
|
resp.cmd.raw[base_state_sz + 1] = crc & 0xFF;
|
|
|
|
ac_io_emu_icca_cipher(icca, resp.cmd.raw, 18);
|
|
}
|
|
}
|
|
|
|
ac_io_emu_response_push(icca->emu, &resp, delay_us);
|
|
}
|
|
|
|
static void ac_io_emu_icca_cipher_shift_keys(struct ac_io_emu_icca *icca)
|
|
{
|
|
uint32_t key4_old = icca->cipher_keys[3];
|
|
uint32_t key_new = (key4_old << 11) ^ key4_old;
|
|
uint32_t key1_old = icca->cipher_keys[0];
|
|
|
|
// shift keys up
|
|
icca->cipher_keys[3] = icca->cipher_keys[2];
|
|
icca->cipher_keys[2] = icca->cipher_keys[1];
|
|
icca->cipher_keys[1] = icca->cipher_keys[0];
|
|
icca->cipher_keys[0] =
|
|
((((key1_old >> 11) ^ key_new) >> 8) ^ key_new ^ key1_old);
|
|
}
|
|
|
|
static void
|
|
ac_io_emu_icca_cipher(struct ac_io_emu_icca *icca, uint8_t *data, size_t length)
|
|
{
|
|
for (size_t i = 0; i < length; i++) {
|
|
uint8_t count4 = i % 4;
|
|
// shift keys every 4 bytes
|
|
if (count4 == 0) {
|
|
ac_io_emu_icca_cipher_shift_keys(icca);
|
|
}
|
|
|
|
// process data
|
|
data[i] =
|
|
(uint8_t)(icca->cipher_keys[0] >> (((3 - count4) << 3)) ^ data[i]);
|
|
}
|
|
}
|
|
|
|
static void ac_io_emu_icca_cipher_set_key(
|
|
struct ac_io_emu_icca *icca, const struct ac_io_message *req)
|
|
{
|
|
struct ac_io_message resp;
|
|
|
|
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
|
resp.cmd.code = req->cmd.code;
|
|
resp.cmd.seq_no = req->cmd.seq_no;
|
|
resp.cmd.nbytes = 4; // 4 bytes, uint32_t
|
|
|
|
uint32_t host_key = 0x00000000;
|
|
host_key |= req->cmd.raw[0] << 24;
|
|
host_key |= req->cmd.raw[1] << 16;
|
|
host_key |= req->cmd.raw[2] << 8;
|
|
host_key |= req->cmd.raw[3];
|
|
|
|
// TODO: should probably RNG this
|
|
uint32_t reader_key = 0x14243444;
|
|
resp.cmd.raw[0] = (reader_key >> 24) & 0xFF;
|
|
resp.cmd.raw[1] = (reader_key >> 16) & 0xFF;
|
|
resp.cmd.raw[2] = (reader_key >> 8) & 0xFF;
|
|
resp.cmd.raw[3] = (reader_key) & 0xFF;
|
|
|
|
// so I looked these constants up, this isn't actually a secure key
|
|
// generator it's actually Marsaglia's "KISS" algorithm with different
|
|
// initial states
|
|
icca->cipher_keys[0] = reader_key ^ 0x5491333;
|
|
icca->cipher_keys[1] = host_key ^ 0x1F123BB5;
|
|
icca->cipher_keys[2] = reader_key ^ 0x159A55E5;
|
|
icca->cipher_keys[3] = host_key ^ 0x75BCD15;
|
|
|
|
log_misc("keys set to: H: %08x R: %08x", host_key, reader_key);
|
|
|
|
ac_io_emu_response_push(icca->emu, &resp, 0);
|
|
icca->cipher_started = true;
|
|
}
|