582 lines
16 KiB
C++
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);
|
||
|
}
|