Files
djhackersdev_bemanitools/src/main/acioemu/icca.c
T

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;
}