Update to spice2x-26-02-17 (pre-apply)
> broken commit
This commit is contained in:
+100
-36
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -10,12 +12,22 @@
|
||||
#include "launcher/options.h"
|
||||
#include "io.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/tapeled.h"
|
||||
#include "util/time.h"
|
||||
#include "acioemu/icca.h"
|
||||
|
||||
#define DEBUG_VERBOSE 0
|
||||
|
||||
#if DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
namespace games::sdvx {
|
||||
constexpr bool BI2X_PASSTHROUGH = false;
|
||||
bool BI2X_INITIALIZED = false;
|
||||
|
||||
/*
|
||||
* class definitions
|
||||
@@ -26,20 +38,54 @@ namespace games::sdvx {
|
||||
uint8_t data[0x10];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t dummy0[0x50];
|
||||
struct AIO_NMGR_IOB2_VTABLE {
|
||||
// other functions here, but they never get called
|
||||
uint8_t dummy[0x50];
|
||||
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
|
||||
uint8_t dummy1[0x9A0];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
AIO_NMGR_IOB2_VTABLE *vptr;
|
||||
uint8_t dummy[0x9F0];
|
||||
};
|
||||
|
||||
// confirmed in EG final in aioNMgrIob2_Create
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC {
|
||||
// who knows
|
||||
uint8_t data[0x13F8];
|
||||
uint8_t data[0x39D8];
|
||||
};
|
||||
|
||||
// confirmed in EG final in aioIob2Bi2xUFC_Create
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_UFC) == 0x39D8);
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC__INPUT {
|
||||
uint8_t DevIoCounter;
|
||||
uint8_t bExIoAErr;
|
||||
uint8_t bExIoBErr;
|
||||
uint8_t bPcPowerOn;
|
||||
uint8_t bPcPowerCheck;
|
||||
uint8_t CoinCount;
|
||||
uint8_t bTest;
|
||||
uint8_t bService;
|
||||
uint8_t bCoinSw;
|
||||
uint8_t bCoinJam;
|
||||
uint8_t bHPDetect;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC__DEVSTATUS {
|
||||
// of course you could work with variables here
|
||||
uint8_t buffer[0x19E];
|
||||
uint8_t InputCounter;
|
||||
uint8_t OutputCounter;
|
||||
uint8_t IoResetCounter;
|
||||
uint8_t TapeLedCounter;
|
||||
uint8_t TapeLedRate[8];
|
||||
AIO_IOB2_BI2X_UFC__INPUT Input;
|
||||
uint8_t unk_1[289];
|
||||
uint16_t AnalogLeft;
|
||||
uint16_t AnalogRight;
|
||||
uint8_t Buttons;
|
||||
uint8_t unk_2[97];
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -66,7 +112,7 @@ namespace games::sdvx {
|
||||
// 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);
|
||||
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
@@ -133,8 +179,8 @@ namespace games::sdvx {
|
||||
AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status);
|
||||
}
|
||||
|
||||
status->buffer[0] = count;
|
||||
status->buffer[12] = count;
|
||||
status->InputCounter = count;
|
||||
status->Input.DevIoCounter = count;
|
||||
count++;
|
||||
|
||||
// get buttons
|
||||
@@ -142,41 +188,51 @@ namespace games::sdvx {
|
||||
|
||||
// control buttons
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
|
||||
status->buffer[18] = 0x01;
|
||||
status->Input.bTest = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
|
||||
status->buffer[19] = 0x01;
|
||||
status->Input.bService = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
|
||||
status->buffer[20] = 0x01;
|
||||
status->Input.bCoinSw = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Start]))
|
||||
status->buffer[316] |= 0x01;
|
||||
status->Buttons |= 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_A]))
|
||||
status->buffer[316] |= 0x02;
|
||||
status->Buttons |= 0x02;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_B]))
|
||||
status->buffer[316] |= 0x04;
|
||||
status->Buttons |= 0x04;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_C]))
|
||||
status->buffer[316] |= 0x08;
|
||||
status->Buttons |= 0x08;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_D]))
|
||||
status->buffer[316] |= 0x10;
|
||||
status->Buttons |= 0x10;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_L]))
|
||||
status->buffer[316] |= 0x20;
|
||||
status->Buttons |= 0x20;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_R]))
|
||||
status->buffer[316] |= 0x40;
|
||||
status->Buttons |= 0x40;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]))
|
||||
status->buffer[22] = 0x01;
|
||||
status->Input.bHPDetect = 0x01;
|
||||
|
||||
status->Input.CoinCount += eamuse_coin_get_stock();
|
||||
|
||||
const auto now = get_performance_milliseconds();
|
||||
|
||||
// volume left
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left])) {
|
||||
const auto vol_l_state = socd::socd_clean(0,
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right]),
|
||||
now);
|
||||
if (vol_l_state == socd::SocdCCW) {
|
||||
VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right])) {
|
||||
} else if (vol_l_state == socd::SocdCW) {
|
||||
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
|
||||
// volume right
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left])) {
|
||||
const auto vol_r_state = socd::socd_clean(1,
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right]),
|
||||
now);
|
||||
if (vol_r_state == socd::SocdCCW) {
|
||||
VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right])) {
|
||||
} else if (vol_r_state == socd::SocdCW) {
|
||||
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
|
||||
@@ -189,8 +245,14 @@ namespace games::sdvx {
|
||||
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;
|
||||
status->AnalogLeft = vol_left;
|
||||
status->AnalogRight = vol_right;
|
||||
|
||||
log_debug(
|
||||
"bi2x_hook",
|
||||
"knobs = {} {}",
|
||||
status->AnalogLeft,
|
||||
status->AnalogRight);
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
|
||||
@@ -345,21 +407,21 @@ namespace games::sdvx {
|
||||
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
|
||||
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;
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2{};
|
||||
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
|
||||
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||
}
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create");
|
||||
BI2X_INITIALIZED = true;
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
|
||||
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
|
||||
|
||||
// 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;
|
||||
return aioNmgrIob2;
|
||||
}
|
||||
|
||||
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
|
||||
@@ -411,3 +473,5 @@ namespace games::sdvx {
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::sdvx {
|
||||
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
+103
-16
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/shared/lcdhandle.h"
|
||||
#include "games/io.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "hooks/devicehook.h"
|
||||
@@ -12,11 +12,14 @@
|
||||
#include "hooks/powrprof.h"
|
||||
#include "hooks/sleephook.h"
|
||||
#include "hooks/winuser.h"
|
||||
#include "launcher/options.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/libutils.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "misc/eamuse.h"
|
||||
@@ -46,21 +49,27 @@ namespace games::sdvx {
|
||||
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
|
||||
bool ENABLE_COM_PORT_SCAN_HOOK = false;
|
||||
|
||||
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
|
||||
bool USE_ASIO = false;
|
||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||
|
||||
// states
|
||||
bool SHOW_VM_MONITOR_WARNING = false;
|
||||
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;
|
||||
if (lpSubKey != nullptr &&
|
||||
phkResult != nullptr &&
|
||||
hKey == HKEY_LOCAL_MACHINE &&
|
||||
_stricmp(lpSubKey, "software\\asio") == 0) {
|
||||
|
||||
// prevent the game from randomly picking an ASIO device
|
||||
if (!USE_ASIO) {
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
// convince the game to use our hook (so we can swap in the preferred device)
|
||||
if (ASIO_DRIVER.has_value()) {
|
||||
*phkResult = PARENT_ASIO_REG_HANDLE;
|
||||
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
|
||||
}
|
||||
}
|
||||
@@ -106,8 +115,8 @@ namespace games::sdvx {
|
||||
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
|
||||
log_info("sdvx::io", "hook access to HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
|
||||
@@ -215,9 +224,44 @@ namespace games::sdvx {
|
||||
deferredlogs::defer_error_messages(deferredlogs::SUPERSTEP_SOUND_ERROR_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
if (data.find("W:ea3-pos: ea3_report_posev: no such node:") != std::string::npos) {
|
||||
deferredlogs::defer_error_messages({
|
||||
"bad prop\\ea3-config.xml detected by game",
|
||||
std::string(" ") + data,
|
||||
" this will most likely cause Server Busy errors in certain conditions",
|
||||
" follow the FAQ to fix your ea3-config.xml and try again"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SDVXGame::detect_sound_output_device() {
|
||||
USE_ASIO = false;
|
||||
// if the user specified a value for -sdvxasio, use asio
|
||||
if (ASIO_DRIVER.has_value()) {
|
||||
log_misc(
|
||||
"sdvx",
|
||||
"-sdvxasio is set to \"{}\"", ASIO_DRIVER.value());
|
||||
USE_ASIO = true;
|
||||
} else {
|
||||
// see if XONAR AE is present; if so, use that
|
||||
HKEY subkey;
|
||||
LSTATUS result;
|
||||
result = RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\ASIO\\XONAR SOUND CARD(64)", &subkey);
|
||||
if (result == ERROR_SUCCESS) {
|
||||
RegCloseKey(subkey);
|
||||
USE_ASIO = true;
|
||||
log_misc(
|
||||
"sdvx",
|
||||
"found HKLM\\SOFTWARE\\ASIO\\XONAR SOUND CARD(64)");
|
||||
}
|
||||
}
|
||||
if (!USE_ASIO) {
|
||||
log_misc("sdvx", "asio not configured");
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -268,7 +312,7 @@ namespace games::sdvx {
|
||||
AUTO_INSERT_CARD_COOLDOWN = 15.f;
|
||||
// check bad model name
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("UFC")) {
|
||||
log_fatal(
|
||||
log_warning(
|
||||
"sdvx",
|
||||
"BAD MODEL NAME ERROR\n\n\n"
|
||||
"!!! model name set to UFC, this is WRONG and will break your game !!!\n"
|
||||
@@ -278,17 +322,61 @@ namespace games::sdvx {
|
||||
"!!! !!!\n"
|
||||
"!!! model name set to UFC, this is WRONG and will break your game !!!\n\n\n"
|
||||
);
|
||||
}
|
||||
log_fatal(
|
||||
"sdvx",
|
||||
"BAD MODEL NAME ERROR - model name set to UFC, must be KFC instead");
|
||||
}
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
// check -monitor + UFC mode
|
||||
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
|
||||
SHOW_VM_MONITOR_WARNING = true;
|
||||
log_warning(
|
||||
"sdvx",
|
||||
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
|
||||
"!!! known compatibility issues with the game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
|
||||
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
|
||||
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
|
||||
);
|
||||
|
||||
deferredlogs::defer_error_messages({
|
||||
"-monitor option is NOT recommended when running with Valkyrie mode",
|
||||
" due to known compatibility issues with the game:",
|
||||
" * game may launch in wrong resolution or refresh rate",
|
||||
" * touch / mouse input may stop working in subscreen / overlay",
|
||||
" recommended fix is to NOT use -monitor and instead set the primary",
|
||||
" monitor in Windows settings before launching the game"
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SPICE64
|
||||
this->detect_sound_output_device();
|
||||
#endif
|
||||
|
||||
// SOCD
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
if (options->at(launcher::Options::SDVXDigitalKnobSocd).is_active()) {
|
||||
if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "neutral") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
|
||||
} else if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "last") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!is_valkyrie_model()) {
|
||||
devicehook_init();
|
||||
devicehook_add(new games::shared::LCDHandle());
|
||||
}
|
||||
@@ -327,8 +415,7 @@ namespace games::sdvx {
|
||||
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
||||
}
|
||||
|
||||
// check for Valkyrie cabinet mode
|
||||
if (isValkyrieCabinetMode) {
|
||||
if (is_valkyrie_model()) {
|
||||
// hook touch window
|
||||
// in windowed mode, game can accept mouse input on the second screen
|
||||
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
||||
|
||||
+13
-1
@@ -4,6 +4,7 @@
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::sdvx {
|
||||
@@ -20,9 +21,18 @@ namespace games::sdvx {
|
||||
extern bool NATIVETOUCH;
|
||||
extern uint8_t DIGITAL_KNOB_SENS;
|
||||
extern std::optional<std::string> ASIO_DRIVER;
|
||||
extern bool BI2X_INITIALIZED;
|
||||
extern SdvxOverlayPosition OVERLAY_POS;
|
||||
|
||||
// states
|
||||
extern bool SHOW_VM_MONITOR_WARNING;
|
||||
|
||||
static inline bool is_valkyrie_model() {
|
||||
return (
|
||||
avs::game::is_model("KFC") &&
|
||||
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H')
|
||||
);
|
||||
}
|
||||
|
||||
class SDVXGame : public games::Game {
|
||||
public:
|
||||
SDVXGame();
|
||||
@@ -30,5 +40,7 @@ namespace games::sdvx {
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
private:
|
||||
void detect_sound_output_device();
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user