#include #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(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(reinterpret_cast(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); }