mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-10-12 12:53:50 -07:00
fdc7adff7f
There are no servers to test this, but: * touch, touch with mouse * in theory card reader should work * printer works (RGB, front and back) * test/service button, lights What is (painfully) missing: * windowed mode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
372 lines
15 KiB
C++
372 lines
15 KiB
C++
#include "bi2x_hook.h"
|
|
|
|
#if SPICE64
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <memory>
|
|
|
|
#include "io.h"
|
|
#include "misc/eamuse.h"
|
|
#include "rawinput/rawinput.h"
|
|
#include "util/detour.h"
|
|
#include "util/logging.h"
|
|
#include "util/tapeled.h"
|
|
|
|
namespace games::cardconnect {
|
|
|
|
struct AIO_SCI_COMM {
|
|
uint8_t data[0xf8];
|
|
};
|
|
|
|
struct AIO_NMGR_IOB2 {
|
|
uint8_t data[0x9f8];
|
|
};
|
|
|
|
struct AIO_IOB2_BI2X_TCS {
|
|
uint8_t data[0x350];
|
|
};
|
|
|
|
struct AIO_IOB2_BI2X_WRFIRM {
|
|
uint8_t data[0x20f48];
|
|
};
|
|
|
|
#pragma pack(push, 1)
|
|
struct AIO_IOB2_BI2X_TCS__INPUT {
|
|
uint8_t DevIoCounter;
|
|
uint8_t bTest;
|
|
uint8_t bService;
|
|
uint8_t bCoinSw;
|
|
uint8_t bCoinJam;
|
|
uint8_t CoinCount;
|
|
};
|
|
|
|
struct AIO_IOB2_BI2X_TCS__DEVSTATUS {
|
|
uint8_t InputCounter;
|
|
uint8_t OutputCounter;
|
|
uint8_t TapeLedCounter;
|
|
AIO_IOB2_BI2X_TCS__INPUT Input;
|
|
uint8_t reserved[17];
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
// The supplied native DLL copies 26 bytes, even though the managed wrapper requests 27.
|
|
static_assert(sizeof(AIO_IOB2_BI2X_TCS__DEVSTATUS) == 26);
|
|
static_assert(offsetof(AIO_IOB2_BI2X_TCS__DEVSTATUS, Input) == 3);
|
|
static_assert(offsetof(AIO_IOB2_BI2X_TCS__INPUT, bTest) == 1);
|
|
static_assert(offsetof(AIO_IOB2_BI2X_TCS__INPUT, bService) == 2);
|
|
static_assert(offsetof(AIO_IOB2_BI2X_TCS__INPUT, bCoinSw) == 3);
|
|
static_assert(offsetof(AIO_IOB2_BI2X_TCS__INPUT, CoinCount) == 5);
|
|
|
|
static AIO_IOB2_BI2X_TCS *(__fastcall *aioIob2Bi2xTCS_Create_orig)(
|
|
AIO_NMGR_IOB2 *, uint32_t) = nullptr;
|
|
static uint32_t (__fastcall *aioIob2Bi2xTCS_GetDeviceStatus_orig)(
|
|
AIO_IOB2_BI2X_TCS *, AIO_IOB2_BI2X_TCS__DEVSTATUS *, uint32_t) = nullptr;
|
|
static void (__fastcall *aioIob2Bi2xTCS_IoReset_orig)(
|
|
AIO_IOB2_BI2X_TCS *, uint32_t) = nullptr;
|
|
static void (__fastcall *aioIob2Bi2xTCS_SetWatchDogTimer_orig)(
|
|
AIO_IOB2_BI2X_TCS *, uint8_t) = nullptr;
|
|
static void (__fastcall *aioIob2Bi2xTCS_ControlCoinBlocker_orig)(
|
|
AIO_IOB2_BI2X_TCS *, uint32_t, bool) = nullptr;
|
|
static void (__fastcall *aioIob2Bi2xTCS_AddCounter_orig)(
|
|
AIO_IOB2_BI2X_TCS *, uint32_t, uint32_t) = nullptr;
|
|
static void (__fastcall *aioIob2Bi2xTCS_SetPhotoLight_orig)(
|
|
AIO_IOB2_BI2X_TCS *, bool) = nullptr;
|
|
static void (__fastcall *aioIob2Bi2xTCS_SetIccrLed_orig)(
|
|
AIO_IOB2_BI2X_TCS *, uint32_t) = nullptr;
|
|
static void (__fastcall *aioIob2Bi2xTCS_SetTapeLedData_orig)(
|
|
AIO_IOB2_BI2X_TCS *, uint32_t, uint8_t *) = nullptr;
|
|
|
|
static AIO_SCI_COMM *(__fastcall *aioIob2Bi2x_OpenSciUsbCdc_orig)(uint32_t) = nullptr;
|
|
static AIO_IOB2_BI2X_WRFIRM *(__fastcall *aioIob2Bi2x_CreateWriteFirmContext_orig)(
|
|
uint32_t, uint32_t) = nullptr;
|
|
static void (__fastcall *aioIob2Bi2x_DestroyWriteFirmContext_orig)(
|
|
AIO_IOB2_BI2X_WRFIRM *) = nullptr;
|
|
static int32_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_orig)(
|
|
AIO_IOB2_BI2X_WRFIRM *) = nullptr;
|
|
static AIO_NMGR_IOB2 *(__fastcall *aioNMgrIob2_Create_orig)(
|
|
AIO_SCI_COMM *, uint32_t) = nullptr;
|
|
static void (__fastcall *aioNMgrIob_BeginManage_orig)(AIO_NMGR_IOB2 *) = nullptr;
|
|
|
|
static void (__fastcall *aioNodeMgr_Destroy_orig)(AIO_NMGR_IOB2 *) = nullptr;
|
|
static int32_t (__fastcall *aioNodeMgr_GetState_orig)(AIO_NMGR_IOB2 *) = nullptr;
|
|
static bool (__fastcall *aioNodeMgr_IsReady_orig)(AIO_NMGR_IOB2 *, int32_t) = nullptr;
|
|
static void (__fastcall *aioNodeCtl_Destroy_orig)(AIO_IOB2_BI2X_TCS *) = nullptr;
|
|
static int32_t (__fastcall *aioNodeCtl_GetState_orig)(AIO_IOB2_BI2X_TCS *) = nullptr;
|
|
static bool (__fastcall *aioNodeCtl_IsReady_orig)(AIO_IOB2_BI2X_TCS *, int32_t) = nullptr;
|
|
static void (__fastcall *aioSci_Destroy_orig)(AIO_SCI_COMM *) = nullptr;
|
|
|
|
static std::unique_ptr<AIO_SCI_COMM> aio_sci;
|
|
static std::unique_ptr<AIO_NMGR_IOB2> aio_manager;
|
|
static std::unique_ptr<AIO_IOB2_BI2X_TCS> aio_board;
|
|
static std::unique_ptr<AIO_IOB2_BI2X_WRFIRM> aio_firmware;
|
|
static uint8_t input_counter = 0;
|
|
static bool coin_pressed = false;
|
|
|
|
static void write_light(size_t index, float value) {
|
|
auto &lights = get_lights();
|
|
GameAPI::Lights::writeLight(RI_MGR, lights[index], value);
|
|
}
|
|
|
|
static AIO_IOB2_BI2X_TCS *__fastcall aioIob2Bi2xTCS_Create(
|
|
AIO_NMGR_IOB2 *manager, uint32_t device_id) {
|
|
if (!aio_manager || manager != aio_manager.get()) {
|
|
return aioIob2Bi2xTCS_Create_orig(manager, device_id);
|
|
}
|
|
if (!aio_board) {
|
|
aio_board = std::make_unique<AIO_IOB2_BI2X_TCS>();
|
|
input_counter = 0;
|
|
coin_pressed = false;
|
|
log_info("cardconnect::bi2x", "created emulated BI2X-TCS board");
|
|
}
|
|
return aio_board.get();
|
|
}
|
|
|
|
static uint32_t __fastcall aioIob2Bi2xTCS_GetDeviceStatus(
|
|
AIO_IOB2_BI2X_TCS *board, AIO_IOB2_BI2X_TCS__DEVSTATUS *status,
|
|
uint32_t status_size) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioIob2Bi2xTCS_GetDeviceStatus_orig(board, status, status_size);
|
|
}
|
|
if (!status && status_size > 0) {
|
|
log_warning("cardconnect::bi2x", "GetDeviceStatus called with a null output buffer");
|
|
return 0;
|
|
}
|
|
|
|
auto &buttons = get_buttons();
|
|
const bool coin = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]);
|
|
if (coin && !coin_pressed) {
|
|
eamuse_coin_insert();
|
|
}
|
|
coin_pressed = coin;
|
|
|
|
AIO_IOB2_BI2X_TCS__DEVSTATUS current {};
|
|
current.InputCounter = ++input_counter;
|
|
current.Input.DevIoCounter = input_counter;
|
|
current.Input.bTest = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 1 : 0;
|
|
current.Input.bService = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 1 : 0;
|
|
current.Input.bCoinSw = coin ? 1 : 0;
|
|
current.Input.CoinCount = static_cast<uint8_t>(eamuse_coin_get_stock());
|
|
|
|
RI_MGR->devices_flush_output();
|
|
|
|
const auto copy_size = std::min<uint32_t>(status_size, sizeof(current));
|
|
if (copy_size > 0) {
|
|
std::memcpy(status, ¤t, copy_size);
|
|
}
|
|
return copy_size;
|
|
}
|
|
|
|
static void __fastcall aioIob2Bi2xTCS_IoReset(AIO_IOB2_BI2X_TCS *board, uint32_t bits) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioIob2Bi2xTCS_IoReset_orig(board, bits);
|
|
}
|
|
}
|
|
|
|
static void __fastcall aioIob2Bi2xTCS_SetWatchDogTimer(
|
|
AIO_IOB2_BI2X_TCS *board, uint8_t count) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioIob2Bi2xTCS_SetWatchDogTimer_orig(board, count);
|
|
}
|
|
}
|
|
|
|
static void __fastcall aioIob2Bi2xTCS_ControlCoinBlocker(
|
|
AIO_IOB2_BI2X_TCS *board, uint32_t slot, bool open) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioIob2Bi2xTCS_ControlCoinBlocker_orig(board, slot, open);
|
|
}
|
|
if (slot != 0) {
|
|
log_warning("cardconnect::bi2x", "unknown coin blocker slot {}", slot);
|
|
return;
|
|
}
|
|
eamuse_coin_set_block(!open);
|
|
}
|
|
|
|
static void __fastcall aioIob2Bi2xTCS_AddCounter(
|
|
AIO_IOB2_BI2X_TCS *board, uint32_t counter, uint32_t count) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioIob2Bi2xTCS_AddCounter_orig(board, counter, count);
|
|
}
|
|
// This drives the physical bookkeeping meter, not the input coin counter.
|
|
}
|
|
|
|
static void __fastcall aioIob2Bi2xTCS_SetPhotoLight(AIO_IOB2_BI2X_TCS *board, bool on) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioIob2Bi2xTCS_SetPhotoLight_orig(board, on);
|
|
}
|
|
write_light(Lights::PhotoLight, on ? 1.f : 0.f);
|
|
}
|
|
|
|
static void __fastcall aioIob2Bi2xTCS_SetIccrLed(AIO_IOB2_BI2X_TCS *board, uint32_t rgb) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioIob2Bi2xTCS_SetIccrLed_orig(board, rgb);
|
|
}
|
|
write_light(Lights::CardReaderR, ((rgb >> 16) & 0xff) / 255.f);
|
|
write_light(Lights::CardReaderG, ((rgb >> 8) & 0xff) / 255.f);
|
|
write_light(Lights::CardReaderB, (rgb & 0xff) / 255.f);
|
|
}
|
|
|
|
static void __fastcall aioIob2Bi2xTCS_SetTapeLedData(
|
|
AIO_IOB2_BI2X_TCS *board, uint32_t tape, uint8_t *data) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioIob2Bi2xTCS_SetTapeLedData_orig(board, tape, data);
|
|
}
|
|
if (!data || tape >= 2) {
|
|
log_warning("cardconnect::bi2x", "invalid tape LED data for channel {}", tape);
|
|
return;
|
|
}
|
|
if (tapeledutils::is_enabled()) {
|
|
const auto color = tapeledutils::pick_color_from_led_tape(data, 15);
|
|
const auto first = tape == 0 ? Lights::SideLeftR : Lights::SideRightR;
|
|
write_light(first, color.r);
|
|
write_light(first + 1, color.g);
|
|
write_light(first + 2, color.b);
|
|
}
|
|
}
|
|
|
|
static AIO_SCI_COMM *__fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t serial_number) {
|
|
if (!aio_sci) {
|
|
aio_sci = std::make_unique<AIO_SCI_COMM>();
|
|
}
|
|
return aio_sci.get();
|
|
}
|
|
|
|
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(
|
|
uint32_t serial_number, uint32_t iob_mask) {
|
|
if (!aio_firmware) {
|
|
aio_firmware = std::make_unique<AIO_IOB2_BI2X_WRFIRM>();
|
|
}
|
|
return aio_firmware.get();
|
|
}
|
|
|
|
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *context) {
|
|
if (!aio_firmware || context != aio_firmware.get()) {
|
|
return aioIob2Bi2x_DestroyWriteFirmContext_orig(context);
|
|
}
|
|
aio_firmware.reset();
|
|
}
|
|
|
|
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *context) {
|
|
if (!aio_firmware || context != aio_firmware.get()) {
|
|
return aioIob2Bi2x_WriteFirmGetState_orig(context);
|
|
}
|
|
return 8;
|
|
}
|
|
|
|
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *sci, uint32_t mode) {
|
|
if (!aio_sci || sci != aio_sci.get()) {
|
|
return aioNMgrIob2_Create_orig(sci, mode);
|
|
}
|
|
if (!aio_manager) {
|
|
aio_manager = std::make_unique<AIO_NMGR_IOB2>();
|
|
}
|
|
return aio_manager.get();
|
|
}
|
|
|
|
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *manager) {
|
|
if (!aio_manager || manager != aio_manager.get()) {
|
|
return aioNMgrIob_BeginManage_orig(manager);
|
|
}
|
|
}
|
|
|
|
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB2 *manager) {
|
|
if (!aio_manager || manager != aio_manager.get()) {
|
|
return aioNodeMgr_Destroy_orig(manager);
|
|
}
|
|
aio_manager.reset();
|
|
}
|
|
|
|
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB2 *manager) {
|
|
if (!aio_manager || manager != aio_manager.get()) {
|
|
return aioNodeMgr_GetState_orig(manager);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB2 *manager, int32_t state) {
|
|
if (!aio_manager || manager != aio_manager.get()) {
|
|
return aioNodeMgr_IsReady_orig(manager, state);
|
|
}
|
|
return state >= 1;
|
|
}
|
|
|
|
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_TCS *board) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioNodeCtl_Destroy_orig(board);
|
|
}
|
|
aio_board.reset();
|
|
}
|
|
|
|
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB2_BI2X_TCS *board) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioNodeCtl_GetState_orig(board);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB2_BI2X_TCS *board, int32_t state) {
|
|
if (!aio_board || board != aio_board.get()) {
|
|
return aioNodeCtl_IsReady_orig(board, state);
|
|
}
|
|
return state >= 1;
|
|
}
|
|
|
|
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *sci) {
|
|
if (!aio_sci || sci != aio_sci.get()) {
|
|
return aioSci_Destroy_orig(sci);
|
|
}
|
|
aio_sci.reset();
|
|
}
|
|
|
|
void bi2x_hook_init() {
|
|
log_info("cardconnect::bi2x", "initializing native BI2X-TCS hooks");
|
|
|
|
const auto video = "libaio-iob2_video.dll";
|
|
detour::trampoline(video, "aioIob2Bi2xTCS_Create",
|
|
aioIob2Bi2xTCS_Create, &aioIob2Bi2xTCS_Create_orig);
|
|
detour::trampoline(video, "aioIob2Bi2xTCS_GetDeviceStatus",
|
|
aioIob2Bi2xTCS_GetDeviceStatus, &aioIob2Bi2xTCS_GetDeviceStatus_orig);
|
|
detour::trampoline(video, "aioIob2Bi2xTCS_IoReset",
|
|
aioIob2Bi2xTCS_IoReset, &aioIob2Bi2xTCS_IoReset_orig);
|
|
detour::trampoline(video, "aioIob2Bi2xTCS_SetWatchDogTimer",
|
|
aioIob2Bi2xTCS_SetWatchDogTimer, &aioIob2Bi2xTCS_SetWatchDogTimer_orig);
|
|
detour::trampoline(video, "aioIob2Bi2xTCS_ControlCoinBlocker",
|
|
aioIob2Bi2xTCS_ControlCoinBlocker, &aioIob2Bi2xTCS_ControlCoinBlocker_orig);
|
|
detour::trampoline(video, "aioIob2Bi2xTCS_AddCounter",
|
|
aioIob2Bi2xTCS_AddCounter, &aioIob2Bi2xTCS_AddCounter_orig);
|
|
detour::trampoline(video, "aioIob2Bi2xTCS_SetPhotoLight",
|
|
aioIob2Bi2xTCS_SetPhotoLight, &aioIob2Bi2xTCS_SetPhotoLight_orig);
|
|
detour::trampoline(video, "aioIob2Bi2xTCS_SetIccrLed",
|
|
aioIob2Bi2xTCS_SetIccrLed, &aioIob2Bi2xTCS_SetIccrLed_orig);
|
|
detour::trampoline(video, "aioIob2Bi2xTCS_SetTapeLedData",
|
|
aioIob2Bi2xTCS_SetTapeLedData, &aioIob2Bi2xTCS_SetTapeLedData_orig);
|
|
|
|
const auto iob = "libaio-iob.dll";
|
|
detour::trampoline(iob, "aioIob2Bi2x_OpenSciUsbCdc",
|
|
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
|
detour::trampoline(iob, "aioIob2Bi2x_CreateWriteFirmContext",
|
|
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
|
detour::trampoline(iob, "aioIob2Bi2x_DestroyWriteFirmContext",
|
|
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
|
detour::trampoline(iob, "aioIob2Bi2x_WriteFirmGetState",
|
|
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
|
detour::trampoline(iob, "aioNMgrIob2_Create",
|
|
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
|
detour::trampoline(iob, "aioNMgrIob_BeginManage",
|
|
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
|
|
|
|
const auto aio = "libaio.dll";
|
|
detour::trampoline(aio, "aioNodeMgr_Destroy", aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
|
detour::trampoline(aio, "aioNodeMgr_GetState", aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
|
|
detour::trampoline(aio, "aioNodeMgr_IsReady", aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
|
|
detour::trampoline(aio, "aioNodeCtl_Destroy", aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
|
detour::trampoline(aio, "aioNodeCtl_GetState", aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
|
|
detour::trampoline(aio, "aioNodeCtl_IsReady", aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
|
|
detour::trampoline(aio, "aioSci_Destroy", aioSci_Destroy, &aioSci_Destroy_orig);
|
|
}
|
|
}
|
|
|
|
#endif
|