Initial re-upload of spice2x-24-08-24
This commit is contained in:
413
games/sdvx/bi2x_hook.cpp
Normal file
413
games/sdvx/bi2x_hook.cpp
Normal file
@@ -0,0 +1,413 @@
|
||||
#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 "games/sdvx/sdvx.h"
|
||||
#include "util/tapeled.h"
|
||||
#include "acioemu/icca.h"
|
||||
|
||||
namespace games::sdvx {
|
||||
constexpr bool BI2X_PASSTHROUGH = false;
|
||||
bool BI2X_INITIALIZED = 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_UFC {
|
||||
// who knows
|
||||
uint8_t data[0x13F8];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC__DEVSTATUS {
|
||||
// of course you could work with variables here
|
||||
uint8_t buffer[0x19E];
|
||||
};
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
typedef AIO_IOB2_BI2X_UFC* (__fastcall *aioIob2Bi2xUFC_Create_t)(AIO_NMGR_IOB2 *nmgr, int a2);
|
||||
typedef void (__fastcall *AIO_IOB2_BI2X_UFC__GetDeviceStatus_t)(AIO_IOB2_BI2X_UFC *This,
|
||||
AIO_IOB2_BI2X_UFC__DEVSTATUS *a2);
|
||||
typedef void (__fastcall *AIO_IOB2_BI2X_UFC__IoReset_t)(AIO_IOB2_BI2X_UFC *This,
|
||||
unsigned int a2);
|
||||
typedef void (__fastcall *AIO_IOB2_BI2X_UFC__SetWatchDogTimer_t)(AIO_IOB2_BI2X_UFC *This, uint8_t a2);
|
||||
typedef void (__fastcall *AIO_IOB2_BI2X_UFC__ControlCoinBlocker_t)(AIO_IOB2_BI2X_UFC *This,
|
||||
uint64_t index, uint8_t state);
|
||||
typedef void (__fastcall *AIO_IOB2_BI2X_UFC__AddCounter_t)(AIO_IOB2_BI2X_UFC *This,
|
||||
unsigned int a2, unsigned int a3);
|
||||
typedef void (__fastcall *AIO_IOB2_BI2X_UFC__SetIccrLed_t)(AIO_IOB2_BI2X_UFC *This, uint32_t color);
|
||||
typedef void (__fastcall *AIO_IOB2_BI2X_UFC__SetPlayerButtonLamp_t)(AIO_IOB2_BI2X_UFC *This,
|
||||
int button, uint8_t state);
|
||||
typedef void (__fastcall *AIO_IOB2_BI2X_UFC__SetTapeLedData_t)(AIO_IOB2_BI2X_UFC *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 aioIob2Bi2xUFC_Create_t aioIob2Bi2xUFC_Create_orig = nullptr;
|
||||
static AIO_IOB2_BI2X_UFC__GetDeviceStatus_t AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig = nullptr;
|
||||
static AIO_IOB2_BI2X_UFC__IoReset_t AIO_IOB2_BI2X_UFC__IoReset_orig = nullptr;
|
||||
static AIO_IOB2_BI2X_UFC__SetWatchDogTimer_t AIO_IOB2_BI2X_UFC__SetWatchDogTimer_orig = nullptr;
|
||||
static AIO_IOB2_BI2X_UFC__ControlCoinBlocker_t AIO_IOB2_BI2X_UFC__ControlCoinBlocker_orig = nullptr;
|
||||
static AIO_IOB2_BI2X_UFC__AddCounter_t AIO_IOB2_BI2X_UFC__AddCounter_orig = nullptr;
|
||||
static AIO_IOB2_BI2X_UFC__SetIccrLed_t AIO_IOB2_BI2X_UFC__SetIccrLed_orig = nullptr;
|
||||
static AIO_IOB2_BI2X_UFC__SetPlayerButtonLamp_t AIO_IOB2_BI2X_UFC__SetPlayerButtonLamp_orig = nullptr;
|
||||
static AIO_IOB2_BI2X_UFC__SetTapeLedData_t AIO_IOB2_BI2X_UFC__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_UFC *custom_node = nullptr;
|
||||
AC_HNDLIF *acHndlif = nullptr;
|
||||
AIO_NMGR_IOB2 *aioNmgrIob2 = nullptr;
|
||||
// state
|
||||
static uint8_t count = 0;
|
||||
static uint16_t VOL_L = 0;
|
||||
static uint16_t VOL_R = 0;
|
||||
|
||||
/*
|
||||
* implementations
|
||||
*/
|
||||
|
||||
static AIO_IOB2_BI2X_UFC* __fastcall aioIob2Bi2xUFC_Create(AIO_NMGR_IOB2 *nmgr, int a2) {
|
||||
if (!BI2X_PASSTHROUGH) {
|
||||
custom_node = new AIO_IOB2_BI2X_UFC;
|
||||
memset(&custom_node->data, 0, sizeof(custom_node->data));
|
||||
return custom_node;
|
||||
} else {
|
||||
|
||||
// call original
|
||||
return aioIob2Bi2xUFC_Create_orig(nmgr, a2);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__GetDeviceStatus(AIO_IOB2_BI2X_UFC *This,
|
||||
AIO_IOB2_BI2X_UFC__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_UFC__DEVSTATUS));
|
||||
} else {
|
||||
|
||||
// get data from real device
|
||||
AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status);
|
||||
}
|
||||
|
||||
status->buffer[0] = count;
|
||||
status->buffer[12] = count;
|
||||
count++;
|
||||
|
||||
// get buttons
|
||||
auto &buttons = get_buttons();
|
||||
|
||||
// control buttons
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
|
||||
status->buffer[18] = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
|
||||
status->buffer[19] = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
|
||||
status->buffer[20] = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Start]))
|
||||
status->buffer[316] |= 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_A]))
|
||||
status->buffer[316] |= 0x02;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_B]))
|
||||
status->buffer[316] |= 0x04;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_C]))
|
||||
status->buffer[316] |= 0x08;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_D]))
|
||||
status->buffer[316] |= 0x10;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_L]))
|
||||
status->buffer[316] |= 0x20;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_R]))
|
||||
status->buffer[316] |= 0x40;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]))
|
||||
status->buffer[22] = 0x01;
|
||||
|
||||
// volume left
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left])) {
|
||||
VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right])) {
|
||||
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
|
||||
// volume right
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left])) {
|
||||
VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right])) {
|
||||
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
|
||||
// update volumes
|
||||
auto &analogs = get_analogs();
|
||||
auto vol_left = VOL_L;
|
||||
auto vol_right = VOL_R;
|
||||
if (analogs[0].isSet() || analogs[1].isSet()) {
|
||||
vol_left += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_L]) * 65535);
|
||||
vol_right += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_R]) * 65535);
|
||||
}
|
||||
|
||||
*((uint16_t*) &status->buffer[312]) = vol_left;
|
||||
*((uint16_t*) &status->buffer[314]) = vol_right;
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
|
||||
unsigned int a2)
|
||||
{
|
||||
if (This != custom_node) {
|
||||
return AIO_IOB2_BI2X_UFC__IoReset_orig(This, a2);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__SetWatchDogTimer(AIO_IOB2_BI2X_UFC *This,
|
||||
uint8_t a2)
|
||||
{
|
||||
if (This != custom_node) {
|
||||
|
||||
// comment this out if you want to disable the BI2X watchdog timer
|
||||
return AIO_IOB2_BI2X_UFC__SetWatchDogTimer_orig(This, a2);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__ControlCoinBlocker(AIO_IOB2_BI2X_UFC *This,
|
||||
uint64_t 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_UFC__ControlCoinBlocker_orig(This, index, state);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__AddCounter(AIO_IOB2_BI2X_UFC *This,
|
||||
unsigned int a2, unsigned int a3)
|
||||
{
|
||||
if (This != custom_node) {
|
||||
return AIO_IOB2_BI2X_UFC__AddCounter_orig(This, a2, a3);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__SetIccrLed(AIO_IOB2_BI2X_UFC *This, 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::ICCR_R], col_r / 255.f);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_G], col_g / 255.f);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_B], col_b / 255.f);
|
||||
|
||||
if (This != custom_node) {
|
||||
return AIO_IOB2_BI2X_UFC__SetIccrLed_orig(This, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__SetPlayerButtonLamp(AIO_IOB2_BI2X_UFC *This,
|
||||
int button, uint8_t state)
|
||||
{
|
||||
auto &lights = get_lights();
|
||||
|
||||
/**
|
||||
* button
|
||||
* 0: START, 1: BT_A, 2: BT_B, 3: BT_C, 4: BT_D, 5: FX_L, 6: FX_R
|
||||
*
|
||||
* state
|
||||
* 0: ON, 1: OFF
|
||||
*/
|
||||
if (button == 0) {
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::START], state ? 1.f : 0.f);
|
||||
} else {
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::BT_A + button - 1], state ? 1.f : 0.f);
|
||||
}
|
||||
|
||||
if (This != custom_node) {
|
||||
return AIO_IOB2_BI2X_UFC__SetPlayerButtonLamp_orig(This, button, state);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__SetTapeLedData(AIO_IOB2_BI2X_UFC *This,
|
||||
unsigned int index, uint8_t *data)
|
||||
{
|
||||
/*
|
||||
* index mapping
|
||||
* 0 - title - 222 bytes - 74 colors
|
||||
* 1 - upper left speaker - 36 bytes - 12 colors
|
||||
* 2 - upper right speaker - 36 bytes - 12 colors
|
||||
* 3 - left wing - 168 bytes - 56 colors
|
||||
* 4 - right wing - 168 bytes - 56 colors
|
||||
* 5 - control panel - 282 bytes - 94 colors
|
||||
* 6 - lower left speaker - 36 bytes - 12 colors
|
||||
* 7 - lower right speaker - 36 bytes - 12 colors
|
||||
* 8 - woofer - 42 bytes - 14 colors
|
||||
* 9 - v unit - 258 bytes - 86 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;
|
||||
|
||||
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) {}
|
||||
|
||||
} mapping[] = {
|
||||
{ 74, Lights::TITLE_AVG_R, Lights::TITLE_AVG_G, Lights::TITLE_AVG_B },
|
||||
{ 12, Lights::UPPER_LEFT_SPEAKER_AVG_R, Lights::UPPER_LEFT_SPEAKER_AVG_G, Lights::UPPER_LEFT_SPEAKER_AVG_B },
|
||||
{ 12, Lights::UPPER_RIGHT_SPEAKER_AVG_R, Lights::UPPER_RIGHT_SPEAKER_AVG_G, Lights::UPPER_RIGHT_SPEAKER_AVG_B },
|
||||
{ 56, Lights::LEFT_WING_AVG_R, Lights::LEFT_WING_AVG_G, Lights::LEFT_WING_AVG_B },
|
||||
{ 56, Lights::RIGHT_WING_AVG_R, Lights::RIGHT_WING_AVG_G, Lights::RIGHT_WING_AVG_B },
|
||||
{ 94, Lights::CONTROL_PANEL_AVG_R, Lights::CONTROL_PANEL_AVG_G, Lights::CONTROL_PANEL_AVG_B },
|
||||
{ 12, Lights::LOWER_LEFT_SPEAKER_AVG_R, Lights::LOWER_LEFT_SPEAKER_AVG_G, Lights::LOWER_LEFT_SPEAKER_AVG_B },
|
||||
{ 12, Lights::LOWER_RIGHT_SPEAKER_AVG_R, Lights::LOWER_RIGHT_SPEAKER_AVG_G, Lights::LOWER_RIGHT_SPEAKER_AVG_B },
|
||||
{ 14, Lights::WOOFER_AVG_R, Lights::WOOFER_AVG_G, Lights::WOOFER_AVG_B },
|
||||
{ 86, Lights::V_UNIT_AVG_R, Lights::V_UNIT_AVG_G, Lights::V_UNIT_AVG_B },
|
||||
};
|
||||
|
||||
// check index bounds
|
||||
if (tapeledutils::is_enabled() && index < std::size(mapping)) {
|
||||
auto &map = mapping[index];
|
||||
|
||||
// pick a color to use
|
||||
const auto rgb = tapeledutils::pick_color_from_led_tape(data, map.data_size);
|
||||
|
||||
// program the lights into API
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
|
||||
}
|
||||
|
||||
if (This != custom_node) {
|
||||
// send tape data to real device
|
||||
return AIO_IOB2_BI2X_UFC__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");
|
||||
BI2X_INITIALIZED = true;
|
||||
|
||||
// enable hack to make PIN pad work for KFC in BI2X mode
|
||||
// this explicit check in the I/O init path is necessary
|
||||
// (as opposed to just doing a check for "isValkyrieCabMode?")
|
||||
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode
|
||||
acioemu::ICCA_DEVICE_HACK = true;
|
||||
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, "aioIob2Bi2xUFC_Create",
|
||||
aioIob2Bi2xUFC_Create, &aioIob2Bi2xUFC_Create_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?GetDeviceStatus@AIO_IOB2_BI2X_UFC@@QEBAXAEAUDEVSTATUS@1@@Z",
|
||||
AIO_IOB2_BI2X_UFC__GetDeviceStatus, &AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?IoReset@AIO_IOB2_BI2X_UFC@@QEAAXI@Z",
|
||||
AIO_IOB2_BI2X_UFC__IoReset, &AIO_IOB2_BI2X_UFC__IoReset_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?SetWatchDogTimer@AIO_IOB2_BI2X_UFC@@QEAAXE@Z",
|
||||
AIO_IOB2_BI2X_UFC__SetWatchDogTimer, &AIO_IOB2_BI2X_UFC__SetWatchDogTimer_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?ControlCoinBlocker@AIO_IOB2_BI2X_UFC@@QEAAXI_N@Z",
|
||||
AIO_IOB2_BI2X_UFC__ControlCoinBlocker, &AIO_IOB2_BI2X_UFC__ControlCoinBlocker_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?AddCounter@AIO_IOB2_BI2X_UFC@@QEAAXII@Z",
|
||||
AIO_IOB2_BI2X_UFC__AddCounter, &AIO_IOB2_BI2X_UFC__AddCounter_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?SetIccrLed@AIO_IOB2_BI2X_UFC@@QEAAXI@Z",
|
||||
AIO_IOB2_BI2X_UFC__SetIccrLed, &AIO_IOB2_BI2X_UFC__SetIccrLed_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?SetPlayerButtonLamp@AIO_IOB2_BI2X_UFC@@QEAAXI_N@Z",
|
||||
AIO_IOB2_BI2X_UFC__SetPlayerButtonLamp, &AIO_IOB2_BI2X_UFC__SetPlayerButtonLamp_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "?SetTapeLedData@AIO_IOB2_BI2X_UFC@@QEAAXIPEBX@Z",
|
||||
AIO_IOB2_BI2X_UFC__SetTapeLedData, &AIO_IOB2_BI2X_UFC__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
games/sdvx/bi2x_hook.h
Normal file
6
games/sdvx/bi2x_hook.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::sdvx {
|
||||
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
147
games/sdvx/camera.cpp
Normal file
147
games/sdvx/camera.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
// 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 "camera.h"
|
||||
|
||||
#include <mfobjects.h>
|
||||
#include <mfidl.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "hooks/cfgmgr32hook.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
|
||||
|
||||
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
|
||||
|
||||
namespace games::sdvx {
|
||||
|
||||
static std::wstring CAMERA0_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);
|
||||
|
||||
// try first camera
|
||||
wchar_t *pwc = nullptr;
|
||||
if (CAMERA0_ID.length() == 23)
|
||||
pwc = wcsstr(*ppwszValue, CAMERA0_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';
|
||||
}
|
||||
|
||||
// return original result
|
||||
return result;
|
||||
}
|
||||
|
||||
static void hook_camera(IMFActivate* camera, size_t no, std::wstring camera_id, std::string camera_instance) {
|
||||
|
||||
// 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);
|
||||
|
||||
// save original method for later use
|
||||
if (GetAllocatedString_orig == nullptr) {
|
||||
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
|
||||
}
|
||||
|
||||
// hook allocated string method for camera identification
|
||||
memutils::VProtectGuard camera_guard(camera->lpVtbl);
|
||||
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
|
||||
}
|
||||
|
||||
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 < 1; cam_num++) {
|
||||
|
||||
// flip
|
||||
size_t cam_num_flipped = cam_num;
|
||||
|
||||
// get camera link
|
||||
IMFActivate *camera = (*pppSourceActivate)[cam_num_flipped];
|
||||
LPWSTR camera_link_lpwstr;
|
||||
UINT32 camera_link_length;
|
||||
if (SUCCEEDED(camera->lpVtbl->GetAllocatedString(
|
||||
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);
|
||||
|
||||
// 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(camera, cam_hook_num, camera_id, camera_instance);
|
||||
|
||||
// increase camera hook number
|
||||
cam_hook_num++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// return result
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
void camera_init() {
|
||||
|
||||
// camera media framework hook
|
||||
MFEnumDeviceSources_orig = detour::iat_try(
|
||||
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
|
||||
}
|
||||
}
|
||||
6
games/sdvx/camera.h
Normal file
6
games/sdvx/camera.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::sdvx {
|
||||
|
||||
void camera_init();
|
||||
}
|
||||
128
games/sdvx/io.cpp
Normal file
128
games/sdvx/io.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "io.h"
|
||||
|
||||
std::vector<Button> &games::sdvx::get_buttons() {
|
||||
static std::vector<Button> buttons;
|
||||
|
||||
if (buttons.empty()) {
|
||||
buttons = GameAPI::Buttons::getButtons("Sound Voltex");
|
||||
|
||||
GameAPI::Buttons::sortButtons(
|
||||
&buttons,
|
||||
"Service",
|
||||
"Test",
|
||||
"Coin Mech",
|
||||
"BT-A",
|
||||
"BT-B",
|
||||
"BT-C",
|
||||
"BT-D",
|
||||
"FX-L",
|
||||
"FX-R",
|
||||
"Start",
|
||||
"VOL-L Left",
|
||||
"VOL-L Right",
|
||||
"VOL-R Left",
|
||||
"VOL-R Right",
|
||||
"Headphone"
|
||||
);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::sdvx::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Sound Voltex");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"VOL-L",
|
||||
"VOL-R"
|
||||
);
|
||||
}
|
||||
|
||||
return analogs;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::sdvx::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("Sound Voltex");
|
||||
|
||||
GameAPI::Lights::sortLights(
|
||||
&lights,
|
||||
"BT-A",
|
||||
"BT-B",
|
||||
"BT-C",
|
||||
"BT-D",
|
||||
"FX-L",
|
||||
"FX-R",
|
||||
"Start",
|
||||
"Wing Left Up R",
|
||||
"Wing Left Up G",
|
||||
"Wing Left Up B",
|
||||
"Wing Right Up R",
|
||||
"Wing Right Up G",
|
||||
"Wing Right Up B",
|
||||
"Wing Left Low R",
|
||||
"Wing Left Low G",
|
||||
"Wing Left Low B",
|
||||
"Wing Right Low R",
|
||||
"Wing Right Low G",
|
||||
"Wing Right Low B",
|
||||
"Woofer R",
|
||||
"Woofer G",
|
||||
"Woofer B",
|
||||
"Controller R",
|
||||
"Controller G",
|
||||
"Controller B",
|
||||
"Generator R",
|
||||
"Generator G",
|
||||
"Generator B",
|
||||
"Pop",
|
||||
"Title Left",
|
||||
"Title Right",
|
||||
"Volume Sound",
|
||||
"Volume Headphone",
|
||||
"Volume External",
|
||||
"Volume Woofer",
|
||||
"IC Card Reader R",
|
||||
"IC Card Reader G",
|
||||
"IC Card Reader B",
|
||||
"Title Avg R",
|
||||
"Title Avg G",
|
||||
"Title Avg B",
|
||||
"Upper Left Speaker Avg R",
|
||||
"Upper Left Speaker Avg G",
|
||||
"Upper Left Speaker Avg B",
|
||||
"Upper Right Speaker Avg R",
|
||||
"Upper Right Speaker Avg G",
|
||||
"Upper Right Speaker Avg B",
|
||||
"Left Wing Avg R",
|
||||
"Left Wing Avg G",
|
||||
"Left Wing Avg B",
|
||||
"Right Wing Avg R",
|
||||
"Right Wing Avg G",
|
||||
"Right Wing Avg B",
|
||||
"Lower Left Speaker Avg R",
|
||||
"Lower Left Speaker Avg G",
|
||||
"Lower Left Speaker Avg B",
|
||||
"Lower Right Speaker Avg R",
|
||||
"Lower Right Speaker Avg G",
|
||||
"Lower Right Speaker Avg B",
|
||||
"Control Panel Avg R",
|
||||
"Control Panel Avg G",
|
||||
"Control Panel Avg B",
|
||||
"Woofer Avg R",
|
||||
"Woofer Avg G",
|
||||
"Woofer Avg B",
|
||||
"V Unit Avg R",
|
||||
"V Unit Avg G",
|
||||
"V Unit Avg B"
|
||||
);
|
||||
}
|
||||
|
||||
return lights;
|
||||
}
|
||||
115
games/sdvx/io.h
Normal file
115
games/sdvx/io.h
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "cfg/api.h"
|
||||
|
||||
namespace games::sdvx {
|
||||
|
||||
// all buttons in correct order
|
||||
namespace Buttons {
|
||||
enum {
|
||||
Service,
|
||||
Test,
|
||||
CoinMech,
|
||||
BT_A,
|
||||
BT_B,
|
||||
BT_C,
|
||||
BT_D,
|
||||
FX_L,
|
||||
FX_R,
|
||||
Start,
|
||||
VOL_L_Left,
|
||||
VOL_L_Right,
|
||||
VOL_R_Left,
|
||||
VOL_R_Right,
|
||||
Headphone,
|
||||
};
|
||||
}
|
||||
|
||||
// all analogs in correct order
|
||||
namespace Analogs {
|
||||
enum {
|
||||
VOL_L,
|
||||
VOL_R,
|
||||
};
|
||||
}
|
||||
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
enum {
|
||||
BT_A,
|
||||
BT_B,
|
||||
BT_C,
|
||||
BT_D,
|
||||
FX_L,
|
||||
FX_R,
|
||||
START,
|
||||
WING_LEFT_UP_R,
|
||||
WING_LEFT_UP_G,
|
||||
WING_LEFT_UP_B,
|
||||
WING_RIGHT_UP_R,
|
||||
WING_RIGHT_UP_G,
|
||||
WING_RIGHT_UP_B,
|
||||
WING_LEFT_LOW_R,
|
||||
WING_LEFT_LOW_G,
|
||||
WING_LEFT_LOW_B,
|
||||
WING_RIGHT_LOW_R,
|
||||
WING_RIGHT_LOW_G,
|
||||
WING_RIGHT_LOW_B,
|
||||
WOOFER_R,
|
||||
WOOFER_G,
|
||||
WOOFER_B,
|
||||
CONTROLLER_R,
|
||||
CONTROLLER_G,
|
||||
CONTROLLER_B,
|
||||
GENERATOR_R,
|
||||
GENERATOR_G,
|
||||
GENERATOR_B,
|
||||
POP,
|
||||
TITLE_LEFT,
|
||||
TITLE_RIGHT,
|
||||
VOLUME_SOUND,
|
||||
VOLUME_HEADPHONE,
|
||||
VOLUME_EXTERNAL,
|
||||
VOLUME_WOOFER,
|
||||
ICCR_R,
|
||||
ICCR_G,
|
||||
ICCR_B,
|
||||
TITLE_AVG_R,
|
||||
TITLE_AVG_G,
|
||||
TITLE_AVG_B,
|
||||
UPPER_LEFT_SPEAKER_AVG_R,
|
||||
UPPER_LEFT_SPEAKER_AVG_G,
|
||||
UPPER_LEFT_SPEAKER_AVG_B,
|
||||
UPPER_RIGHT_SPEAKER_AVG_R,
|
||||
UPPER_RIGHT_SPEAKER_AVG_G,
|
||||
UPPER_RIGHT_SPEAKER_AVG_B,
|
||||
LEFT_WING_AVG_R,
|
||||
LEFT_WING_AVG_G,
|
||||
LEFT_WING_AVG_B,
|
||||
RIGHT_WING_AVG_R,
|
||||
RIGHT_WING_AVG_G,
|
||||
RIGHT_WING_AVG_B,
|
||||
LOWER_LEFT_SPEAKER_AVG_R,
|
||||
LOWER_LEFT_SPEAKER_AVG_G,
|
||||
LOWER_LEFT_SPEAKER_AVG_B,
|
||||
LOWER_RIGHT_SPEAKER_AVG_R,
|
||||
LOWER_RIGHT_SPEAKER_AVG_G,
|
||||
LOWER_RIGHT_SPEAKER_AVG_B,
|
||||
CONTROL_PANEL_AVG_R,
|
||||
CONTROL_PANEL_AVG_G,
|
||||
CONTROL_PANEL_AVG_B,
|
||||
WOOFER_AVG_R,
|
||||
WOOFER_AVG_G,
|
||||
WOOFER_AVG_B,
|
||||
V_UNIT_AVG_R,
|
||||
V_UNIT_AVG_G,
|
||||
V_UNIT_AVG_B,
|
||||
};
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
466
games/sdvx/sdvx.cpp
Normal file
466
games/sdvx/sdvx.cpp
Normal file
@@ -0,0 +1,466 @@
|
||||
#include "sdvx.h"
|
||||
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/shared/lcdhandle.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "hooks/devicehook.h"
|
||||
#include "hooks/libraryhook.h"
|
||||
#include "hooks/graphics/nvapi_hook.h"
|
||||
#include "hooks/powrprof.h"
|
||||
#include "hooks/sleephook.h"
|
||||
#include "hooks/winuser.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/libutils.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include "camera.h"
|
||||
#include "io.h"
|
||||
#include "acioemu/handle.h"
|
||||
#include "cfg/configurator.h"
|
||||
|
||||
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::sdvx {
|
||||
|
||||
// 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 DISABLECAMS = false;
|
||||
bool NATIVETOUCH = false;
|
||||
uint8_t DIGITAL_KNOB_SENS = 16;
|
||||
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
|
||||
bool ENABLE_COM_PORT_SCAN_HOOK = 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 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)
|
||||
{
|
||||
// ASIO hook
|
||||
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("sdvx::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);
|
||||
}
|
||||
}
|
||||
|
||||
// COM hook
|
||||
if (ENABLE_COM_PORT_SCAN_HOOK &&
|
||||
lpSubKey != nullptr && phkResult != nullptr &&
|
||||
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
|
||||
log_info("sdvx::io", "failing HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
|
||||
return 2; //ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
|
||||
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("sdvx::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);
|
||||
}
|
||||
|
||||
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("sdvx::asio", "RegQueryValueExA({}, \"{}\")", fmt::ptr((void *) hKey), lpValueName);
|
||||
|
||||
if (_stricmp(lpValueName, "Description") == 0) {
|
||||
// sdvx does a comparison against hardcoded string "XONAR SOUND CARD(64)" (same as iidx31)
|
||||
// so what's in the registry must be overridden with "XONAR SOUND CARD(64)"
|
||||
// otherwise you end up with this error: M:BMSoundLib: ASIODriver: No such driver
|
||||
memcpy(lpData, ORIGINAL_ASIO_DEVICE_NAME, strlen(ORIGINAL_ASIO_DEVICE_NAME) + 1);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
} else {
|
||||
hKey = real_asio_device_reg_handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
SDVXGame::SDVXGame() : Game("Sound Voltex") {
|
||||
}
|
||||
|
||||
static LPWSTR __stdcall GetCommandLineW_hook() {
|
||||
static std::wstring lp_args = L"bootstrap.exe prop\\bootstrap.xml";
|
||||
return lp_args.data();
|
||||
}
|
||||
|
||||
#ifdef SPICE64
|
||||
static bool sdvx64_spam_remover(void *user, const std::string &data, logger::Style style, std::string &out) {
|
||||
if (data.empty() || data[0] != '[') {
|
||||
return false;
|
||||
}
|
||||
if (data.find("W:afpu-package: XE592acd000040 texture id invalid") != std::string::npos) {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
if (data.find("W:afpu-package: XE592acd000042 texture id invalid") != std::string::npos) {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
if (data.find("W:CTexture: no such texture: id 0") != std::string::npos) {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
if (data.find("M:autoDj: DEF phrase ") != std::string::npos) {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
if (data.find("W:afp-access: afp_mc_deep_goto_play frame no error") != std::string::npos) {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
if (data.find("W:afputils: CDirectX::SetRenderState") != std::string::npos) {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
if (data.find("W:CameraTexture: Camera error was detected. (err,detail) = (0,0)") != std::string::npos) {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
// M:AppConfig: [env/APPDATA]=C:\Users\username\AppData\Roaming
|
||||
if (data.find("M:AppConfig: [env/") != std::string::npos) {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef void **(__fastcall *volume_set_t)(uint64_t, uint64_t, uint64_t);
|
||||
static volume_set_t volume_set_orig = nullptr;
|
||||
static void **__fastcall volume_set_hook(uint64_t vol_sound, uint64_t vol_woofer, uint64_t vol_headphone) {
|
||||
|
||||
// volume level conversion tables
|
||||
static uint8_t SOUND_VOLUMES[] = {
|
||||
4, 55, 57, 59, 61, 63, 65, 67, 69, 71,
|
||||
73, 75, 77, 78, 79, 80, 81, 82, 83, 84,
|
||||
85, 86, 87, 88, 89, 90, 91, 92, 93, 95, 96,
|
||||
};
|
||||
static uint8_t WOOFER_VOLUMES[] = {
|
||||
4, 70, 72, 73, 74, 75, 76, 77, 79, 80,
|
||||
81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
|
||||
91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 100,
|
||||
};
|
||||
static uint8_t HEADPHONE_VOLUMES[] = {
|
||||
4, 60, 62, 64, 66, 68, 70, 72, 76, 78,
|
||||
80, 82, 83, 84, 85, 86, 87, 88, 89, 90,
|
||||
91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 100,
|
||||
};
|
||||
|
||||
// apply volumes
|
||||
auto &format = hooks::audio::FORMAT.Format;
|
||||
auto &lights = games::sdvx::get_lights();
|
||||
if (format.nChannels == 6 || vol_sound != 30) {
|
||||
if (vol_sound < std::size(SOUND_VOLUMES) && vol_sound != 30) {
|
||||
float value = (float) SOUND_VOLUMES[vol_sound] * 0.01f;
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::VOLUME_SOUND], value);
|
||||
}
|
||||
}
|
||||
if (vol_woofer < std::size(WOOFER_VOLUMES)) {
|
||||
float value = (float) WOOFER_VOLUMES[vol_woofer] * 0.01f;
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::VOLUME_WOOFER], value);
|
||||
}
|
||||
if (vol_headphone < std::size(HEADPHONE_VOLUMES)) {
|
||||
float value = (float) HEADPHONE_VOLUMES[vol_headphone] * 0.01f;
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::VOLUME_HEADPHONE], value);
|
||||
}
|
||||
|
||||
// call original function to set volumes for the 6ch mode
|
||||
return volume_set_orig(format.nChannels == 6 ? vol_sound : 30, vol_woofer, vol_headphone);
|
||||
}
|
||||
#endif
|
||||
|
||||
void SDVXGame::pre_attach() {
|
||||
// for whatever reason, sdvx latches onto cards for much longer than other games
|
||||
// needed because the game waits forever on the game over screen until a card is not detected
|
||||
AUTO_INSERT_CARD_COOLDOWN = 15.f;
|
||||
// check bad model name
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("UFC")) {
|
||||
log_fatal(
|
||||
"sdvx",
|
||||
"BAD MODEL NAME ERROR\n\n\n"
|
||||
"!!! model name set to UFC, this is WRONG and will break your game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! If you are trying to boot Valkyrie Model, !!!\n"
|
||||
"!!! change <spec> from F to G. !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! model name set to UFC, this is WRONG and will break your game !!!\n\n\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void SDVXGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
|
||||
// LCD handle
|
||||
if (!isValkyrieCabinetMode) {
|
||||
devicehook_init();
|
||||
devicehook_add(new games::shared::LCDHandle());
|
||||
}
|
||||
#else
|
||||
devicehook_init();
|
||||
devicehook_add(new games::shared::LCDHandle());
|
||||
#endif
|
||||
hooks::sleep::init(1000, 1);
|
||||
|
||||
// hooks for chinese SDVX
|
||||
if (libutils::try_module("unisintr.dll")) {
|
||||
detour::iat_try("GetCommandLineW", GetCommandLineW_hook);
|
||||
|
||||
// skip 30 second timeout after NETWORK DEVICE check
|
||||
replace_pattern(
|
||||
avs::game::DLL_INSTANCE,
|
||||
"89F528003D????0000",
|
||||
"89F528003D01000000",
|
||||
0, 0);
|
||||
}
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
|
||||
// check for new I/O DLL
|
||||
auto aio = libutils::try_library("libaio.dll");
|
||||
if (aio != nullptr) {
|
||||
|
||||
// enable 9on12 for AMD
|
||||
if (!libutils::try_library("nvapi64.dll")) {
|
||||
log_info(
|
||||
"sdvx",
|
||||
"nvapi64.dll not found; for non-NVIDIA GPUs, requesting 9on12 to be enabled");
|
||||
GRAPHICS_9_ON_12_REQUESTED_BY_GAME = true;
|
||||
} else {
|
||||
// don't let nvapi mess with display settings
|
||||
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
||||
}
|
||||
|
||||
// check for Valkyrie cabinet mode
|
||||
if (isValkyrieCabinetMode) {
|
||||
// hook touch window
|
||||
// in windowed mode, game can accept mouse input on the second screen
|
||||
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook_title_ends(
|
||||
"SOUND VOLTEX",
|
||||
"Main Screen",
|
||||
avs::game::DLL_INSTANCE);
|
||||
}
|
||||
|
||||
// insert BI2X hooks
|
||||
bi2x_hook_init();
|
||||
|
||||
// add card readers
|
||||
devicehook_init(aio);
|
||||
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
|
||||
|
||||
// this is needed because on some newer versions of SDVX6, soundvoltex.dll will open
|
||||
// HKLM\HARDWARE\DEVICEMAP\SERIALCOMM, go through some of the keys, and depending on
|
||||
// what is present, pick a port other than COM1 (seemingly the highest port
|
||||
// available). We want the game to pick COM1 still, so a workaround is needed to
|
||||
// fool the game.
|
||||
ENABLE_COM_PORT_SCAN_HOOK = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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);
|
||||
RegQueryValueExA_orig = detour::iat_try(
|
||||
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
|
||||
|
||||
#ifdef SPICE64
|
||||
powrprof_hook_init(avs::game::DLL_INSTANCE);
|
||||
winuser_hook_init(avs::game::DLL_INSTANCE);
|
||||
|
||||
// hook camera
|
||||
if (!DISABLECAMS) {
|
||||
camera_init();
|
||||
}
|
||||
|
||||
// RGB CAMERA error ignore
|
||||
if (!replace_pattern(
|
||||
avs::game::DLL_INSTANCE,
|
||||
"418D480484C074218D51FD",
|
||||
"????????????9090??????",
|
||||
0, 0)) {
|
||||
log_warning("sdvx", "failed to insert camera error fix");
|
||||
}
|
||||
|
||||
// remove log spam
|
||||
logger::hook_add(sdvx64_spam_remover, nullptr);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void SDVXGame::post_attach() {
|
||||
Game::post_attach();
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
|
||||
/*
|
||||
* Volume Hook
|
||||
*
|
||||
* How to find the correct RVA:
|
||||
*
|
||||
* Method 1 (older versions):
|
||||
* Search for byte sequence 48 8B C4 48 81 EC 88 00 00 00 80 3D
|
||||
*
|
||||
* Method 2 (older versions):
|
||||
* 1. search for ac_io_bi2a_set_amp_volume
|
||||
* 2. move one function up (function where it does some calculations, that's ours)
|
||||
* 3. take the *file offset* of the *first* instruction of this function
|
||||
*
|
||||
* Method 3:
|
||||
* Search for the function with the 3 arguments which is being called from the sound options.
|
||||
* It is pretty obvious which one it is because it checks all 3 args to be <= 30.
|
||||
*/
|
||||
static const robin_hood::unordered_map<std::string, intptr_t> VOLUME_HOOKS {
|
||||
{ "2019100800", 0x414ED0 },
|
||||
{ "2020011500", 0x417090 },
|
||||
{ "2020022700", 0x4281A0 },
|
||||
{ "2020122200", 0x40C030 },
|
||||
{ "2021042800", 0x096EB0 },
|
||||
{ "2021051802", 0x097930 },
|
||||
{ "2021083100", 0x096E20 },
|
||||
{ "2021102000", 0x097230 },
|
||||
{ "2021121400", 0x09AD00 },
|
||||
};
|
||||
|
||||
bool volume_hook_found = false;
|
||||
for (auto &[datecode, rva] : VOLUME_HOOKS) {
|
||||
if (avs::game::is_ext(datecode.c_str())) {
|
||||
|
||||
// calculate target RVA
|
||||
auto volume_set_rva = libutils::offset2rva(MODULE_PATH / avs::game::DLL_NAME, rva);
|
||||
if (volume_set_rva == -1) {
|
||||
log_warning("sdvx", "failed to insert set volume hook (convert rva {})", rva);
|
||||
break;
|
||||
}
|
||||
|
||||
// convert RVA to real target
|
||||
auto *volume_set_ptr = reinterpret_cast<uint16_t *>(
|
||||
reinterpret_cast<intptr_t>(avs::game::DLL_INSTANCE) + volume_set_rva);
|
||||
if (volume_set_ptr[0] != 0x8B48) {
|
||||
log_warning("sdvx", "failed to insert set volume hook (invalid target)");
|
||||
break;
|
||||
}
|
||||
|
||||
// insert trampoline
|
||||
if (!detour::trampoline(
|
||||
reinterpret_cast<volume_set_t>(volume_set_ptr),
|
||||
volume_set_hook,
|
||||
&volume_set_orig))
|
||||
{
|
||||
log_warning("sdvx", "failed to insert set volume hook (insert trampoline)");
|
||||
}
|
||||
|
||||
// success
|
||||
volume_hook_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if version not found
|
||||
if (!volume_hook_found) {
|
||||
log_warning("sdvx", "volume hook unavailable for this game version");
|
||||
|
||||
// set volumes to sdvx 4 defaults
|
||||
auto &lights = games::sdvx::get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_SOUND],
|
||||
(100 - 15) / 100.f);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_HEADPHONE],
|
||||
(100 - 9) / 100.f);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_EXTERNAL],
|
||||
(100 - 96) / 100.f);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_WOOFER],
|
||||
(100 - 9) / 100.f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SDVXGame::detach() {
|
||||
Game::detach();
|
||||
|
||||
devicehook_dispose();
|
||||
}
|
||||
}
|
||||
33
games/sdvx/sdvx.h
Normal file
33
games/sdvx/sdvx.h
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::sdvx {
|
||||
|
||||
enum SdvxOverlayPosition {
|
||||
SDVX_OVERLAY_TOP,
|
||||
SDVX_OVERLAY_MIDDLE,
|
||||
SDVX_OVERLAY_BOTTOM
|
||||
};
|
||||
|
||||
// settings
|
||||
extern bool DISABLECAMS;
|
||||
extern bool NATIVETOUCH;
|
||||
extern uint8_t DIGITAL_KNOB_SENS;
|
||||
extern std::optional<std::string> ASIO_DRIVER;
|
||||
extern bool BI2X_INITIALIZED;
|
||||
extern SdvxOverlayPosition OVERLAY_POS;
|
||||
|
||||
class SDVXGame : public games::Game {
|
||||
public:
|
||||
SDVXGame();
|
||||
virtual void pre_attach() override;
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user