spicetools/acioemu/icca.cpp

582 lines
16 KiB
C++

#include "icca.h"
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace acioemu;
namespace acioemu {
bool ICCA_DEVICE_HACK = false;
}
ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count) {
// init defaults
this->type_new = false;
this->flip_order = flip_order;
this->node_count = node_count;
this->cards = new uint8_t *[node_count] {};
this->cards_time = new time_t[node_count] {};
this->status = new uint8_t[node_count * 16] {};
this->accept = new bool[node_count] {};
for (int i = 0; i < node_count; i++) {
this->accept[i] = true;
}
this->hold = new bool[node_count] {};
this->keydown = new uint8_t[node_count] {};
this->keypad = new uint16_t[node_count] {};
this->keypad_last = new bool*[node_count] {};
for (int i = 0; i < node_count; i++) {
this->keypad_last[i] = new bool[12] {};
}
this->keypad_capture = new uint8_t[node_count] {};
for (int i = 0; i < node_count; i++) {
this->keypad_capture[i] = 0x08;
}
this->crypt = new std::optional<Crypt>[node_count] {};
this->counter = new uint8_t[node_count] {};
for (int i = 0; i < node_count; i++) {
this->counter[i] = 2;
}
// keypad thread for faster polling
if (keypad_thread) {
this->keypad_thread = new std::thread([this]() {
while (this->cards) {
for (int unit = 0; unit < this->node_count; unit++) {
this->update_keypad(unit, false);
}
Sleep(7);
}
});
}
}
ICCADevice::~ICCADevice() {
// stop thread
delete keypad_thread;
// delete cards in array
for (int i = 0; i < node_count; i++) {
delete cards[i];
}
// delete the rest
delete[] cards;
delete[] cards_time;
delete[] status;
delete[] accept;
delete[] hold;
delete[] keydown;
delete[] keypad;
delete[] keypad_last;
delete[] keypad_capture;
delete[] crypt;
delete[] counter;
}
bool ICCADevice::parse_msg(MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer) {
// get unit
int unit = msg_in->addr - 1;
if (this->flip_order) {
unit = this->node_count - unit - 1;
}
if (unit != 0 && unit != 1) {
log_fatal("icca", "invalid unit: {}", unit);
}
#ifdef ACIOEMU_LOG
log_info("acioemu", "ICCA ADDR: {}, CMD: 0x{:04x}", unit, msg_in->cmd.code);
#endif
// check command
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
if (
avs::game::is_model({"LDJ", "TBS", "UJK"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} else {
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
}
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0130: { // REINITIALIZE
// send status 0
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0131: { // READ CARD UID
// build data array
auto msg = this->create_msg(msg_in, 16);
// update things
update_card(unit);
update_keypad(unit, true);
update_status(unit);
// copy status
memcpy(msg->cmd.raw, &status[unit * 16], 16);
// explicitly set no card since this is just read
msg->cmd.raw[0] = 0x01;
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0135: { // SET ACTION
// check for data
if (msg_in->cmd.data_size >= 2) {
// subcommand
switch (msg_in->cmd.raw[1]) {
case 0x00: // ACCEPT DISABLE
this->accept[unit] = false;
break;
case 0x11: // ACCEPT ENABLE
this->accept[unit] = true;
break;
case 0x12: // EJECT
if (this->cards[unit] != nullptr) {
delete this->cards[unit];
}
this->cards[unit] = nullptr;
this->hold[unit] = false;
default:
break;
}
}
// no break, return status
[[fallthrough]];
}
case 0x0134: { // GET STATUS
// build data array
auto msg = this->create_msg(msg_in, 16);
// update things
update_card(unit);
update_keypad(unit, true);
update_status(unit);
// copy status
memcpy(msg->cmd.raw, &status[unit * 16], 16);
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0160: { // KEY EXCHANGE
// if this cmd is called, the reader type must be new
this->type_new = true;
// build data array
auto msg = this->create_msg(msg_in, 4);
// set key
msg->cmd.raw[0] = 0xBE;
msg->cmd.raw[1] = 0xEF;
msg->cmd.raw[2] = 0xCA;
msg->cmd.raw[3] = 0xFE;
// convert keys
uint32_t game_key =
msg_in->cmd.raw[0] << 24 |
msg_in->cmd.raw[1] << 16 |
msg_in->cmd.raw[2] << 8 |
msg_in->cmd.raw[3];
uint32_t reader_key =
msg->cmd.raw[0] << 24 |
msg->cmd.raw[1] << 16 |
msg->cmd.raw[2] << 8 |
msg->cmd.raw[3];
log_info("icca", "client key: {:08x}", game_key);
log_info("icca", "reader key: {:08x}", reader_key);
this->crypt[unit].emplace();
this->crypt[unit]->set_keys(reader_key, game_key);
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0161: { // READ CARD UID NEW
// if this cmd is called, the reader type must be new
this->type_new = true;
// decide on answer
int answer_type = 0;
//if (avs::game::is_model("LDJ"))
//answer_type = 1;
// SDVX Old cabinet mode
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
answer_type = 1;
if (avs::game::is_model("L44"))
answer_type = 2;
// check answer type
switch (answer_type) {
case 1: {
// send status 1
auto msg = this->create_msg_status(msg_in, 1);
write_msg(msg, response_buffer);
delete msg;
break;
}
case 2: {
// build data array
auto msg = this->create_msg(msg_in, 16);
// update card
update_card(unit);
// check for card
if (this->cards[unit] != nullptr) {
// copy into data buffer
memcpy(msg->cmd.raw, this->cards[unit], 8);
// delete card
delete this->cards[unit];
this->cards[unit] = nullptr;
this->hold[unit] = false;
}
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
default: {
// send response with no data
auto msg = this->create_msg(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
}
break;
}
case 0x0164: { // GET STATUS ENC
// build data array
auto msg = this->create_msg(msg_in, 18);
// update things
update_card(unit);
update_keypad(unit, true);
update_status(unit);
// copy status
memcpy(msg->cmd.raw, &status[unit * 16], 16);
if (this->crypt[unit].has_value()) {
auto &crypt = this->crypt[unit];
uint16_t crc = crypt->crc(msg->cmd.raw, 16);
msg->cmd.raw[16] = (uint8_t) (crc >> 8);
msg->cmd.raw[17] = (uint8_t) crc;
crypt->crypt(msg->cmd.raw, 18);
} else {
log_warning("icca", "'GET STATUS ENC' message received with no crypt keys initialized");
}
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x013A: { // POWER CONTROL (tentative name, used in 1.7 firmware)
// TODO(felix): isolate this logic to LDJ and/or firmware 1.7 emulation
if (this->counter[unit] > 0) {
this->counter[unit]--;
}
//log_info("icca", "counter[{}] = {}", unit, this->counter[unit]);
auto msg = this->create_msg_status(msg_in, this->counter[unit]);
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_STARTUP:
case ACIO_CMD_CLEAR:
case 0x30: // GetBoardProductNumber
case 0x31: // GetMicomInfo
case 0x0116: // ???
case 0x0120: // ???
case 0xFF: // BROADCAST
{
// send status 0
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
return false;
}
// mark as handled
return true;
}
void ICCADevice::update_card(int unit) {
// wavepass timeout after 10s
if (this->cards[unit] != nullptr) {
time_t t_now;
time(&t_now);
if (difftime(t_now, this->cards_time[unit]) >= 10.f) {
if (this->cards[unit] != nullptr) {
delete this->cards[unit];
}
this->cards[unit] = nullptr;
this->hold[unit] = false;
}
}
bool kb_insert_press = false;
// eamio keypress
kb_insert_press |= eamuse_get_keypad_state((size_t) unit) & (1 << EAM_IO_INSERT);
// check for card
if (this->cards[unit] == nullptr && (eamuse_card_insert_consume(this->node_count, unit) || kb_insert_press)) {
auto card = new uint8_t[8];
if (!eamuse_get_card(this->node_count, unit, card)) {
// invalid card found
delete[] card;
} else {
this->cards[unit] = card;
time(&this->cards_time[unit]);
}
}
}
static int KEYPAD_EAMUSE_MAPPING[] = {
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
// map for KEYPAD_KEY_CODES:
// 7 8 9 | 800 8000 8
// 4 5 6 | 400 4000 4
// 1 2 3 | 200 2000 2
// 0 00 . | 100 1000 1
static int KEYPAD_KEY_CODES[]{
0x100, // 0
0x200, // 1
0x2000, // 2
2, // 3
0x400, // 4
0x4000, // 5
4, // 6
0x800, // 7
0x8000, // 8
8, // 9
1, // .
0x1000 // 00
};
// map for KEYPAD_KEY_CODES_ALT:
// 7 8 9 | 8 80 800
// 4 5 6 | 4 40 400
// 1 2 3 | 2 20 200
// 0 00 . | 1 10 100
//
// note that the only game that needs this (SDVX VM) does not accept decimal,
// so that key is untested
static int KEYPAD_KEY_CODES_ALT[]{
1, // 0
2, // 1
0x20, // 2
0x200, // 3
4, // 4
0x40, // 5
0x400, // 6
8, // 7
0x80, // 8
0x800, // 9
0x100, // .
0x10 // 00
};
static uint8_t KEYPAD_KEY_CODE_NUMS[]{
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
void ICCADevice::update_keypad(int unit, bool update_edge) {
// lock keypad so threads can't interfere
std::lock_guard<std::mutex> lock(this->keypad_mutex);
// reset unit
this->keypad[unit] = 0;
// get eamu key states
uint16_t eamu_state = eamuse_get_keypad_state((size_t) unit);
// iterate keypad
bool edge = false;
for (int n = 0; n < 12; n++) {
int i = n;
// check if pressed
if (eamu_state & (1 << KEYPAD_EAMUSE_MAPPING[i])) {
if (ICCA_DEVICE_HACK) {
this->keypad[unit] |= KEYPAD_KEY_CODES_ALT[i];
} else {
this->keypad[unit] |= KEYPAD_KEY_CODES[i];
}
if (!this->keypad_last[unit][i] && update_edge) {
this->keydown[unit] = (this->keypad_capture[unit] << 4) | KEYPAD_KEY_CODE_NUMS[n];
this->keypad_last[unit][i] = true;
edge = true;
}
} else {
this->keypad_last[unit][i] = false;
}
}
// update keypad capture
if (update_edge && edge) {
this->keypad_capture[unit]++;
this->keypad_capture[unit] |= 0x08;
} else {
this->keydown[unit] = 0;
}
}
void ICCADevice::update_status(int unit) {
// get buffer
uint8_t *buffer = &this->status[unit * 16];
// clear buffer
memset(buffer, 0x00, 16);
// check for card
bool card = false;
if (this->cards[unit] != nullptr) {
// copy card into buffer
memcpy(buffer + 2, this->cards[unit], 8);
card = true;
}
// check for reader type
if (this->type_new) {
// check for card
if (card) {
// set status to card present
buffer[0] = 0x02;
/*
* set card type
* 0x00 - ISO15696
* 0x01 - FELICA
*/
bool felica = buffer[2] != 0xE0 && buffer[3] != 0x04;
buffer[1] = felica ? 0x01 : 0x00;
buffer[10] = felica ? 0x01 : 0x00;
} else if (
avs::game::is_model({"LDJ", "TBS"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
// set status to 0 otherwise reader power on fails
buffer[0] = 0x00;
} else {
// set status to no card present (1 or 4)
buffer[0] = 0x04;
}
} else { // old reader
// check for card
if (card && accept[unit]) {
this->hold[unit] = true;
}
// check for hold
if (this->hold[unit]) {
// set status to card present
buffer[0] = 0x02;
/*
* sensors
* 0x10 - OLD READER FRONT
* 0x20 - OLD READER BACK
*/
// activate both sensors
buffer[1] = 0x30;
} else {
// card present but reader isn't accepting it
if (card) {
// set card present
buffer[0] = 0x02;
// set front sensor
buffer[1] = 0x10;
} else {
// no card present
buffer[0] = 0x01;
}
}
// card type not present for old reader
buffer[10] = 0x00;
}
// other flags
buffer[11] = 0x03;
buffer[12] = keydown[unit];
buffer[13] = 0x00;
buffer[14] = (uint8_t) (keypad[unit] >> 8);
buffer[15] = (uint8_t) (keypad[unit] & 0xFF);
}