Initial re-upload of spice2x-24-08-24

This commit is contained in:
2024-08-28 11:10:34 -04:00
commit caa9e02285
1181 changed files with 380065 additions and 0 deletions

413
games/sdvx/bi2x_hook.cpp Normal file
View 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
View File

@@ -0,0 +1,6 @@
#pragma once
namespace games::sdvx {
void bi2x_hook_init();
}

147
games/sdvx/camera.cpp Normal file
View 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
View File

@@ -0,0 +1,6 @@
#pragma once
namespace games::sdvx {
void camera_init();
}

128
games/sdvx/io.cpp Normal file
View 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
View 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
View 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
View 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;
};
}