spicetools/acio/icca/icca.cpp

473 lines
12 KiB
C++
Raw Permalink Normal View History

2024-08-28 15:10:34 +00:00
#include <cmath>
#include "icca.h"
#include "avs/game.h"
#include "misc/eamuse.h"
#include "util/time.h"
// settings
namespace acio {
bool ICCA_FLIP_ROWS = false;
bool ICCA_COMPAT_ACTIVE = false;
}
/*
* Helpers
*/
struct ICCA_STATUS {
uint8_t status_code;
uint8_t solenoid;
uint8_t front_sensor;
uint8_t rear_sensor;
uint8_t uid[8];
int32_t error;
uint32_t key_edge;
uint32_t key_level;
};
struct ICCA_STATUS_LA9 {
uint8_t status_code;
uint8_t card_in;
uint8_t uid[8];
uint8_t error;
uint8_t uid2[8];
};
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
enum ICCA_WORKFLOW {
STEP,
SLEEP,
START,
INIT,
READY,
GET_USERID,
ACTIVE,
EJECT,
EJECT_CHECK,
END,
CLOSE_EJECT,
CLOSE_E_CHK,
CLOSE_END,
ERR_GETUID = -2
};
struct ICCA_UNIT {
struct ICCA_STATUS status {};
enum ICCA_WORKFLOW state = STEP;
bool card_cmd_pressed = false;
bool card_in = false;
double card_in_time = 0.0;
char key_serial = 0;
bool uid_skip = false;
bool initialized = false;
};
static ICCA_UNIT ICCA_UNITS[2] {};
static bool IS_LAST_CARD_FELICA = false;
static bool STATUS_BUFFER_FREEZE = false;
static double CARD_TIMEOUT = 2.0;
static inline int icca_get_active_count() {
int active_count = 0;
for (auto unit : ICCA_UNITS) {
active_count += unit.initialized;
}
return active_count;
}
static inline int icca_get_unit_id(int unit_id) {
if (icca_get_active_count() < 2)
return 1;
else {
if (unit_id > 1) {
return 1;
} else {
return 0;
}
}
}
static inline void update_card(int unit_id) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return;
}
// eamio keypress
int index = unit_id > 0 && icca_get_active_count() > 1 ? 1 : 0;
bool kb_insert_press = (eamuse_get_keypad_state(index) & (1 << EAM_IO_INSERT)) > 0;
// get unit
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
// check for card insert
static bool kb_insert_press_old[2] = {false, false};
if (eamuse_card_insert_consume(icca_get_active_count(), unit_id) ||
(kb_insert_press && !kb_insert_press_old[unit_id])) {
if (!unit->card_cmd_pressed) {
unit->card_cmd_pressed = true;
if (unit->state == GET_USERID || unit->state == CLOSE_EJECT || unit->state == STEP) {
if (unit->uid_skip || eamuse_get_card(icca_get_active_count(), unit_id, unit->status.uid)) {
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->status.error = 0;
} else {
unit->state = ERR_GETUID;
memset(unit->status.uid, 0, 8);
}
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
} else if (unit->state == EJECT_CHECK) {
unit->state = SLEEP;
unit->card_in = false;
}
} else {
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->status.error = 0;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
}
} else {
unit->card_cmd_pressed = false;
unit->state = CLOSE_EJECT;
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
unit->card_in = false;
}
}
// save state
kb_insert_press_old[unit_id] = kb_insert_press;
}
static bool KEYPAD_LAST[2][12];
static uint32_t KEYPAD_EAMUSE_MAPPING[] = {
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
static uint32_t KEYPAD_KEY_CODES[] = {
0x100,
0x200,
0x2000,
2,
0x400,
0x4000,
4,
0x800,
0x8000,
8,
1,
0x1000
};
static uint32_t KEYPAD_KEY_CODE_NUMS[] = {
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
static inline void keypad_update(int unit_id) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return;
}
// reset unit
struct ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->status.key_level = 0;
unit->status.error = 0;
// get eamu state
int index = unit_id > 0 && icca_get_active_count() > 1 ? 1 : 0;
uint16_t eamu_state = eamuse_get_keypad_state(index);
// iterate keypad
for (int n = 0; n < 12; n++) {
int i = n;
// flip 123 with 789
if (acio::ICCA_FLIP_ROWS) {
if (!n)
i = 11;
else if (n < 4)
i = n + 6;
else if (n > 6 && n < 10)
i = n - 6;
else if (n == 11)
i = 0;
}
// check if pressed
if ((eamu_state & (1 << KEYPAD_EAMUSE_MAPPING[i])))
{
unit->status.key_level |= KEYPAD_KEY_CODES[i];
if (!KEYPAD_LAST[unit_id][i]) {
unit->status.key_edge = KEYPAD_KEY_CODES[n] << 16;
unit->status.key_edge |= 0x80 | (unit->key_serial << 4) | KEYPAD_KEY_CODE_NUMS[n];
unit->key_serial += 1;
unit->key_serial &= 0x07;
}
KEYPAD_LAST[unit_id][i] = true;
} else {
unit->status.key_edge &= ~(KEYPAD_KEY_CODES[n] << 16);
KEYPAD_LAST[unit_id][i] = false;
}
}
}
/*
* Implementations
*/
static bool __cdecl ac_io_icca_cardunit_init(int unit_id) {
unit_id = icca_get_unit_id(unit_id);
// dirty workaround code
if (icca_get_active_count() < 1)
ICCA_UNITS[unit_id].initialized = true;
else {
ICCA_UNITS[0].initialized = true;
ICCA_UNITS[1].initialized = true;
}
// initial poll
eamuse_get_keypad_state(unit_id);
// return success
return true;
}
static char __cdecl ac_io_icca_cardunit_init_isfinished(int unit_id, DWORD *status) {
*status = READY;
return 1;
}
static char __cdecl ac_io_icca_crypt_init(int unit_id) {
return 1;
}
static char __cdecl ac_io_icca_device_control_iccard_power_supply_off(int unit_id) {
return 1;
}
static char __cdecl ac_io_icca_device_control_iccard_power_supply_on(int unit_id) {
return 1;
}
static bool __cdecl ac_io_icca_device_control_isfinished(int unit_id, DWORD *a2) {
if (a2 && avs::game::is_model("KFC")) {
*a2 = 6;
}
return true;
}
static bool __cdecl ac_io_icca_get_keep_alive_error(int unit_id, DWORD *a2) {
*a2 = 0;
return false;
}
static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
// Metal Gear Arcade and Charge Machine had the args swapped so we need to check for valid pointers!
if (reinterpret_cast<uintptr_t>(a2) > 2) {
std::swap(a1, a2);
// honestly this could be used to detect if compat mode should be active
// but we are too lazy to check if all games still work with this change
//acio::ICCA_COMPAT_ACTIVE = true;
}
// and best just leave this casting mess alone unless something is wrong with it.
// long long is required because casting to int loses precision on 64-bit
void *status = a1;
int unit_id = static_cast<int>(reinterpret_cast<long long>(a2));
// update state
unit_id = icca_get_unit_id(unit_id);
keypad_update(unit_id);
update_card(unit_id);
// copy state to output buffer
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->status.status_code = unit->state;
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
// funny workaround
if (acio::ICCA_COMPAT_ACTIVE) {
if (avs::game::is_model("LA9")) {
auto p = (ICCA_STATUS*) status;
ICCA_STATUS_LA9 p_la9;
p_la9.status_code = unit->state;
p_la9.card_in = unit->card_in;
memcpy(p_la9.uid, p->uid, sizeof(p_la9.uid));
p_la9.error = p->error;
memcpy(p_la9.uid2, p->uid, sizeof(p_la9.uid));
memcpy(status, &p_la9, sizeof(ICCA_STATUS_LA9));
} else {
// the struct is different (28 bytes instead of 24) but nobody ain't got time for that
auto p = (ICCA_STATUS*) status;
p->error = p->key_level << 16;
p->front_sensor = p->uid[0];
p->rear_sensor = p->uid[1];
for (size_t i = 2; i < sizeof(p->uid); i++) {
p->uid[i - 2] = p->uid[i];
}
p->uid[sizeof(p->uid) - 2] = 0;
p->uid[sizeof(p->uid) - 1] = 0;
}
}
return 1;
}
static char __cdecl ac_io_icca_get_uid(int unit_id, char *card) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
// copy card
memcpy(card, unit->status.uid, 8);
// set felica flag
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
// check for error
return unit->state != ERR_GETUID;
}
static char __cdecl ac_io_icca_get_uid_felica(int unit_id, char *card) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
// copy card
memcpy(card, unit->status.uid, 8);
// set felica flag
bool felica = is_card_uid_felica(unit->status.uid);
card[8] = (char) (felica ? 1 : 0);
IS_LAST_CARD_FELICA = felica;
// check for error
return unit->state != ERR_GETUID;
}
static bool __cdecl ac_io_icca_is_felica() {
return IS_LAST_CARD_FELICA;
}
static char __cdecl ac_io_icca_req_uid(int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->state = GET_USERID;
update_card(unit_id);
return 1;
}
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (unit->card_in) {
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
unit->state = END;
} else {
unit->state = ERR_GETUID;
}
unit->card_in = false;
}
*read_state = unit->state;
return 1;
}
static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
return 0;
}
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
case STEP:
if (avs::game::is_model("JDZ"))
unit->state = SLEEP;
else
unit->state = STEP;
break;
case SLEEP:
unit->state = SLEEP;
break;
case INIT:
unit->state = READY;
break;
case START:
if (unit->card_in)
unit->state = ACTIVE;
else
unit->state = READY;
break;
case EJECT:
unit->card_in = false;
break;
case CLOSE_EJECT:
unit->state = unit->card_in ? EJECT_CHECK : SLEEP;
break;
case CLOSE_END:
unit->state = SLEEP;
break;
case GET_USERID:
unit->state = GET_USERID;
break;
default:
break;
}
return unit->state;
}
static char __cdecl ac_io_icca_req_status(int a1, char a2) {
return 1;
}
static bool __cdecl ac_io_icca_req_status_isfinished(int a1, int *a2) {
*a2 = 11;
return true;
}
/*
* Module stuff
*/
acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("ICCA", module, hookMode) {
this->status_buffer = (uint8_t*) &ICCA_UNITS[0];
this->status_buffer_size = sizeof(ICCA_UNITS);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::ICCAModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
ACIO_MODULE_HOOK(ac_io_icca_crypt_init);
ACIO_MODULE_HOOK(ac_io_icca_device_control_iccard_power_supply_off);
ACIO_MODULE_HOOK(ac_io_icca_device_control_iccard_power_supply_on);
ACIO_MODULE_HOOK(ac_io_icca_device_control_isfinished);
ACIO_MODULE_HOOK(ac_io_icca_get_keep_alive_error);
ACIO_MODULE_HOOK(ac_io_icca_get_status);
ACIO_MODULE_HOOK(ac_io_icca_get_uid);
ACIO_MODULE_HOOK(ac_io_icca_get_uid_felica);
ACIO_MODULE_HOOK(ac_io_icca_is_felica);
ACIO_MODULE_HOOK(ac_io_icca_req_uid);
ACIO_MODULE_HOOK(ac_io_icca_req_uid_isfinished);
ACIO_MODULE_HOOK(ac_io_icca_send_keep_alive_packet);
ACIO_MODULE_HOOK(ac_io_icca_workflow);
ACIO_MODULE_HOOK(ac_io_icca_req_status);
ACIO_MODULE_HOOK(ac_io_icca_req_status_isfinished);
}