Let us start here, from square one. No, from Zero!
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::iidx {
|
||||
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user