mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 14:50:41 -07:00
71ba9b6b47
## Link to GitHub Issue or related Pull Request, if one exists Fixes #681 ## Description of change `Sleep` and `sleep_for()` can be very inaccurate and varies depending on what the OS gives us... ### `timeBeginPeriod(1)` On boot, we are now calling `timeBeginPeriod(1)`, which affects the whole process but makes `Sleep` more accurate. There is some risk here if any game was relying on doing things like `Sleep(1)` and expecting it to run for 15.6ms. Most games already call `timeBeginPeriod(1)` in the game engine, though not the whole time, so I'm hoping that this is not too impactful. ### Opt out of Win11 power throttling Ensure that timer resolution change above is respected even when the window is occluded / minimized by opting out of throttling via `PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION`. ### Use Win10 high resolution timer instead of Sleep On Win10 1803 and above, there is a new OS-level API for high resolution timers; if this is available, use it (`CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`). Worth noting that WINE doesn't support this currently. If not, fall back to `Sleep`, which is significantly better than `sleep_for()` in my experiments. Callers of Sleep / sleep_for were replaced with this new timer. Most of them anyway; calls to Sleep() with more than 100ms+ was left alone. ### Add an option as a chicken bit To opt out I'm adding a new option called `Use Legacy Timers` which will revert to behavior before this PR. The code paths that switched from `sleep_for` to `Sleep` will remain in place though, not affected by the option. ## Expected changes In some I/O emulation modules, poll threads may run more frequently, resulting in lower latency. It also means that spice overall may use more CPU resources and power. If you don't like this, you can always enable the option to opt out; e.g., if you're on old arcade cab PC. ## Testing DDR p4io - ok drs touch hook - ok IIDX camera hook - ok CCJ trackball - ok
581 lines
16 KiB
C++
581 lines
16 KiB
C++
#include "icca.h"
|
|
|
|
#include "acio/icca/icca.h"
|
|
#include "avs/game.h"
|
|
#include "games/sdvx/sdvx.h"
|
|
#include "misc/eamuse.h"
|
|
#include "util/logging.h"
|
|
#include "util/precise_timer.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]() {
|
|
timeutils::PreciseSleepTimer timer;
|
|
while (this->cards) {
|
|
for (int unit = 0; unit < this->node_count; unit++) {
|
|
this->update_keypad(unit, false);
|
|
}
|
|
timer.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", "XIF"}) ||
|
|
games::sdvx::is_valkyrie_model()) {
|
|
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
|
|
} else if (avs::game::is_model("VFG")) {
|
|
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
|
|
} 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 0x3A: // ???
|
|
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 |= static_cast<bool>(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", "XIF"}) || games::sdvx::is_valkyrie_model()) {
|
|
// 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);
|
|
}
|