Let us start here, from square one. No, from Zero!

This commit is contained in:
smpn2
2022-09-30 22:45:51 +09:00
parent 14215af0d1
commit b7a60638a4
1005 changed files with 303069 additions and 0 deletions
+241
View File
@@ -0,0 +1,241 @@
#include "bi2a.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "iidx.h"
#include "io.h"
using namespace acioemu;
games::iidx::IIDXFMSerialHandle::FMSerialDevice::FMSerialDevice() {
this->node_count = 1;
}
bool games::iidx::IIDXFMSerialHandle::FMSerialDevice::parse_msg(
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer
) {
#ifdef ACIOEMU_LOG
log_info("iidx", "BI2A ADDR: {}, CMD: 0x{:04x}", msg_in->addr, 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);
this->set_version(msg, 0x03, 0, 4, 2, 0, "BI2A");
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0152: { // STATUS
// check input data length
if (msg_in->cmd.data_size == 0x30) {
// LED TICKER
IIDX_LED_TICKER_LOCK.lock();
if (!IIDXIO_LED_TICKER_READONLY)
memcpy(IIDXIO_LED_TICKER, &msg_in->cmd.raw[0x17], 9);
IIDX_LED_TICKER_LOCK.unlock();
// NEON LIGHT
bool neon_light = msg_in->cmd.raw[0x24] != 0;
write_top_neon(neon_light ? (uint8_t) 0xFF : (uint8_t) 0);
// SPOT LIGHT
uint8_t spot_light_bits = 0;
for (size_t i = 0; i < 8; i++) {
auto index = i > 3 ? i + 1 : i;
if (msg_in->cmd.raw[0x20 + index] != 0)
spot_light_bits |= 1 << i;
}
write_top_lamp(spot_light_bits);
// SWLED (DECK)
uint16_t lamp_bits = 0;
for (size_t i = 0; i < 14; i++) {
if (msg_in->cmd.raw[0x07 + i] != 0)
lamp_bits |= 1 << i;
}
write_lamp(lamp_bits);
// SWLED (PANEL)
uint8_t led_bits = 0;
for (size_t i = 0; i < 4; i++) {
if (msg_in->cmd.raw[0x03 + i] != 0)
led_bits |= 1 << i;
}
write_led(led_bits);
// flush device output
RI_MGR->devices_flush_output();
}
// sleep - otherwise the IO thread will go too hard on the CPU
Sleep(1);
// generate message
auto msg = this->create_msg(msg_in, 0x2E);
// get buttons
auto &buttons = get_buttons();
// player 1 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_1)))
ARRAY_SETB(msg->cmd.raw, 151);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_2)))
ARRAY_SETB(msg->cmd.raw, 167);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_3)))
ARRAY_SETB(msg->cmd.raw, 183);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_4)))
ARRAY_SETB(msg->cmd.raw, 199);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_5)))
ARRAY_SETB(msg->cmd.raw, 215);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_6)))
ARRAY_SETB(msg->cmd.raw, 231);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_7)))
ARRAY_SETB(msg->cmd.raw, 247);
// player 2 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_1)))
ARRAY_SETB(msg->cmd.raw, 263);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_2)))
ARRAY_SETB(msg->cmd.raw, 279);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_3)))
ARRAY_SETB(msg->cmd.raw, 295);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_4)))
ARRAY_SETB(msg->cmd.raw, 311);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_5)))
ARRAY_SETB(msg->cmd.raw, 327);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_6)))
ARRAY_SETB(msg->cmd.raw, 343);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_7)))
ARRAY_SETB(msg->cmd.raw, 359);
// player 1 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_Start)))
ARRAY_SETB(msg->cmd.raw, 79);
// player 2 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_Start)))
ARRAY_SETB(msg->cmd.raw, 78);
// VEFX
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::VEFX)))
ARRAY_SETB(msg->cmd.raw, 77);
// EFFECT
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Effect)))
ARRAY_SETB(msg->cmd.raw, 76);
// service
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service)))
ARRAY_SETB(msg->cmd.raw, 10);
// test
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test)))
ARRAY_SETB(msg->cmd.raw, 11);
// turntables
msg->cmd.raw[16] = get_tt(0, true);
msg->cmd.raw[17] = get_tt(1, true);
// slider
msg->cmd.raw[0] |= get_slider(0) << 4;
msg->cmd.raw[2] |= get_slider(1) << 4;
msg->cmd.raw[4] |= get_slider(2) << 4;
msg->cmd.raw[6] |= get_slider(3) << 4;
msg->cmd.raw[7] |= get_slider(4) << 4;
// coin
this->coin_counter += eamuse_coin_consume_stock();
msg->cmd.raw[8] |= this->coin_counter;
/*
* TODO
* bit 9 appears to be the coin mech
*/
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_CLEAR:
case ACIO_CMD_STARTUP:
case 0x0120: // WD COMMAND
case 0xFF: // BROADCAST
{
// send status 0
auto msg = this->create_msg_status(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
return false;
}
// mark as handled
return true;
}
bool games::iidx::IIDXFMSerialHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM2") != 0) {
return false;
}
log_info("iidx", "Opened COM2 (FM DEVICE)");
// ACIO device
acio_emu.add_device(new FMSerialDevice());
return true;
}
int games::iidx::IIDXFMSerialHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// read from emu
DWORD bytes_read = 0;
while (bytes_read < nNumberOfBytesToRead) {
auto cur_byte = acio_emu.read();
if (cur_byte.has_value()) {
buffer[bytes_read++] = cur_byte.value();
} else {
break;
}
}
// return amount of bytes read
return (int) bytes_read;
}
int games::iidx::IIDXFMSerialHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
// write to emu
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu.write(buffer[i]);
}
// return all data written
return (int) nNumberOfBytesToWrite;
}
int games::iidx::IIDXFMSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::iidx::IIDXFMSerialHandle::close() {
log_info("iidx", "Closed COM2 (FM DEVICE)");
return true;
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace games::iidx {
class IIDXFMSerialHandle : public CustomHandle {
private:
acioemu::ACIOEmu acio_emu;
class FMSerialDevice : public acioemu::ACIODeviceEmu {
private:
uint8_t coin_counter = 0;
public:
FMSerialDevice();
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
};
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}
+248
View File
@@ -0,0 +1,248 @@
#include "bi2x.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "iidx.h"
#include "io.h"
using namespace acioemu;
games::iidx::BI2XSerialHandle::BI2XDevice::BI2XDevice() {
this->node_count = 1;
}
bool games::iidx::BI2XSerialHandle::BI2XDevice::parse_msg(
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer
) {
#ifdef ACIOEMU_LOG
log_info("iidx", "BI2X ADDR: {}, CMD: 0x{:04x}", msg_in->addr, 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);
this->set_version(msg, 0x03, 0, 4, 2, 0, "BI2X");
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0152: { // STATUS
// check input data length
if (msg_in->cmd.data_size == 0x30) {
// LED TICKER
IIDX_LED_TICKER_LOCK.lock();
if (!IIDXIO_LED_TICKER_READONLY)
memcpy(IIDXIO_LED_TICKER, &msg_in->cmd.raw[0x17], 9);
IIDX_LED_TICKER_LOCK.unlock();
// NEON LIGHT
bool neon_light = msg_in->cmd.raw[0x24] != 0;
write_top_neon(neon_light ? (uint8_t) 0xFF : (uint8_t) 0);
// SPOT LIGHT
uint8_t spot_light_bits = 0;
for (size_t i = 0; i < 8; i++) {
auto index = i > 3 ? i + 1 : i;
if (msg_in->cmd.raw[0x20 + index] != 0)
spot_light_bits |= 1 << i;
}
write_top_lamp(spot_light_bits);
// SWLED (DECK)
uint16_t lamp_bits = 0;
for (size_t i = 0; i < 14; i++) {
if (msg_in->cmd.raw[0x07 + i] != 0)
lamp_bits |= 1 << i;
}
write_lamp(lamp_bits);
// SWLED (PANEL)
uint8_t led_bits = 0;
for (size_t i = 0; i < 4; i++) {
if (msg_in->cmd.raw[0x03 + i] != 0)
led_bits |= 1 << i;
}
write_led(led_bits);
// flush device output
RI_MGR->devices_flush_output();
}
// sleep - otherwise the IO thread will go too hard on the CPU
Sleep(1);
// generate message
auto msg = this->create_msg(msg_in, 0x2E);
// get buttons
auto &buttons = get_buttons();
// player 1 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_1)))
ARRAY_SETB(msg->cmd.raw, 151);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_2)))
ARRAY_SETB(msg->cmd.raw, 167);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_3)))
ARRAY_SETB(msg->cmd.raw, 183);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_4)))
ARRAY_SETB(msg->cmd.raw, 199);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_5)))
ARRAY_SETB(msg->cmd.raw, 215);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_6)))
ARRAY_SETB(msg->cmd.raw, 231);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_7)))
ARRAY_SETB(msg->cmd.raw, 247);
// player 2 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_1)))
ARRAY_SETB(msg->cmd.raw, 263);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_2)))
ARRAY_SETB(msg->cmd.raw, 279);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_3)))
ARRAY_SETB(msg->cmd.raw, 295);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_4)))
ARRAY_SETB(msg->cmd.raw, 311);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_5)))
ARRAY_SETB(msg->cmd.raw, 327);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_6)))
ARRAY_SETB(msg->cmd.raw, 343);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_7)))
ARRAY_SETB(msg->cmd.raw, 359);
// player 1 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_Start)))
ARRAY_SETB(msg->cmd.raw, 79);
// player 2 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_Start)))
ARRAY_SETB(msg->cmd.raw, 78);
// VEFX
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::VEFX)))
ARRAY_SETB(msg->cmd.raw, 77);
// EFFECT
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Effect)))
ARRAY_SETB(msg->cmd.raw, 76);
// service
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service)))
ARRAY_SETB(msg->cmd.raw, 10);
// test
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test)))
ARRAY_SETB(msg->cmd.raw, 11);
// turntables
msg->cmd.raw[16] = get_tt(0, true);
msg->cmd.raw[17] = get_tt(1, true);
// slider
msg->cmd.raw[0] |= get_slider(0) << 4;
msg->cmd.raw[2] |= get_slider(1) << 4;
msg->cmd.raw[4] |= get_slider(2) << 4;
msg->cmd.raw[6] |= get_slider(3) << 4;
msg->cmd.raw[7] |= get_slider(4) << 4;
// coin
this->coin_counter += eamuse_coin_consume_stock();
msg->cmd.raw[8] |= this->coin_counter;
/*
* TODO
* bit 9 appears to be the coin mech
*/
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_CLEAR:
case ACIO_CMD_STARTUP:
case 0x0120: // WD COMMAND
case 0xFF: // BROADCAST
{
// send status 0
auto msg = this->create_msg_status(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
return false;
}
// mark as handled
return true;
}
bool games::iidx::BI2XSerialHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM3") != 0) {
return false;
}
log_info("iidx", "Opened COM3 (BIO2)");
// ACIO device
//acio_emu.add_device(new FMSerialDevice());
return true;
}
int games::iidx::BI2XSerialHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
//auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// read from emu
/*
DWORD bytes_read = 0;
while (bytes_read < nNumberOfBytesToRead) {
auto cur_byte = acio_emu.read();
if (cur_byte.has_value()) {
buffer[bytes_read++] = cur_byte.value();
} else {
break;
}
}
*/
// return amount of bytes read
//return (int) bytes_read;
return (int) nNumberOfBytesToRead;
}
int games::iidx::BI2XSerialHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
log_info("iidx", "BIO2 Data: {}", bin2hex(buffer, nNumberOfBytesToWrite));
// write to emu
/*
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu.write(buffer[i]);
}
*/
// return all data written
return (int) nNumberOfBytesToWrite;
}
int games::iidx::BI2XSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::iidx::BI2XSerialHandle::close() {
log_info("iidx", "Closed COM3 (BIO2)");
return true;
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace games::iidx {
class BI2XSerialHandle : public CustomHandle {
private:
acioemu::ACIOEmu acio_emu;
class BI2XDevice : public acioemu::ACIODeviceEmu {
private:
uint8_t coin_counter = 0;
public:
BI2XDevice();
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
};
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}
+561
View File
@@ -0,0 +1,561 @@
#include "bi2x_hook.h"
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
#include "util/utils.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/io.h"
#include "launcher/options.h"
#include "io.h"
#include "iidx.h"
namespace games::iidx {
constexpr bool BI2X_PASSTHROUGH = false;
/*
* class definitions
*/
struct AC_HNDLIF {
// dummy
uint8_t data[0x10];
};
struct AIO_NMGR_IOB2 {
uint8_t dummy0[0x50];
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
uint8_t dummy1[0x9A0];
};
struct AIO_IOB2_BI2X_TDJ {
// who knows
uint8_t data[0x13F8];
};
struct AIO_IOB2_BI2X_TDJ__DEVSTATUS {
// of course you could work with variables here
uint8_t buffer[0xCA];
};
/*
* typedefs
*/
// libaio-iob2_video.dll
typedef AIO_IOB2_BI2X_TDJ* (__fastcall *aioIob2Bi2xTDJ_Create_t)(AIO_NMGR_IOB2 *nmgr, int a2);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__GetDeviceStatus_t)(AIO_IOB2_BI2X_TDJ *This,
AIO_IOB2_BI2X_TDJ__DEVSTATUS *a2);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__IoReset_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int a2);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_t)(AIO_IOB2_BI2X_TDJ *This,
uint8_t a2);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_t)(AIO_IOB2_BI2X_TDJ *This,
int index, uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__AddCounter_t)(AIO_IOB2_BI2X_TDJ *This,
int a2, int a3);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetStartLamp_t)(AIO_IOB2_BI2X_TDJ *This,
int player, uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_t)(AIO_IOB2_BI2X_TDJ *This,
uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_t)(AIO_IOB2_BI2X_TDJ *This,
uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_t)(AIO_IOB2_BI2X_TDJ *This,
int player, int button, uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetWooferLED_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetIccrLed_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetTurnTableLed_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetTurnTableResist_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint8_t resistance);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetTapeLedData_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint8_t *data);
// libaio-iob.dll
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
/*
* function pointers
*/
// libaio-iob2_video.dll
static aioIob2Bi2xTDJ_Create_t aioIob2Bi2xTDJ_Create_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__GetDeviceStatus_t AIO_IOB2_BI2X_TDJ__GetDeviceStatus_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__IoReset_t AIO_IOB2_BI2X_TDJ__IoReset_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_t AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_t AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__AddCounter_t AIO_IOB2_BI2X_TDJ__AddCounter_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetStartLamp_t AIO_IOB2_BI2X_TDJ__SetStartLamp_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_t AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_t AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_t AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetWooferLED_t AIO_IOB2_BI2X_TDJ__SetWooferLED_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetIccrLed_t AIO_IOB2_BI2X_TDJ__SetIccrLed_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetTurnTableLed_t AIO_IOB2_BI2X_TDJ__SetTurnTableLed_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetTurnTableResist_t AIO_IOB2_BI2X_TDJ__SetTurnTableResist_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetTapeLedData_t AIO_IOB2_BI2X_TDJ__SetTapeLedData_orig = nullptr;
// libaio-iob.dll
static aioIob2Bi2x_OpenSciUsbCdc_t aioIob2Bi2x_OpenSciUsbCdc_orig = nullptr;
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
/*
* variables
*/
AIO_IOB2_BI2X_TDJ *custom_node = nullptr;
AC_HNDLIF *acHndlif = nullptr;
AIO_NMGR_IOB2 *aioNmgrIob2 = nullptr;
/*
* implementations
*/
static AIO_IOB2_BI2X_TDJ* __fastcall aioIob2Bi2xTDJ_Create(AIO_NMGR_IOB2 *nmgr, int a2) {
if (!BI2X_PASSTHROUGH) {
custom_node = new AIO_IOB2_BI2X_TDJ;
memset(&custom_node->data, 0, sizeof(custom_node->data));
return custom_node;
} else {
// call original
return aioIob2Bi2xTDJ_Create_orig(nmgr, a2);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__GetDeviceStatus(AIO_IOB2_BI2X_TDJ *This,
AIO_IOB2_BI2X_TDJ__DEVSTATUS *status) {
// flush raw input
RI_MGR->devices_flush_output();
// check handle
if (This == custom_node) {
// clear input data
memset(status, 0x00, sizeof(AIO_IOB2_BI2X_TDJ__DEVSTATUS));
} else {
// get data from real device
AIO_IOB2_BI2X_TDJ__GetDeviceStatus_orig(This, status);
}
// get buttons
auto &buttons = get_buttons();
// control buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
status->buffer[4] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
status->buffer[5] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
status->buffer[6] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VEFX]))
status->buffer[10] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Effect]))
status->buffer[11] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_Headphone]))
status->buffer[12] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_Headphone]))
status->buffer[13] = 0xFF;
// coin stock
status->buffer[22] += eamuse_coin_get_stock();
// player 1 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_Start]))
status->buffer[8] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_1]))
status->buffer[27] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_2]))
status->buffer[28] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_3]))
status->buffer[29] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_4]))
status->buffer[30] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_5]))
status->buffer[31] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_6]))
status->buffer[32] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_7]))
status->buffer[33] = 0xFF;
// player 2 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_Start]))
status->buffer[9] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_1]))
status->buffer[34] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_2]))
status->buffer[35] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_3]))
status->buffer[36] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_4]))
status->buffer[37] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_5]))
status->buffer[38] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_6]))
status->buffer[39] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_7]))
status->buffer[40] = 0xFF;
// turntables
status->buffer[20] += get_tt(0, false);
status->buffer[21] += get_tt(1, false);
return;
}
static void __fastcall AIO_IOB2_BI2X_TDJ__IoReset(AIO_IOB2_BI2X_TDJ *This,
unsigned int a2)
{
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__IoReset_orig(This, a2);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetWatchDogTimer(AIO_IOB2_BI2X_TDJ *This,
uint8_t a2)
{
if (This != custom_node) {
// comment this out if you want to disable the BI2X watchdog timer
return AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_orig(This, a2);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__ControlCoinBlocker(AIO_IOB2_BI2X_TDJ *This,
int index, uint8_t state) {
// coin blocker is closed when state is zero
eamuse_coin_set_block(state == 0);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_orig(This, index, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__AddCounter(AIO_IOB2_BI2X_TDJ *This,
int a2, int a3)
{
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__AddCounter_orig(This, a2, a3);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetStartLamp(AIO_IOB2_BI2X_TDJ *This,
int player, uint8_t state)
{
auto &lights = get_lights();
if (player == 0) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::P1_Start], state ? 1.f : 0.f);
} else if (player == 1) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::P2_Start], state ? 1.f : 0.f);
}
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetStartLamp_orig(This, player, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp(AIO_IOB2_BI2X_TDJ *This,
uint8_t state)
{
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::VEFX], state ? 1.f : 0.f);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_orig(This, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp(AIO_IOB2_BI2X_TDJ *This,
uint8_t state)
{
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::Effect], state ? 1.f : 0.f);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_orig(This, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp(AIO_IOB2_BI2X_TDJ *This,
int player, int button, uint8_t state)
{
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::P1_1 + player * 7 + button], state ? 1.f : 0.f);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_orig(This, player, button, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetWooferLED(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color)
{
uint32_t col_r = (color & 0xFF0000) >> 16;
uint32_t col_g = (color & 0x00FF00) >> 8;
uint32_t col_b = (color & 0x0000FF) >> 0;
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::WooferR], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::WooferG], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::WooferB], col_b / 255.f);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetWooferLED_orig(This, index, color);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetIccrLed(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color)
{
uint32_t col_r = (color & 0xFF0000) >> 16;
uint32_t col_g = (color & 0x00FF00) >> 8;
uint32_t col_b = (color & 0x0000FF) >> 0;
auto &lights = get_lights();
if (index == 0) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P1_R], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P1_G], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P1_B], col_b / 255.f);
} else if (index == 1) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P2_R], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P2_G], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P2_B], col_b / 255.f);
}
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetIccrLed_orig(This, index, color);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetTurnTableLed(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color)
{
uint32_t col_r = (color & 0xFF0000) >> 16;
uint32_t col_g = (color & 0x00FF00) >> 8;
uint32_t col_b = (color & 0x0000FF) >> 0;
auto &lights = get_lights();
if (index == 0) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P1_R], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P1_G], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P1_B], col_b / 255.f);
} else if (index == 1) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P2_R], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P2_G], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P2_B], col_b / 255.f);
}
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetTurnTableLed_orig(This, index, color);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetTurnTableResist(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint8_t resistance)
{
auto &lights = get_lights();
if (index == 0) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P1_Resistance], resistance / 255.f);
} else if (index == 1) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P2_Resistance], resistance / 255.f);
}
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetTurnTableResist_orig(This, index, resistance);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetTapeLedData(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint8_t *data)
{
/*
* index mapping
*
* 0 - stage left - 57 bytes - 19 colors
* 1 - stage right - 57 bytes - 19 colors
* 2 - cabinet left - 135 bytes - 45 colors
* 3 - cabinet right - 135 bytes - 45 colors
* 4 - control panel under - 63 bytes - 21 colors
* 5 - ceiling left - 162 bytes - 54 colors
* 6 - title left - 33 bytes - 11 colors
* 7 - title right - 33 bytes - 11 colors
* 8 - ceiling right - 162 bytes - 54 colors
* 9 - touch panel left - 51 bytes - 17 colors
* 10 - touch panel right - 51 bytes - 17 colors
* 11 - side panel left inner - 204 bytes - 68 colors
* 12 - side panel left outer - 204 bytes - 68 colors
* 13 - side panel left - 183 bytes - 61 colors
* 14 - side panel right outer - 204 bytes - 68 colors
* 15 - side panel right inner - 204 bytes - 68 colors
* 16 - side panel right - 183 bytes - 61 colors
*
* data is stored in RGB order, 3 bytes per color
*
* TODO: expose this data via API
*/
// data mapping
static struct TapeLedMapping {
size_t data_size;
int index_r, index_g, index_b;
float avg_mult;
TapeLedMapping(size_t data_size, int index_r, int index_g, int index_b)
: data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {
avg_mult = 1.f / (data_size * 255);
}
} mapping[] = {
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB },
{ 19, Lights::StageRightAvgR, Lights::StageRightAvgG, Lights::StageRightAvgB },
{ 45, Lights::CabinetLeftAvgR, Lights::CabinetLeftAvgG, Lights::CabinetLeftAvgB },
{ 45, Lights::CabinetRightAvgR, Lights::CabinetRightAvgG, Lights::CabinetRightAvgB },
{ 21, Lights::ControlPanelUnderAvgR, Lights::ControlPanelUnderAvgG, Lights::ControlPanelUnderAvgB },
{ 54, Lights::CeilingLeftAvgR, Lights::CeilingLeftAvgG, Lights::CeilingLeftAvgB },
{ 11, Lights::TitleLeftAvgR, Lights::TitleLeftAvgG, Lights::TitleLeftAvgB },
{ 11, Lights::TitleRightAvgR, Lights::TitleRightAvgG, Lights::TitleRightAvgB },
{ 54, Lights::CeilingRightAvgR, Lights::CeilingRightAvgG, Lights::CeilingRightAvgB },
{ 17, Lights::TouchPanelLeftAvgR, Lights::TouchPanelLeftAvgG, Lights::TouchPanelLeftAvgB },
{ 17, Lights::TouchPanelRightAvgR, Lights::TouchPanelRightAvgG, Lights::TouchPanelRightAvgB },
{ 68, Lights::SidePanelLeftInnerAvgR, Lights::SidePanelLeftInnerAvgG, Lights::SidePanelLeftInnerAvgB },
{ 68, Lights::SidePanelLeftOuterAvgR, Lights::SidePanelLeftOuterAvgG, Lights::SidePanelLeftOuterAvgB },
{ 61, Lights::SidePanelLeftAvgR, Lights::SidePanelLeftAvgG, Lights::SidePanelLeftAvgB },
{ 68, Lights::SidePanelRightOuterAvgR, Lights::SidePanelRightOuterAvgG, Lights::SidePanelRightOuterAvgB },
{ 68, Lights::SidePanelRightInnerAvgR, Lights::SidePanelRightInnerAvgG, Lights::SidePanelRightInnerAvgB },
{ 61, Lights::SidePanelRightAvgR, Lights::SidePanelRightAvgG, Lights::SidePanelRightAvgB },
};
// check index bounds
if (index < std::size(mapping)) {
auto &map = mapping[index];
// calculate average color
size_t avg_ri = 0;
size_t avg_gi = 0;
size_t avg_bi = 0;
for (size_t i = 0; i < map.data_size; i++) {
auto color = &data[i * 3];
avg_ri += color[0];
avg_gi += color[1];
avg_bi += color[2];
}
float avg_r = avg_ri * map.avg_mult;
float avg_g = avg_gi * map.avg_mult;
float avg_b = avg_bi * map.avg_mult;
// set average color
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], avg_r);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], avg_g);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], avg_b);
}
if (This != custom_node) {
// send tape data to real device
return AIO_IOB2_BI2X_TDJ__SetTapeLedData_orig(This, index, data);
}
}
static AC_HNDLIF* __fastcall aioIob2Bi2X_OpenSciUsbCdc(uint8_t device_num) {
if (acHndlif == nullptr) {
acHndlif = new AC_HNDLIF;
memset(acHndlif->data, 0x0, sizeof(acHndlif->data));
}
log_info("bi2x_hook", "aioIob2Bi2x_OpenSciUsbCdc");
return acHndlif;
}
static void __fastcall AIO_NMGR_IOB_BeginManageStub(int64_t a1) {
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
};
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2;
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
}
log_info("bi2x_hook", "aioNMgrIob2_Create");
return &aioNmgrIob2;
}
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
log_info("bi2x_hook", "aioIob2Bi2x_WriteFirmGetState");
return 8;
}
void bi2x_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi2x_hook", "init");
// hook IOB2 video
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTDJ_Create",
aioIob2Bi2xTDJ_Create, &aioIob2Bi2xTDJ_Create_orig);
detour::trampoline_try(libaioIob2VideoDll, "?GetDeviceStatus@AIO_IOB2_BI2X_TDJ@@QEBAXAEAUDEVSTATUS@1@@Z",
AIO_IOB2_BI2X_TDJ__GetDeviceStatus, &AIO_IOB2_BI2X_TDJ__GetDeviceStatus_orig);
detour::trampoline_try(libaioIob2VideoDll, "?IoReset@AIO_IOB2_BI2X_TDJ@@QEAAXI@Z",
AIO_IOB2_BI2X_TDJ__IoReset, &AIO_IOB2_BI2X_TDJ__IoReset_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetWatchDogTimer@AIO_IOB2_BI2X_TDJ@@QEAAXE@Z",
AIO_IOB2_BI2X_TDJ__SetWatchDogTimer, &AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_orig);
detour::trampoline_try(libaioIob2VideoDll, "?ControlCoinBlocker@AIO_IOB2_BI2X_TDJ@@QEAAXI_N@Z",
AIO_IOB2_BI2X_TDJ__ControlCoinBlocker, &AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_orig);
detour::trampoline_try(libaioIob2VideoDll, "?AddCounter@AIO_IOB2_BI2X_TDJ@@QEAAXII@Z",
AIO_IOB2_BI2X_TDJ__AddCounter, &AIO_IOB2_BI2X_TDJ__AddCounter_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetStartLamp@AIO_IOB2_BI2X_TDJ@@QEAAXI_N@Z",
AIO_IOB2_BI2X_TDJ__SetStartLamp, &AIO_IOB2_BI2X_TDJ__SetStartLamp_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetVefxButtonLamp@AIO_IOB2_BI2X_TDJ@@QEAAX_N@Z",
AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp, &AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetEffectButtonLamp@AIO_IOB2_BI2X_TDJ@@QEAAX_N@Z",
AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp, &AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetPlayerButtonLamp@AIO_IOB2_BI2X_TDJ@@QEAAXII_N@Z",
AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp, &AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetWooferLed@AIO_IOB2_BI2X_TDJ@@QEAAXI@Z",
AIO_IOB2_BI2X_TDJ__SetWooferLED, &AIO_IOB2_BI2X_TDJ__SetWooferLED_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetIccrLed@AIO_IOB2_BI2X_TDJ@@QEAAXII@Z",
AIO_IOB2_BI2X_TDJ__SetIccrLed, &AIO_IOB2_BI2X_TDJ__SetIccrLed_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetTurnTableLed@AIO_IOB2_BI2X_TDJ@@QEAAXII@Z",
AIO_IOB2_BI2X_TDJ__SetTurnTableLed, &AIO_IOB2_BI2X_TDJ__SetTurnTableLed_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetTurnTableResist@AIO_IOB2_BI2X_TDJ@@QEAAXIE@Z",
AIO_IOB2_BI2X_TDJ__SetTurnTableResist, &AIO_IOB2_BI2X_TDJ__SetTurnTableResist_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetTapeLedData@AIO_IOB2_BI2X_TDJ@@QEAAXIPEBX@Z",
AIO_IOB2_BI2X_TDJ__SetTapeLedData, &AIO_IOB2_BI2X_TDJ__SetTapeLedData_orig);
// hook IOB
const auto libaioIobDll = "libaio-iob.dll";
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
aioIob2Bi2X_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
}
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
namespace games::iidx {
void bi2x_hook_init();
}
+277
View File
@@ -0,0 +1,277 @@
#include "ezusb.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "iidx.h"
#include "io.h"
bool games::iidx::EZUSBHandle::open(LPCWSTR lpFileName) {
return wcscmp(lpFileName, L"\\\\.\\Ezusb-0") == 0;
}
int games::iidx::EZUSBHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
log_warning("iidx", "EZUSB invalid read operation!");
return -1;
}
int games::iidx::EZUSBHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
log_warning("iidx", "EZUSB invalid write operation!");
return -1;
}
int games::iidx::EZUSBHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
// initialize check
if (!init) {
// TODO: initialize input
init = true;
init_success = true;
}
if (init_success) {
switch (dwIoControlCode) {
// device descriptor
case 0x222004:
// check output buffer size
if (nOutBufferSize >= 18) {
// set descriptor data
*((char *) lpOutBuffer + 8) = 0x47;
*((char *) lpOutBuffer + 9) = 0x05;
*((char *) lpOutBuffer + 10) = 0x35;
*((char *) lpOutBuffer + 11) = 0x22;
// return output buffer size
return 18;
} else // buffer too small
return -1;
// vendor
case 0x222014:
// check input buffer size
if (nInBufferSize >= 10)
return 0;
else
return -1;
// data read
case 0x22204E:
// check input buffer size
if (nInBufferSize < 4)
return -1;
// read 0x01
if (*(DWORD *) lpInBuffer == 0x01) {
*((DWORD *) lpOutBuffer) = get_pad();
*((char *) lpOutBuffer + 4) = FPGA_CMD_STATE;
*((char *) lpOutBuffer + 7) = get_tt(1, false);
*((char *) lpOutBuffer + 8) = get_tt(0, false);
*((char *) lpOutBuffer + 9) = FPGA_COUNTER++;
*((char *) lpOutBuffer + 11) = 1; // 1 success; 0 failed
*((char *) lpOutBuffer + 13) = get_slider(1) << 4 | get_slider(0);
*((char *) lpOutBuffer + 14) = get_slider(3) << 4 | get_slider(2);
*((char *) lpOutBuffer + 15) = get_slider(4);
return (int) nOutBufferSize;
}
// read 0x03
if (*(DWORD *) lpInBuffer == 0x03) {
// check output buffer size
if (nOutBufferSize < 64)
return -1;
// null node read
if (FPGA_CUR_NODE == 0) {
*(int *) lpOutBuffer = 0;
return (int) nOutBufferSize;
}
// check node size
if (FPGA_CUR_NODE != 64)
return -1;
// check command
if (SRAM_CMD != 2)
return -1;
// check if page exists
if (SRAM_PAGE >= 12)
return -1;
// write page
*((char *) lpOutBuffer + 1) = (char) SRAM_PAGE;
// copy page
memcpy((uint8_t*) lpOutBuffer + 2, SRAM_DATA + 62 * SRAM_PAGE, 62);
SRAM_PAGE++;
// write node
*((char *) lpOutBuffer) = (char) FPGA_CUR_NODE;
return (int) nOutBufferSize;
}
// unknown
return -1;
// data write
case 0x222051:
// check in buffer size
if (nInBufferSize < 4)
return -1;
// write 0x00
if (*(DWORD *) lpInBuffer == 0x00) {
// check out buffer size
if (nOutBufferSize < 10)
return -1;
// get data
uint16_t lamp = *((uint16_t *) lpOutBuffer + 0);
uint8_t led = *((uint8_t *) lpOutBuffer + 6);
uint8_t top_lamp = *((uint8_t *) lpOutBuffer + 8);
uint8_t top_neon = *((uint8_t *) lpOutBuffer + 9);
// process data
write_lamp(lamp);
write_led(led);
write_top_lamp(top_lamp);
write_top_neon(top_neon);
// flush device output
RI_MGR->devices_flush_output();
char write_node = *((char *) lpOutBuffer + 2);
if (write_node != 0) {
switch (write_node) {
case 4:
switch (*((char *) lpOutBuffer + 3)) {
case 1: // init
FPGA_CMD_STATE = 65;
break;
case 2: // check
FPGA_CMD_STATE = 66;
break;
case 3: // write
{
//int write_data = *((short *) lpOutBuffer + 4);
break;
}
case 4: // write done
FPGA_CMD_STATE = 67;
break;
default: // unknown
return -1;
}
break;
case 5:
break;
case 9:
break;
case 64: // SRAM command
SRAM_CMD = *((char *) lpOutBuffer + 3);
switch (SRAM_CMD) {
case 2: // read
SRAM_PAGE = 0;
break;
case 3: // write
SRAM_PAGE = 0;
break;
case 4: // done
break;
default: // unknown
return -1;
}
break;
default: // unknown node
return -1;
}
}
// save node
FPGA_CUR_NODE = write_node;
// return out buffer size
return (int) nOutBufferSize;
}
// write 0x02
if (*(DWORD *) lpInBuffer == 0x02) {
// check out buffer size
if (nOutBufferSize < 64)
return -1;
int cmd = *(char *) lpOutBuffer;
switch (cmd) {
case 0: // null write
break;
case 4: // unknown
break;
case 5: // 16seg
{
// write to status
IIDX_LED_TICKER_LOCK.lock();
if (!IIDXIO_LED_TICKER_READONLY)
memcpy(IIDXIO_LED_TICKER, (char *) lpOutBuffer + 2, 9);
IIDX_LED_TICKER_LOCK.unlock();
break;
}
case 64: // SRAM write
{
// get page
static char page = *((char *) lpOutBuffer + 1);
page = (char) SRAM_PAGE++;
// check page
if (page >= 12)
return -1;
// copy data
memcpy(SRAM_DATA + 62 * page, (uint8_t*) lpOutBuffer + 2, 62);
break;
}
default: // unknown node
return -1;
}
// return out buffer size
return (int) nOutBufferSize;
}
// unknown write
return -1;
// firmware upload
case 0x22206D:
// check buffer size
if (nInBufferSize >= 2)
return (int) nOutBufferSize;
else
return -1;
// unknown control code
default:
log_warning("iidx", "EZUSB unknown: {}", dwIoControlCode);
return -1;
}
} else
return -1;
}
bool games::iidx::EZUSBHandle::close() {
return true;
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "hooks/devicehook.h"
namespace games::iidx {
class EZUSBHandle : public CustomHandle {
private:
// state
bool init = false;
bool init_success = false;
char FPGA_CMD_STATE = 67;
char FPGA_COUNTER = 0;
int FPGA_CUR_NODE = 0;
int SRAM_CMD = 0;
int SRAM_PAGE = 0;
char SRAM_DATA[62 * 12]{};
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}
+796
View File
@@ -0,0 +1,796 @@
// set version to Windows 7 to enable Media Foundation functions
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0601
// turn on C-style COM objects
#define CINTERFACE
#include "iidx.h"
#include <mfobjects.h>
#include <mfidl.h>
#include "acioemu/handle.h"
#include "avs/core.h"
#include "avs/game.h"
#include "games/io.h"
#include "hooks/avshook.h"
#include "hooks/cfgmgr32hook.h"
#include "hooks/devicehook.h"
#include "hooks/graphics/graphics.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "launcher/options.h"
#include "misc/wintouchemu.h"
#include "misc/eamuse.h"
#include "util/detour.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/memutils.h"
#include "util/utils.h"
#include "bi2a.h"
#include "bi2x_hook.h"
#include "ezusb.h"
#include "io.h"
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
static decltype(RegOpenKeyA) *RegOpenKeyA_orig = nullptr;
static decltype(RegOpenKeyExA) *RegOpenKeyExA_orig = nullptr;
static decltype(RegQueryValueExA) *RegQueryValueExA_orig = nullptr;
namespace games::iidx {
// constants
const HKEY PARENT_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x3001);
const HKEY DEVICE_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x3002);
const char *ORIGINAL_ASIO_DEVICE_NAME = "XONAR SOUND CARD(64)";
// settings
bool FLIP_CAMS = false;
bool DISABLE_CAMS = false;
bool TDJ_MODE = false;
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
std::optional<std::string> ASIO_DRIVER = std::nullopt;
// states
static HKEY real_asio_reg_handle = nullptr;
static HKEY real_asio_device_reg_handle = nullptr;
static uint16_t IIDXIO_TT_STATE[2]{};
static uint16_t IIDXIO_TT_DIRECTION[2]{};
static bool IIDXIO_TT_PRESSED[2]{};
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
bool IIDXIO_LED_TICKER_READONLY = false;
std::mutex IIDX_LED_TICKER_LOCK;
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
if (lpSubKey != nullptr && phkResult != nullptr) {
if (hKey == HKEY_LOCAL_MACHINE &&
ASIO_DRIVER.has_value() &&
_stricmp(lpSubKey, "software\\asio") == 0)
{
*phkResult = PARENT_ASIO_REG_HANDLE;
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
}
}
return RegOpenKeyA_orig(hKey, lpSubKey, phkResult);
}
static LONG WINAPI RegOpenKeyExA_hook(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
PHKEY phkResult)
{
if (lpSubKey != nullptr && phkResult != nullptr) {
if (hKey == PARENT_ASIO_REG_HANDLE &&
ASIO_DRIVER.has_value() &&
_stricmp(lpSubKey, ORIGINAL_ASIO_DEVICE_NAME) == 0)
{
*phkResult = DEVICE_ASIO_REG_HANDLE;
log_info("iidx::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
return RegOpenKeyExA_orig(real_asio_reg_handle, ASIO_DRIVER.value().c_str(), ulOptions, samDesired,
&real_asio_device_reg_handle);
}
}
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
}
static LONG WINAPI RegEnumKeyA_hook(HKEY hKey, DWORD dwIndex, LPSTR lpName, DWORD cchName) {
if (hKey == PARENT_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
if (dwIndex == 0) {
auto ret = RegEnumKeyA_orig(real_asio_reg_handle, dwIndex, lpName, cchName);
if (ret == ERROR_SUCCESS && lpName != nullptr) {
log_info("iidx::asio", "stubbing '{}' with '{}'", lpName, ORIGINAL_ASIO_DEVICE_NAME);
strncpy(lpName, ORIGINAL_ASIO_DEVICE_NAME, cchName);
}
return ret;
} else {
return ERROR_NO_MORE_ITEMS;
}
}
return RegEnumKeyA_orig(hKey, dwIndex, lpName, cchName);
}
/*
* Dirty Workaround for IO Device
* Game gets registry object via SetupDiOpenDevRegKey, then looks up "PortName" via RegQueryValueExA
* We ignore the first and just cheat on the second.
*/
static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCTSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
LPBYTE lpData, LPDWORD lpcbData)
{
if (lpValueName != nullptr && lpData != nullptr && lpcbData != nullptr) {
if (hKey == DEVICE_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
log_info("iidx::asio", "RegQueryValueExA({}, \"{}\")", fmt::ptr((void *) hKey), lpValueName);
if (_stricmp(lpValueName, "Description") == 0) {
memcpy(lpData, ASIO_DRIVER.value().c_str(), ASIO_DRIVER.value().size() + 1);
return ERROR_SUCCESS;
} else {
hKey = real_asio_device_reg_handle;
}
}
// check for port name lookup
if (_stricmp(lpValueName, "PortName") == 0) {
const char port[] = "COM2";
memcpy(lpData, port, sizeof(port));
return ERROR_SUCCESS;
}
}
// fallback
return RegQueryValueExA_orig(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
}
static LONG WINAPI RegCloseKey_hook(HKEY hKey) {
if (hKey == PARENT_ASIO_REG_HANDLE || hKey == DEVICE_ASIO_REG_HANDLE) {
return ERROR_SUCCESS;
}
return RegCloseKey_orig(hKey);
}
/*
* Camera related stuff
*/
static std::wstring CAMERA0_ID;
static std::wstring CAMERA1_ID;
static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue,
UINT32 *pcchLength)
{
// call the original
HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength);
// log the cam name
log_misc("iidx::cam", "obtained {}", ws2s(*ppwszValue));
// try first camera
wchar_t *pwc = nullptr;
if (CAMERA0_ID.length() == 23) {
pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str());
}
// try second camera if first wasn't found
if (!pwc && CAMERA1_ID.length() == 23) {
pwc = wcsstr(*ppwszValue, CAMERA1_ID.c_str());
}
// check if camera could be identified
if (pwc) {
// fake the USB IDs
pwc[4] = L'2';
pwc[5] = L'8';
pwc[6] = L'8';
pwc[7] = L'c';
pwc[13] = L'0';
pwc[14] = L'0';
pwc[15] = L'0';
pwc[16] = L'2';
pwc[21] = L'0';
pwc[22] = L'0';
log_misc("iidx::cam", "replaced {}", ws2s(*ppwszValue));
}
// return original result
return result;
}
static void hook_camera_vtable(IMFActivate *camera) {
// hook allocated string method for camera identification
memutils::VProtectGuard camera_guard(camera->lpVtbl);
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
}
static void hook_camera(size_t no, std::wstring camera_id, std::string camera_instance) {
// logic based on camera no
if (no == 0) {
// don't hook if camera 0 is already hooked
if (CAMERA0_ID.length() > 0) {
return;
}
// save the camera ID
CAMERA0_ID = camera_id;
// cfgmgr hook
CFGMGR32_HOOK_SETTING camera_setting;
camera_setting.device_instance = 0xDEADBEEF;
camera_setting.parent_instance = ~camera_setting.device_instance;
camera_setting.device_id = "USB\\VEN_1022&DEV_7908";
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
if (camera_instance.length() == 17) {
for (int i = 0; i < 17; i++) {
camera_setting.device_node_id[28 + i] = camera_instance[i];
}
}
cfgmgr32hook_add(camera_setting);
log_info("iidx::cam", "hooked camera 1 @ {}", ws2s(camera_id));
}
else if (no == 1) {
// don't hook if camera 1 is already hooked
if (CAMERA1_ID.length() > 0) {
return;
}
// save the camera ID
CAMERA1_ID = camera_id;
// cfgmgr hook
CFGMGR32_HOOK_SETTING camera_setting;
camera_setting.device_instance = 0xBEEFDEAD;
camera_setting.parent_instance = ~camera_setting.device_instance;
camera_setting.device_id = "USB\\VEN_1022&DEV_7914";
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
if (camera_instance.length() == 17) {
for (int i = 0; i < 17; i++) {
camera_setting.device_node_id[28 + i] = camera_instance[i];
}
}
cfgmgr32hook_add(camera_setting);
log_info("iidx::cam", "hooked camera 2 @ {}", ws2s(camera_id));
}
}
static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate,
UINT32 *pcSourceActivate) {
// call original function
HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate);
// check for capture devices
if (FAILED(result_orig) || !*pcSourceActivate) {
return result_orig;
}
// iterate cameras
size_t cam_hook_num = 0;
for (size_t cam_num = 0; cam_num < *pcSourceActivate && cam_hook_num < 2; cam_num++) {
// flip
size_t cam_num_flipped = cam_num;
if (FLIP_CAMS) {
cam_num_flipped = *pcSourceActivate - cam_num - 1;
}
// get camera
IMFActivate *camera = (*pppSourceActivate)[cam_num_flipped];
// save original method for later use
if (GetAllocatedString_orig == nullptr) {
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
}
// hook allocated string method for camera identification
hook_camera_vtable(camera);
// get camera link
LPWSTR camera_link_lpwstr;
UINT32 camera_link_length;
if (SUCCEEDED(GetAllocatedString_orig(
camera,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&camera_link_lpwstr,
&camera_link_length))) {
// cut name to make ID
std::wstring camera_link_ws = std::wstring(camera_link_lpwstr);
std::wstring camera_id = camera_link_ws.substr(8, 23);
log_info("iidx::cam", "found video capture device: {}", ws2s(camera_link_ws));
// only support cameras that start with \\?\usb
if (!string_begins_with(camera_link_ws, L"\\\\?\\usb")) {
continue;
}
// get camera instance
std::string camera_link = ws2s(camera_link_ws);
std::string camera_instance = camera_link.substr(32, 17);
// hook the camera
hook_camera(cam_hook_num, camera_id, camera_instance);
// increase camera hook number
cam_hook_num++;
} else {
log_warning("iidx::cam", "failed to open camera {}", cam_num_flipped);
}
}
// return result
return result_orig;
}
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
// get rid of the log spam
if (data.find(" I:graphic: adapter mode ") != std::string::npos) {
out.clear();
return true;
} else {
return false;
}
}
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
static void* aioIob2Bi2x_CreateWriteFirmContext_hook(unsigned int a1, int a2) {
if (aioIob2Bi2x_CreateWriteFirmContext_orig) {
auto options = games::get_options(eamuse_get_game());
if (options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
log_info("iidx", "CreateWriteFirmContext({}, {} -> 2)", a1, a2);
return aioIob2Bi2x_CreateWriteFirmContext_orig(a1, 2);
} else {
log_info("iidx", "CreateWriteFirmContext({}, {})", a1, a2);
return aioIob2Bi2x_CreateWriteFirmContext_orig(a1, a2);
}
}
log_warning("iidx", "CreateWriteFirmContext == nullptr");
return nullptr;
}
IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
logger::hook_add(log_hook, this);
}
IIDXGame::~IIDXGame() {
logger::hook_remove(log_hook, this);
}
void IIDXGame::attach() {
Game::attach();
// IO boards
auto options = games::get_options(eamuse_get_game());
if (!options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
// reduce boot wait time
hooks::sleep::init(1000, 1);
// add old IO board
SETUPAPI_SETTINGS settings1 {};
settings1.class_guid[0] = 0xAE18AA60;
settings1.class_guid[1] = 0x11D47F6A;
settings1.class_guid[2] = 0x0100DD97;
settings1.class_guid[3] = 0x59B92902;
const char property1[] = "Cypress EZ-USB (2235 - EEPROM missing)";
const char interface_detail1[] = "\\\\.\\Ezusb-0";
memcpy(settings1.property_devicedesc, property1, sizeof(property1));
memcpy(settings1.interface_detail, interface_detail1, sizeof(interface_detail1));
setupapihook_init(avs::game::DLL_INSTANCE);
setupapihook_add(settings1);
// IIDX <25 with EZUSB input device
devicehook_init();
devicehook_add(new EZUSBHandle());
// add new BIO2 I/O board
SETUPAPI_SETTINGS settings2 {};
settings2.class_guid[0] = 0x4D36E978;
settings2.class_guid[1] = 0x11CEE325;
settings2.class_guid[2] = 0x0008C1BF;
settings2.class_guid[3] = 0x1803E12B;
const char property2[] = "BIO2(VIDEO)";
const char interface_detail2[] = "COM2";
memcpy(settings2.property_devicedesc, property2, sizeof(property2));
memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2));
setupapihook_add(settings2);
// IIDX 25-27 BIO2 BI2A input device
devicehook_add(new IIDXFMSerialHandle());
}
// check for new I/O DLL
auto aio = libutils::try_library("libaio.dll");
if (aio != nullptr) {
// check TDJ mode
TDJ_MODE |= fileutils::text_read("D:\\001rom.txt") == "TDJ";
// force TDJ mode
if (TDJ_MODE) {
// ensure game starts in the desired mode
hooks::avs::set_rom("/d_drv/001rom.txt", "TDJ");
// need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook
devicehook_init(avs::core::DLL_INSTANCE);
// hook touch window
wintouchemu::FORCE = true;
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
// prevent crash on TDJ mode without correct DLL
if (!GetModuleHandle("nvcuda.dll")) {
DISABLE_CAMS = true;
}
}
// insert BI2X hooks
bi2x_hook_init();
// add card readers
devicehook_init(aio);
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
// firmware upgrade hook
if (options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
aioIob2Bi2x_CreateWriteFirmContext_orig = detour::iat(
"aioIob2Bi2x_CreateWriteFirmContext",
aioIob2Bi2x_CreateWriteFirmContext_hook);
//devicehook_add(new MITMHandle(L"#vid_1ccf&pid_8050", "", true));
} else {
/*
// add the BIO2 I/O board (with different firmware)
SETUPAPI_SETTINGS settings {};
settings.class_guid[0] = 0x0;
settings.class_guid[1] = 0x0;
settings.class_guid[2] = 0x0;
settings.class_guid[3] = 0x0;
const char property[] = "1CCF(8050)_000";
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8050&MI_00\\000";
const char interface_detail[] = "COM3";
memcpy(settings.property_devicedesc, property, sizeof(property));
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
memcpy(settings.interface_detail, interface_detail, sizeof(interface_detail));
setupapihook_init(aio);
setupapihook_add(settings);
// IIDX 27 BIO2 BI2X input devce
devicehook_add(new BI2XSerialHandle());
*/
}
}
// ASIO device hook
RegCloseKey_orig = detour::iat_try(
"RegCloseKey", RegCloseKey_hook, avs::game::DLL_INSTANCE);
RegEnumKeyA_orig = detour::iat_try(
"RegEnumKeyA", RegEnumKeyA_hook, avs::game::DLL_INSTANCE);
RegOpenKeyA_orig = detour::iat_try(
"RegOpenKeyA", RegOpenKeyA_hook, avs::game::DLL_INSTANCE);
RegOpenKeyExA_orig = detour::iat_try(
"RegOpenKeyExA", RegOpenKeyExA_hook, avs::game::DLL_INSTANCE);
// IO device workaround
RegQueryValueExA_orig = detour::iat_try(
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
// check if cam hook should be enabled
if (!DISABLE_CAMS) {
// camera media framework hook
MFEnumDeviceSources_orig = detour::iat_try(
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
// camera settings
SETUPAPI_SETTINGS settings3 {};
settings3.class_guid[0] = 0x00000000;
settings3.class_guid[1] = 0x00000000;
settings3.class_guid[2] = 0x00000000;
settings3.class_guid[3] = 0x00000000;
const char property3[] = "USB Composite Device";
memcpy(settings3.property_devicedesc, property3, sizeof(property3));
settings3.property_address[0] = 1;
settings3.property_address[1] = 7;
setupapihook_add(settings3);
}
// init cfgmgr32 hooks
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
}
void IIDXGame::pre_attach() {
Game::pre_attach();
// environment variables must be set before the DLL is loaded as the VC++ runtime copies all
// environment variables at startup
if (DISABLE_CAMS) {
SetEnvironmentVariable("CONNECT_CAMERA", "0");
}
if (SOUND_OUTPUT_DEVICE.has_value()) {
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", SOUND_OUTPUT_DEVICE.value().c_str());
}
}
void IIDXGame::detach() {
Game::detach();
devicehook_dispose();
}
uint32_t get_pad() {
uint32_t pad = 0;
// get buttons
auto &buttons = get_buttons();
// player 1 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_1)))
pad |= 1 << 0x08;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_2)))
pad |= 1 << 0x09;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_3)))
pad |= 1 << 0x0A;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_4)))
pad |= 1 << 0x0B;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_5)))
pad |= 1 << 0x0C;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_6)))
pad |= 1 << 0x0D;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_7)))
pad |= 1 << 0x0E;
// player 2 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_1)))
pad |= 1 << 0x0F;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_2)))
pad |= 1 << 0x10;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_3)))
pad |= 1 << 0x11;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_4)))
pad |= 1 << 0x12;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_5)))
pad |= 1 << 0x13;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_6)))
pad |= 1 << 0x14;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_7)))
pad |= 1 << 0x15;
// player 1 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_Start)))
pad |= 1 << 0x18;
// player 2 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_Start)))
pad |= 1 << 0x19;
// VEFX
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::VEFX)))
pad |= 1 << 0x1A;
// EFFECT
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Effect)))
pad |= 1 << 0x1B;
// test
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test)))
pad |= 1 << 0x1C;
// service
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service)))
pad |= 1 << 0x1D;
return ~(pad & 0xFFFFFF00);
}
void write_lamp(uint16_t lamp) {
// mapping
static const size_t mapping[] = {
Lights::P1_1,
Lights::P1_2,
Lights::P1_3,
Lights::P1_4,
Lights::P1_5,
Lights::P1_6,
Lights::P1_7,
Lights::P2_1,
Lights::P2_2,
Lights::P2_3,
Lights::P2_4,
Lights::P2_5,
Lights::P2_6,
Lights::P2_7,
};
// get lights
auto &lights = get_lights();
// bit scan
for (int i = 0; i < 14; i++) {
float value = (lamp & (1 << i)) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
void write_led(uint8_t led) {
// mapping
static const size_t mapping[] = {
Lights::P1_Start,
Lights::P2_Start,
Lights::VEFX,
Lights::Effect,
};
// get lights
auto &lights = get_lights();
// bit scan
for (int i = 0; i < 4; i++) {
auto value = (led & (1 << i)) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
void write_top_lamp(uint8_t top_lamp) {
// mapping
static const size_t mapping[] = {
Lights::SpotLight1,
Lights::SpotLight2,
Lights::SpotLight3,
Lights::SpotLight4,
Lights::SpotLight5,
Lights::SpotLight6,
Lights::SpotLight7,
Lights::SpotLight8,
};
// get lights
auto &lights = get_lights();
// bit scan
for (int i = 0; i < 8; i++) {
auto value = (top_lamp & (1 << i)) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
void write_top_neon(uint8_t top_neon) {
// get lights
auto &lights = get_lights();
// write value
auto value = top_neon > 0 ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::NeonLamp), value);
}
unsigned char get_tt(int player, bool slow) {
// check change value for high/low precision
uint16_t change = slow ? (uint16_t) 1 : (uint16_t) 4;
// check player number
if (player > 1)
return 0;
// get buttons
auto &buttons = get_buttons();
bool ttp = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlus : Buttons::P1_TTPlus));
bool ttm = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTMinus : Buttons::P1_TTMinus));
bool ttpm = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlusMinus : Buttons::P1_TTPlusMinus));
// TT+
if (ttp)
IIDXIO_TT_STATE[player] += change;
// TT-
if (ttm)
IIDXIO_TT_STATE[player] -= change;
// TT+/-
if (ttpm) {
if (IIDXIO_TT_DIRECTION[player] != 0u) {
if (!IIDXIO_TT_PRESSED[player])
IIDXIO_TT_DIRECTION[player] = 0;
IIDXIO_TT_STATE[player] += change;
} else {
if (!IIDXIO_TT_PRESSED[player])
IIDXIO_TT_DIRECTION[player] = 1;
IIDXIO_TT_STATE[player] -= change;
}
IIDXIO_TT_PRESSED[player] = true;
} else
IIDXIO_TT_PRESSED[player] = false;
// raw input
auto &analogs = get_analogs();
auto &analog = analogs[player != 0 ? Analogs::TT_P2 : Analogs::TT_P1];
auto ret_value = IIDXIO_TT_STATE[player];
if (analog.isSet()) {
ret_value = IIDXIO_TT_STATE[player];
ret_value += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analog) * 1023.999f);
}
// return higher 8 bit
return (uint8_t) (ret_value >> 2);
}
unsigned char get_slider(uint8_t slider) {
// check slide
if (slider > 4)
return 0;
// get analog
auto &analogs = get_analogs();
Analog *analog = nullptr;
switch (slider) {
case 0:
analog = &analogs.at(Analogs::VEFX);
break;
case 1:
analog = &analogs.at(Analogs::LowEQ);
break;
case 2:
analog = &analogs.at(Analogs::HiEQ);
break;
case 3:
analog = &analogs.at(Analogs::Filter);
break;
case 4:
analog = &analogs.at(Analogs::PlayVolume);
break;
default:
break;
}
// if not set return max value
if (!analog || !analog->isSet()) {
return 0xF;
}
// return slide
return (unsigned char) (GameAPI::Analogs::getState(RI_MGR, *analog) * 15.999f);
}
const char* get_16seg() {
return IIDXIO_LED_TICKER;
}
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <mutex>
#include <optional>
#include "games/game.h"
namespace games::iidx {
// settings
extern bool FLIP_CAMS;
extern bool DISABLE_CAMS;
extern bool TDJ_MODE;
extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
extern std::optional<std::string> ASIO_DRIVER;
// state
extern char IIDXIO_LED_TICKER[10];
extern bool IIDXIO_LED_TICKER_READONLY;
extern std::mutex IIDX_LED_TICKER_LOCK;
class IIDXGame : public games::Game {
public:
IIDXGame();
virtual ~IIDXGame() override;
virtual void attach() override;
virtual void pre_attach() override;
virtual void detach() override;
};
// helper methods
uint32_t get_pad();
void write_lamp(uint16_t lamp);
void write_led(uint8_t led);
void write_top_lamp(uint8_t top_lamp);
void write_top_neon(uint8_t top_neon);
unsigned char get_tt(int player, bool slow);
unsigned char get_slider(uint8_t slider);
const char* get_16seg();
}
+173
View File
@@ -0,0 +1,173 @@
#include "io.h"
std::vector<Button> &games::iidx::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Beatmania IIDX");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"P1 1",
"P1 2",
"P1 3",
"P1 4",
"P1 5",
"P1 6",
"P1 7",
"P1 TT+",
"P1 TT-",
"P1 TT+/-",
"P1 Start",
"P2 1",
"P2 2",
"P2 3",
"P2 4",
"P2 5",
"P2 6",
"P2 7",
"P2 TT+",
"P2 TT-",
"P2 TT+/-",
"P2 Start",
"EFFECT",
"VEFX",
"P1 Headphone",
"P2 Headphone"
);
}
return buttons;
}
std::vector<Analog> &games::iidx::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Turntable P1",
"Turntable P2",
"VEFX",
"Low-EQ",
"Hi-EQ",
"Filter",
"Play Volume"
);
}
return analogs;
}
std::vector<Light> &games::iidx::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Beatmania IIDX");
GameAPI::Lights::sortLights(
&lights,
"P1 1",
"P1 2",
"P1 3",
"P1 4",
"P1 5",
"P1 6",
"P1 7",
"P2 1",
"P2 2",
"P2 3",
"P2 4",
"P2 5",
"P2 6",
"P2 7",
"P1 Start",
"P2 Start",
"VEFX",
"Effect",
"Spot Light 1",
"Spot Light 2",
"Spot Light 3",
"Spot Light 4",
"Spot Light 5",
"Spot Light 6",
"Spot Light 7",
"Spot Light 8",
"Neon Lamp",
"Woofer R",
"Woofer G",
"Woofer B",
"IC Card Reader P1 R",
"IC Card Reader P1 G",
"IC Card Reader P1 B",
"IC Card Reader P2 R",
"IC Card Reader P2 G",
"IC Card Reader P2 B",
"Turntable P1 R",
"Turntable P1 G",
"Turntable P1 B",
"Turntable P2 R",
"Turntable P2 G",
"Turntable P2 B",
"Turntable P1 Resistance",
"Turntable P2 Resistance",
"Stage Left Avg R",
"Stage Left Avg G",
"Stage Left Avg B",
"Stage Right Avg R",
"Stage Right Avg G",
"Stage Right Avg B",
"Cabinet Left Avg R",
"Cabinet Left Avg G",
"Cabinet Left Avg B",
"Cabinet Right Avg R",
"Cabinet Right Avg G",
"Cabinet Right Avg B",
"Control Panel Under Avg R",
"Control Panel Under Avg G",
"Control Panel Under Avg B",
"Ceiling Left Avg R",
"Ceiling Left Avg G",
"Ceiling Left Avg B",
"Title Left Avg R",
"Title Left Avg G",
"Title Left Avg B",
"Title Right Avg R",
"Title Right Avg G",
"Title Right Avg B",
"Ceiling Right Avg R",
"Ceiling Right Avg G",
"Ceiling Right Avg B",
"Touch Panel Left Avg R",
"Touch Panel Left Avg G",
"Touch Panel Left Avg B",
"Touch Panel Right Avg R",
"Touch Panel Right Avg G",
"Touch Panel Right Avg B",
"Side Panel Left Inner Avg R",
"Side Panel Left Inner Avg G",
"Side Panel Left Inner Avg B",
"Side Panel Left Outer Avg R",
"Side Panel Left Outer Avg G",
"Side Panel Left Outer Avg B",
"Side Panel Left Avg R",
"Side Panel Left Avg G",
"Side Panel Left Avg B",
"Side Panel Right Outer Avg R",
"Side Panel Right Outer Avg G",
"Side Panel Right Outer Avg B",
"Side Panel Right Inner Avg R",
"Side Panel Right Inner Avg G",
"Side Panel Right Inner Avg B",
"Side Panel Right Avg R",
"Side Panel Right Avg G",
"Side Panel Right Avg B"
);
}
return lights;
}
+161
View File
@@ -0,0 +1,161 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::iidx {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
CoinMech,
P1_1,
P1_2,
P1_3,
P1_4,
P1_5,
P1_6,
P1_7,
P1_TTPlus,
P1_TTMinus,
P1_TTPlusMinus,
P1_Start,
P2_1,
P2_2,
P2_3,
P2_4,
P2_5,
P2_6,
P2_7,
P2_TTPlus,
P2_TTMinus,
P2_TTPlusMinus,
P2_Start,
Effect,
VEFX,
P1_Headphone,
P2_Headphone,
};
}
// all analogs in correct order
namespace Analogs {
enum {
TT_P1,
TT_P2,
VEFX,
LowEQ,
HiEQ,
Filter,
PlayVolume
};
}
// all lights in correct order
namespace Lights {
enum {
P1_1,
P1_2,
P1_3,
P1_4,
P1_5,
P1_6,
P1_7,
P2_1,
P2_2,
P2_3,
P2_4,
P2_5,
P2_6,
P2_7,
P1_Start,
P2_Start,
VEFX,
Effect,
SpotLight1,
SpotLight2,
SpotLight3,
SpotLight4,
SpotLight5,
SpotLight6,
SpotLight7,
SpotLight8,
NeonLamp,
WooferR,
WooferG,
WooferB,
ICCR_P1_R,
ICCR_P1_G,
ICCR_P1_B,
ICCR_P2_R,
ICCR_P2_G,
ICCR_P2_B,
TT_P1_R,
TT_P1_G,
TT_P1_B,
TT_P2_R,
TT_P2_G,
TT_P2_B,
TT_P1_Resistance,
TT_P2_Resistance,
StageLeftAvgR,
StageLeftAvgG,
StageLeftAvgB,
StageRightAvgR,
StageRightAvgG,
StageRightAvgB,
CabinetLeftAvgR,
CabinetLeftAvgG,
CabinetLeftAvgB,
CabinetRightAvgR,
CabinetRightAvgG,
CabinetRightAvgB,
ControlPanelUnderAvgR,
ControlPanelUnderAvgG,
ControlPanelUnderAvgB,
CeilingLeftAvgR,
CeilingLeftAvgG,
CeilingLeftAvgB,
TitleLeftAvgR,
TitleLeftAvgG,
TitleLeftAvgB,
TitleRightAvgR,
TitleRightAvgG,
TitleRightAvgB,
CeilingRightAvgR,
CeilingRightAvgG,
CeilingRightAvgB,
TouchPanelLeftAvgR,
TouchPanelLeftAvgG,
TouchPanelLeftAvgB,
TouchPanelRightAvgR,
TouchPanelRightAvgG,
TouchPanelRightAvgB,
SidePanelLeftInnerAvgR,
SidePanelLeftInnerAvgG,
SidePanelLeftInnerAvgB,
SidePanelLeftOuterAvgR,
SidePanelLeftOuterAvgG,
SidePanelLeftOuterAvgB,
SidePanelLeftAvgR,
SidePanelLeftAvgG,
SidePanelLeftAvgB,
SidePanelRightOuterAvgR,
SidePanelRightOuterAvgG,
SidePanelRightOuterAvgB,
SidePanelRightInnerAvgR,
SidePanelRightInnerAvgG,
SidePanelRightInnerAvgB,
SidePanelRightAvgR,
SidePanelRightAvgG,
SidePanelRightAvgB,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}