mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
231c998cc0
## Link to GitHub Issue or related Pull Request, if one exists #121 ## Description of change This implements a seperate ICCA card workflow for the slotted readers acting how Resort Anthem expects, enabling cards to be inserted. Additionally this fixes a bug where both card units would be treated the same (for keypad reads) under Resort Anthem. The slotted reader workflow was implemented as a seperate function for two reasons - it would be nice to not mess with any existing code to avoid breaking all the functional games, and the way the slotted readers are handled by RA seemed different enough to how everything was already designed to where it didn't make sense to keep adding special edge cases for RA. ## Testing IIDX18 was tested and cards could be inserted on both P1 and P2 side. IIDX19 through 22 was tested to make sure nothing had broken for wavepass emulation. ## Known Issues In IIDX18, when a card isn't set but the insert key is pressed, after the card timeout the game will error out with an invalid state error.
591 lines
17 KiB
C++
591 lines
17 KiB
C++
#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 = 0,
|
|
SLEEP = 1,
|
|
START = 2,
|
|
INIT = 3,
|
|
READY = 4,
|
|
GET_USERID = 5,
|
|
ACTIVE = 6,
|
|
EJECT = 7,
|
|
EJECT_CHECK = 8,
|
|
END = 9,
|
|
CLOSE_EJECT = 10,
|
|
CLOSE_E_CHK = 11,
|
|
CLOSE_END = 12,
|
|
ERR_GETUID = -2,
|
|
ERR_REMOVED = -11
|
|
};
|
|
|
|
// used by RA as the status_code
|
|
enum ICCA_STATUS_CODE {
|
|
EMPTY = 1,
|
|
ENGAGED = 2
|
|
};
|
|
|
|
struct ICCA_UNIT {
|
|
struct ICCA_STATUS status {};
|
|
enum ICCA_WORKFLOW state = STEP;
|
|
enum ICCA_WORKFLOW state_jdz = SLEEP;
|
|
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;
|
|
int felica_retries = 0;
|
|
};
|
|
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) {
|
|
// on RA the unit_id is zero-indexed
|
|
if (avs::game::is_model("JDZ")) {
|
|
return 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;
|
|
static bool kb_insert_press_old[2] = {false, false};
|
|
|
|
// get unit
|
|
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
|
|
|
const bool card_presented = eamuse_card_insert_consume(icca_get_active_count(), unit_id);
|
|
const bool key_pressed = (kb_insert_press && !kb_insert_press_old[unit_id]);
|
|
|
|
// beatstream and nostalgia have logic that requires ac_io_icca_get_uid_felica to return the
|
|
// exact same card number multiple times in a row in order for the card number to be read...
|
|
// for whatever reason setting this to 3-4 doesn't work the very first time the game boots up
|
|
// so we use 10 just to be safe
|
|
const bool need_felica_retries = avs::game::is_model({"NBT", "PAN"});
|
|
if (need_felica_retries && (card_presented || key_pressed)) {
|
|
unit->felica_retries = 10;
|
|
}
|
|
|
|
// check for card insert
|
|
if (card_presented || key_pressed || (0 < unit->felica_retries)) {
|
|
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->state_jdz = ACTIVE;
|
|
unit->status.error = 0;
|
|
unit->status.front_sensor = 1;
|
|
unit->status.rear_sensor = 1;
|
|
} else {
|
|
unit->state = ERR_GETUID;
|
|
unit->state_jdz = ERR_GETUID;
|
|
unit->status.front_sensor = 1;
|
|
unit->status.rear_sensor = 1;
|
|
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;
|
|
unit->status.front_sensor = 0;
|
|
unit->status.rear_sensor = 0;
|
|
} else if (unit->state_jdz == READY) {
|
|
unit->state = GET_USERID;
|
|
unit->state_jdz = GET_USERID;
|
|
unit->status.error = 0;
|
|
unit->status.front_sensor = 1;
|
|
unit->status.rear_sensor = 1;
|
|
unit->card_in = true;
|
|
unit->card_in_time = get_performance_seconds();
|
|
}
|
|
} else {
|
|
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
|
if (unit->state_jdz == READY) {
|
|
unit->state_jdz = GET_USERID;
|
|
}
|
|
unit->status.error = 0;
|
|
unit->status.front_sensor = 1;
|
|
unit->status.rear_sensor = 1;
|
|
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;
|
|
memset(unit->status.uid, 0, 8);
|
|
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
|
|
unit->state_jdz = ERR_REMOVED;
|
|
}
|
|
unit->status.front_sensor = 0;
|
|
unit->status.rear_sensor = 0;
|
|
}
|
|
}
|
|
|
|
// 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];
|
|
if (avs::game::is_model("JDZ")) {
|
|
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
|
|
} else {
|
|
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;
|
|
|
|
if (0 < unit->felica_retries) {
|
|
unit->felica_retries--;
|
|
}
|
|
|
|
// 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 (avs::game::is_model("JDZ")) {
|
|
// hack for RA - only ever seen it as this
|
|
*read_state = 8;
|
|
return 1;
|
|
}
|
|
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_jdz(int workflow, int unit_id) {
|
|
|
|
unit_id = icca_get_unit_id(unit_id);
|
|
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
|
switch (workflow) {
|
|
// RA uses "STEP" as more of a polling thing it seems
|
|
case STEP:
|
|
switch (unit->state_jdz) {
|
|
case CLOSE_EJECT:
|
|
unit->state_jdz = CLOSE_E_CHK;
|
|
break;
|
|
case CLOSE_E_CHK:
|
|
unit->state_jdz = CLOSE_END;
|
|
break;
|
|
case CLOSE_END:
|
|
unit->state_jdz = SLEEP;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case SLEEP:
|
|
unit->state = SLEEP;
|
|
break;
|
|
case START:
|
|
unit->state_jdz = INIT;
|
|
break;
|
|
case INIT:
|
|
case READY:
|
|
unit->state_jdz = READY;
|
|
break;
|
|
case CLOSE_EJECT:
|
|
unit->state_jdz = CLOSE_EJECT;
|
|
return CLOSE_E_CHK;
|
|
case CLOSE_END:
|
|
unit->state_jdz = SLEEP;
|
|
break;
|
|
default:
|
|
// should probably log a warning here
|
|
break;
|
|
}
|
|
return unit->state_jdz;
|
|
}
|
|
|
|
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
|
|
// RA uses a different workflow process for the slotted readers; treat it differently
|
|
if (avs::game::is_model("JDZ")) {
|
|
return ac_io_icca_workflow_jdz(workflow, unit_id);
|
|
}
|
|
|
|
unit_id = icca_get_unit_id(unit_id);
|
|
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
|
switch (workflow) {
|
|
case STEP:
|
|
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();
|
|
|
|
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
|
|
if (avs::game::is_model("JDZ")) {
|
|
ICCA_UNITS[0].initialized = true;
|
|
ICCA_UNITS[1].initialized = true;
|
|
|
|
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
|
|
}
|
|
|
|
// 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);
|
|
}
|