734 lines
26 KiB
C++
734 lines
26 KiB
C++
|
#include "iidx.h"
|
||
|
|
||
|
#include "acioemu/handle.h"
|
||
|
#include "avs/core.h"
|
||
|
#include "avs/game.h"
|
||
|
#include "cfg/configurator.h"
|
||
|
#include "games/io.h"
|
||
|
|
||
|
#ifdef SPICE64
|
||
|
#include "games/iidx/camera.h"
|
||
|
#endif
|
||
|
#include "games/iidx/legacy_camera.h"
|
||
|
|
||
|
#include "hooks/avshook.h"
|
||
|
#include "hooks/cfgmgr32hook.h"
|
||
|
#include "hooks/devicehook.h"
|
||
|
#include "hooks/graphics/graphics.h"
|
||
|
#ifdef SPICE64
|
||
|
#include "hooks/graphics/nvenc_hook.h"
|
||
|
#endif
|
||
|
#include "hooks/setupapihook.h"
|
||
|
#include "hooks/sleephook.h"
|
||
|
#include "launcher/options.h"
|
||
|
#include "touch/touch.h"
|
||
|
#include "misc/wintouchemu.h"
|
||
|
#include "misc/eamuse.h"
|
||
|
#include "util/detour.h"
|
||
|
#include "util/fileutils.h"
|
||
|
#include "util/libutils.h"
|
||
|
#include "util/memutils.h"
|
||
|
#include "util/sigscan.h"
|
||
|
#include "util/utils.h"
|
||
|
|
||
|
#include "bi2a.h"
|
||
|
#include "bi2x_hook.h"
|
||
|
#include "ezusb.h"
|
||
|
#include "io.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::iidx {
|
||
|
|
||
|
// constants
|
||
|
const HKEY PARENT_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x3001);
|
||
|
const HKEY DEVICE_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x3002);
|
||
|
const char *ORIGINAL_ASIO_DEVICE_NAME = "XONAR SOUND CARD(64)";
|
||
|
|
||
|
// settings
|
||
|
bool FLIP_CAMS = false;
|
||
|
bool DISABLE_CAMS = false;
|
||
|
bool TDJ_CAMERA = false;
|
||
|
bool TDJ_CAMERA_PREFER_16_9 = true;
|
||
|
bool TDJ_MODE = false;
|
||
|
bool FORCE_720P = false;
|
||
|
bool DISABLE_ESPEC_IO = false;
|
||
|
bool NATIVE_TOUCH = false;
|
||
|
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
|
||
|
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||
|
uint8_t DIGITAL_TT_SENS = 4;
|
||
|
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt;
|
||
|
std::optional<std::string> SCREEN_MODE = std::nullopt;
|
||
|
std::optional<std::string> TDJ_CAMERA_OVERRIDE = std::nullopt;
|
||
|
|
||
|
// states
|
||
|
static HKEY real_asio_reg_handle = nullptr;
|
||
|
static HKEY real_asio_device_reg_handle = nullptr;
|
||
|
static uint16_t IIDXIO_TT_STATE[2]{};
|
||
|
static int8_t IIDXIO_TT_DIRECTION[2]{1, 1};
|
||
|
static bool IIDXIO_TT_PRESSED[2]{};
|
||
|
static bool IIDXIO_TT_ALT_PRESSED[2]{};
|
||
|
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
|
||
|
bool IIDXIO_LED_TICKER_READONLY = false;
|
||
|
std::mutex IIDX_LED_TICKER_LOCK;
|
||
|
|
||
|
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
|
||
|
if (lpSubKey != nullptr && phkResult != nullptr) {
|
||
|
if (hKey == HKEY_LOCAL_MACHINE &&
|
||
|
ASIO_DRIVER.has_value() &&
|
||
|
_stricmp(lpSubKey, "software\\asio") == 0)
|
||
|
{
|
||
|
*phkResult = PARENT_ASIO_REG_HANDLE;
|
||
|
|
||
|
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return RegOpenKeyA_orig(hKey, lpSubKey, phkResult);
|
||
|
}
|
||
|
|
||
|
static LONG WINAPI RegOpenKeyExA_hook(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
|
||
|
PHKEY phkResult)
|
||
|
{
|
||
|
if (lpSubKey != nullptr && phkResult != nullptr) {
|
||
|
if (hKey == PARENT_ASIO_REG_HANDLE &&
|
||
|
ASIO_DRIVER.has_value() &&
|
||
|
_stricmp(lpSubKey, ORIGINAL_ASIO_DEVICE_NAME) == 0)
|
||
|
{
|
||
|
*phkResult = DEVICE_ASIO_REG_HANDLE;
|
||
|
|
||
|
log_info("iidx::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
|
||
|
|
||
|
return RegOpenKeyExA_orig(real_asio_reg_handle, ASIO_DRIVER.value().c_str(), ulOptions, samDesired,
|
||
|
&real_asio_device_reg_handle);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
|
||
|
}
|
||
|
|
||
|
static LONG WINAPI RegEnumKeyA_hook(HKEY hKey, DWORD dwIndex, LPSTR lpName, DWORD cchName) {
|
||
|
if (hKey == PARENT_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
|
||
|
if (dwIndex == 0) {
|
||
|
auto ret = RegEnumKeyA_orig(real_asio_reg_handle, dwIndex, lpName, cchName);
|
||
|
|
||
|
if (ret == ERROR_SUCCESS && lpName != nullptr) {
|
||
|
log_info("iidx::asio", "stubbing '{}' with '{}'", lpName, ORIGINAL_ASIO_DEVICE_NAME);
|
||
|
|
||
|
strncpy(lpName, ORIGINAL_ASIO_DEVICE_NAME, cchName);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
} else {
|
||
|
return ERROR_NO_MORE_ITEMS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return RegEnumKeyA_orig(hKey, dwIndex, lpName, cchName);
|
||
|
}
|
||
|
|
||
|
static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCTSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
|
||
|
LPBYTE lpData, LPDWORD lpcbData)
|
||
|
{
|
||
|
if (lpValueName != nullptr && lpData != nullptr && lpcbData != nullptr) {
|
||
|
|
||
|
// ASIO hack
|
||
|
if (hKey == DEVICE_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
|
||
|
log_info("iidx::asio", "RegQueryValueExA({}, \"{}\")", fmt::ptr((void *) hKey), lpValueName);
|
||
|
|
||
|
if (_stricmp(lpValueName, "Description") == 0) {
|
||
|
// newer iidx does a comparison against hardcoded string "XONAR SOUND CARD(64)" (same as sdvx)
|
||
|
// 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Dirty Workaround for IO Device
|
||
|
* Game gets registry object via SetupDiOpenDevRegKey, then looks up "PortName" via RegQueryValueExA
|
||
|
* We ignore the first and just cheat on the second.
|
||
|
*/
|
||
|
// check for port name lookup
|
||
|
if (_stricmp(lpValueName, "PortName") == 0) {
|
||
|
const char port[] = "COM2";
|
||
|
memcpy(lpData, port, sizeof(port));
|
||
|
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// fallback
|
||
|
return RegQueryValueExA_orig(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
|
||
|
}
|
||
|
|
||
|
static LONG WINAPI RegCloseKey_hook(HKEY hKey) {
|
||
|
if (hKey == PARENT_ASIO_REG_HANDLE || hKey == DEVICE_ASIO_REG_HANDLE) {
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
return RegCloseKey_orig(hKey);
|
||
|
}
|
||
|
|
||
|
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
|
||
|
if (data.empty() || data[0] != '[') {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// get rid of the log spam
|
||
|
if (data.find(" I:graphic: adapter mode ") != std::string::npos) {
|
||
|
out.clear();
|
||
|
return true;
|
||
|
} else if (data.find(" W:afputils: CDirectX::SetRenderState ") != std::string::npos) {
|
||
|
out.clear();
|
||
|
return true;
|
||
|
} else if (data.find("\" layer ID 0 is not layer ID.") != std::string::npos) {
|
||
|
out.clear();
|
||
|
return true;
|
||
|
} else if (data.find(" W:touch: missing trigger:") != std::string::npos) {
|
||
|
out.clear();
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
|
||
|
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
||
|
|
||
|
static void* aioIob2Bi2x_CreateWriteFirmContext_hook(unsigned int a1, int a2) {
|
||
|
if (aioIob2Bi2x_CreateWriteFirmContext_orig) {
|
||
|
auto options = games::get_options(eamuse_get_game());
|
||
|
if (options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
|
||
|
log_info("iidx", "CreateWriteFirmContext({}, {} -> 2)", a1, a2);
|
||
|
return aioIob2Bi2x_CreateWriteFirmContext_orig(a1, 2);
|
||
|
} else {
|
||
|
log_info("iidx", "CreateWriteFirmContext({}, {})", a1, a2);
|
||
|
return aioIob2Bi2x_CreateWriteFirmContext_orig(a1, a2);
|
||
|
}
|
||
|
}
|
||
|
log_warning("iidx", "CreateWriteFirmContext == nullptr");
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
|
||
|
logger::hook_add(log_hook, this);
|
||
|
}
|
||
|
|
||
|
IIDXGame::~IIDXGame() {
|
||
|
logger::hook_remove(log_hook, this);
|
||
|
}
|
||
|
|
||
|
void IIDXGame::attach() {
|
||
|
Game::attach();
|
||
|
|
||
|
#ifdef SPICE64
|
||
|
if (find_pattern(
|
||
|
avs::game::DLL_INSTANCE,
|
||
|
"534F554E445F4F55545055545F444556494345", // SOUND_OUTPUT_DEVICE
|
||
|
"XXXXXXXXXXXXXXXXXXX",
|
||
|
0, 0)) {
|
||
|
log_misc("iidx", "This game accepts SOUND_OUTPUT_DEVICE environment variable");
|
||
|
} else {
|
||
|
log_warning("iidx", "This game does not accept SOUND_OUTPUT_DEVICE environment variable; it will be ignored");
|
||
|
log_warning("iidx", "Make sure you applied appropriate patches to use the correct sound device (ASIO or WASAPI)");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// IO boards
|
||
|
auto options = games::get_options(eamuse_get_game());
|
||
|
if (!options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
|
||
|
|
||
|
// reduce boot wait time
|
||
|
hooks::sleep::init(1000, 1);
|
||
|
|
||
|
// add old IO board
|
||
|
SETUPAPI_SETTINGS settings1 {};
|
||
|
settings1.class_guid[0] = 0xAE18AA60;
|
||
|
settings1.class_guid[1] = 0x11D47F6A;
|
||
|
settings1.class_guid[2] = 0x0100DD97;
|
||
|
settings1.class_guid[3] = 0x59B92902;
|
||
|
const char property1[] = "Cypress EZ-USB (2235 - EEPROM missing)";
|
||
|
const char interface_detail1[] = "\\\\.\\Ezusb-0";
|
||
|
memcpy(settings1.property_devicedesc, property1, sizeof(property1));
|
||
|
memcpy(settings1.interface_detail, interface_detail1, sizeof(interface_detail1));
|
||
|
setupapihook_init(avs::game::DLL_INSTANCE);
|
||
|
setupapihook_add(settings1);
|
||
|
|
||
|
// IIDX <25 with EZUSB input device
|
||
|
devicehook_init();
|
||
|
devicehook_add(new EZUSBHandle());
|
||
|
|
||
|
// add new BIO2 I/O board
|
||
|
SETUPAPI_SETTINGS settings2 {};
|
||
|
settings2.class_guid[0] = 0x4D36E978;
|
||
|
settings2.class_guid[1] = 0x11CEE325;
|
||
|
settings2.class_guid[2] = 0x0008C1BF;
|
||
|
settings2.class_guid[3] = 0x1803E12B;
|
||
|
const char property2[] = "BIO2(VIDEO)";
|
||
|
const char interface_detail2[] = "COM2";
|
||
|
memcpy(settings2.property_devicedesc, property2, sizeof(property2));
|
||
|
memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2));
|
||
|
setupapihook_add(settings2);
|
||
|
|
||
|
// IIDX 25-27 BIO2 BI2A input device
|
||
|
devicehook_add(new IIDXFMSerialHandle());
|
||
|
}
|
||
|
|
||
|
// check for new I/O DLL
|
||
|
auto aio = libutils::try_library("libaio.dll");
|
||
|
if (aio != nullptr) {
|
||
|
|
||
|
// check TDJ mode
|
||
|
TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA";
|
||
|
TDJ_MODE |= fileutils::text_read("D:\\001rom.txt") == "TDJ";
|
||
|
|
||
|
// force TDJ mode
|
||
|
if (TDJ_MODE) {
|
||
|
|
||
|
// ensure game starts in the desired mode
|
||
|
hooks::avs::set_rom("/c_drv/000rom.txt", "TDJ-JA");
|
||
|
hooks::avs::set_rom("/d_drv/001rom.txt", "TDJ");
|
||
|
|
||
|
// need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook
|
||
|
devicehook_init(avs::core::DLL_INSTANCE);
|
||
|
|
||
|
if (!NATIVE_TOUCH) {
|
||
|
wintouchemu::FORCE = true;
|
||
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||
|
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
|
||
|
}
|
||
|
|
||
|
// prevent crash on TDJ mode without correct DLL
|
||
|
if (!GetModuleHandle("nvcuda.dll")) {
|
||
|
DISABLE_CAMS = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// insert BI2X hooks
|
||
|
bi2x_hook_init();
|
||
|
|
||
|
// add card readers
|
||
|
devicehook_init(aio);
|
||
|
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
|
||
|
|
||
|
// firmware upgrade hook
|
||
|
if (options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
|
||
|
aioIob2Bi2x_CreateWriteFirmContext_orig = detour::iat(
|
||
|
"aioIob2Bi2x_CreateWriteFirmContext",
|
||
|
aioIob2Bi2x_CreateWriteFirmContext_hook);
|
||
|
//devicehook_add(new MITMHandle(L"#vid_1ccf&pid_8050", "", true));
|
||
|
} else {
|
||
|
|
||
|
/*
|
||
|
// add the BIO2 I/O board (with different firmware)
|
||
|
SETUPAPI_SETTINGS settings {};
|
||
|
settings.class_guid[0] = 0x0;
|
||
|
settings.class_guid[1] = 0x0;
|
||
|
settings.class_guid[2] = 0x0;
|
||
|
settings.class_guid[3] = 0x0;
|
||
|
const char property[] = "1CCF(8050)_000";
|
||
|
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8050&MI_00\\000";
|
||
|
const char interface_detail[] = "COM3";
|
||
|
memcpy(settings.property_devicedesc, property, sizeof(property));
|
||
|
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
|
||
|
memcpy(settings.interface_detail, interface_detail, sizeof(interface_detail));
|
||
|
setupapihook_init(aio);
|
||
|
setupapihook_add(settings);
|
||
|
|
||
|
// IIDX 27 BIO2 BI2X input devce
|
||
|
devicehook_add(new BI2XSerialHandle());
|
||
|
*/
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ASIO device hook
|
||
|
RegCloseKey_orig = detour::iat_try(
|
||
|
"RegCloseKey", RegCloseKey_hook, avs::game::DLL_INSTANCE);
|
||
|
RegEnumKeyA_orig = detour::iat_try(
|
||
|
"RegEnumKeyA", RegEnumKeyA_hook, avs::game::DLL_INSTANCE);
|
||
|
RegOpenKeyA_orig = detour::iat_try(
|
||
|
"RegOpenKeyA", RegOpenKeyA_hook, avs::game::DLL_INSTANCE);
|
||
|
RegOpenKeyExA_orig = detour::iat_try(
|
||
|
"RegOpenKeyExA", RegOpenKeyExA_hook, avs::game::DLL_INSTANCE);
|
||
|
|
||
|
// IO device workaround
|
||
|
RegQueryValueExA_orig = detour::iat_try(
|
||
|
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
|
||
|
|
||
|
// check if cam hook should be enabled
|
||
|
if (!DISABLE_CAMS) {
|
||
|
init_legacy_camera_hook(FLIP_CAMS);
|
||
|
}
|
||
|
|
||
|
#ifdef SPICE64
|
||
|
if (TDJ_CAMERA) {
|
||
|
init_camera_hooks();
|
||
|
}
|
||
|
if (TDJ_MODE && !D3D9_DEVICE_HOOK_DISABLE) {
|
||
|
nvenc_hook::initialize();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// init cfgmgr32 hooks
|
||
|
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
|
||
|
}
|
||
|
|
||
|
void IIDXGame::pre_attach() {
|
||
|
Game::pre_attach();
|
||
|
|
||
|
// environment variables must be set before the DLL is loaded as the VC++ runtime copies all
|
||
|
// environment variables at startup
|
||
|
if (DISABLE_CAMS) {
|
||
|
SetEnvironmentVariable("CONNECT_CAMERA", "0");
|
||
|
}
|
||
|
|
||
|
if (SCREEN_MODE.has_value()) {
|
||
|
SetEnvironmentVariable("SCREEN_MODE", SCREEN_MODE.value().c_str());
|
||
|
}
|
||
|
|
||
|
// windowed subscreen, enabled by default, unless turned off by user
|
||
|
auto options = games::get_options(eamuse_get_game());
|
||
|
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool()) {
|
||
|
GRAPHICS_IIDX_WSUB = true;
|
||
|
}
|
||
|
|
||
|
#ifdef SPICE64
|
||
|
this->detect_sound_output_device();
|
||
|
#endif
|
||
|
|
||
|
// check bad model name
|
||
|
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDJ")) {
|
||
|
log_fatal(
|
||
|
"iidx",
|
||
|
"BAD MODEL NAME ERROR\n\n\n"
|
||
|
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n"
|
||
|
"!!! !!!\n"
|
||
|
"!!! If you are trying to boot IIDX with Lightning Model mode, !!!\n"
|
||
|
"!!! please do the following instead: !!!\n"
|
||
|
"!!! !!!\n"
|
||
|
"!!! Revert your changes to XML file so it says !!!\n"
|
||
|
"!!! <model __type=\"str\">LDJ</model> !!!\n"
|
||
|
"!!! !!!\n"
|
||
|
"!!! In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag !!!\n"
|
||
|
"!!! in command line !!!\n"
|
||
|
"!!! !!!\n"
|
||
|
"!!! Apply any applicable settings / patches / hex edits !!!\n"
|
||
|
"!!! !!!\n"
|
||
|
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n\n\n"
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void IIDXGame::detach() {
|
||
|
Game::detach();
|
||
|
|
||
|
devicehook_dispose();
|
||
|
}
|
||
|
|
||
|
void IIDXGame::detect_sound_output_device() {
|
||
|
// if the user specified a value (other than auto), use it as the environment var
|
||
|
// probably "wasapi" or "asio", but it's not explicitly checked here for forward compat
|
||
|
if (SOUND_OUTPUT_DEVICE.has_value() && SOUND_OUTPUT_DEVICE.value() != "auto") {
|
||
|
log_info(
|
||
|
"iidx",
|
||
|
"using user-supplied \"{}\" for SOUND_OUTPUT_DEVICE",
|
||
|
SOUND_OUTPUT_DEVICE.value());
|
||
|
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", SOUND_OUTPUT_DEVICE.value().c_str());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// automatic detection
|
||
|
bool use_asio = false;
|
||
|
log_misc("iidx", "auto-detect SOUND_OUTPUT_DEVICE...");
|
||
|
if (ASIO_DRIVER.has_value()) {
|
||
|
log_misc(
|
||
|
"iidx",
|
||
|
"-iidxasio is set to \"{}\", use asio for SOUND_OUTPUT_DEVICE",
|
||
|
ASIO_DRIVER.value());
|
||
|
use_asio = true;
|
||
|
} else {
|
||
|
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(
|
||
|
"iidx",
|
||
|
"found HKLM\\SOFTWARE\\ASIO\\XONAR SOUND CARD(64), using asio for SOUND_OUTPUT_DEVICE");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const char* device = "wasapi";
|
||
|
if (use_asio) {
|
||
|
device = "asio";
|
||
|
}
|
||
|
log_info("iidx", "SOUND_OUTPUT_DEVICE set to {}", device);
|
||
|
log_info(
|
||
|
"iidx",
|
||
|
"SOUND_OUTPUT_DEVICE only affects IIDX27+ and ignored by older games. "
|
||
|
"If {} is not what you wanted, you can override it with -iidxsounddevice option",
|
||
|
device);
|
||
|
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", device);
|
||
|
}
|
||
|
|
||
|
uint32_t get_pad() {
|
||
|
uint32_t pad = 0;
|
||
|
|
||
|
// get buttons
|
||
|
auto &buttons = get_buttons();
|
||
|
|
||
|
// player 1 buttons
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_1)))
|
||
|
pad |= 1 << 0x08;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_2)))
|
||
|
pad |= 1 << 0x09;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_3)))
|
||
|
pad |= 1 << 0x0A;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_4)))
|
||
|
pad |= 1 << 0x0B;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_5)))
|
||
|
pad |= 1 << 0x0C;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_6)))
|
||
|
pad |= 1 << 0x0D;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_7)))
|
||
|
pad |= 1 << 0x0E;
|
||
|
|
||
|
// player 2 buttons
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_1)))
|
||
|
pad |= 1 << 0x0F;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_2)))
|
||
|
pad |= 1 << 0x10;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_3)))
|
||
|
pad |= 1 << 0x11;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_4)))
|
||
|
pad |= 1 << 0x12;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_5)))
|
||
|
pad |= 1 << 0x13;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_6)))
|
||
|
pad |= 1 << 0x14;
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_7)))
|
||
|
pad |= 1 << 0x15;
|
||
|
|
||
|
// player 1 start
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_Start)))
|
||
|
pad |= 1 << 0x18;
|
||
|
|
||
|
// player 2 start
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_Start)))
|
||
|
pad |= 1 << 0x19;
|
||
|
|
||
|
// VEFX
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::VEFX)))
|
||
|
pad |= 1 << 0x1A;
|
||
|
|
||
|
// EFFECT
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Effect)))
|
||
|
pad |= 1 << 0x1B;
|
||
|
|
||
|
// test
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test)))
|
||
|
pad |= 1 << 0x1C;
|
||
|
|
||
|
// service
|
||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service)))
|
||
|
pad |= 1 << 0x1D;
|
||
|
|
||
|
return ~(pad & 0xFFFFFF00);
|
||
|
}
|
||
|
|
||
|
void write_lamp(uint16_t lamp) {
|
||
|
|
||
|
// mapping
|
||
|
static const size_t mapping[] = {
|
||
|
Lights::P1_1,
|
||
|
Lights::P1_2,
|
||
|
Lights::P1_3,
|
||
|
Lights::P1_4,
|
||
|
Lights::P1_5,
|
||
|
Lights::P1_6,
|
||
|
Lights::P1_7,
|
||
|
Lights::P2_1,
|
||
|
Lights::P2_2,
|
||
|
Lights::P2_3,
|
||
|
Lights::P2_4,
|
||
|
Lights::P2_5,
|
||
|
Lights::P2_6,
|
||
|
Lights::P2_7,
|
||
|
};
|
||
|
|
||
|
// get lights
|
||
|
auto &lights = get_lights();
|
||
|
|
||
|
// bit scan
|
||
|
for (int i = 0; i < 14; i++) {
|
||
|
float value = (lamp & (1 << i)) ? 1.f : 0.f;
|
||
|
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void write_led(uint8_t led) {
|
||
|
|
||
|
// mapping
|
||
|
static const size_t mapping[] = {
|
||
|
Lights::P1_Start,
|
||
|
Lights::P2_Start,
|
||
|
Lights::VEFX,
|
||
|
Lights::Effect,
|
||
|
};
|
||
|
|
||
|
// get lights
|
||
|
auto &lights = get_lights();
|
||
|
|
||
|
// bit scan
|
||
|
for (int i = 0; i < 4; i++) {
|
||
|
auto value = (led & (1 << i)) ? 1.f : 0.f;
|
||
|
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void write_top_lamp(uint8_t top_lamp) {
|
||
|
|
||
|
// mapping
|
||
|
static const size_t mapping[] = {
|
||
|
Lights::SpotLight1,
|
||
|
Lights::SpotLight2,
|
||
|
Lights::SpotLight3,
|
||
|
Lights::SpotLight4,
|
||
|
Lights::SpotLight5,
|
||
|
Lights::SpotLight6,
|
||
|
Lights::SpotLight7,
|
||
|
Lights::SpotLight8,
|
||
|
};
|
||
|
|
||
|
// get lights
|
||
|
auto &lights = get_lights();
|
||
|
|
||
|
// bit scan
|
||
|
for (int i = 0; i < 8; i++) {
|
||
|
auto value = (top_lamp & (1 << i)) ? 1.f : 0.f;
|
||
|
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void write_top_neon(uint8_t top_neon) {
|
||
|
|
||
|
// get lights
|
||
|
auto &lights = get_lights();
|
||
|
|
||
|
// write value
|
||
|
auto value = top_neon > 0 ? 1.f : 0.f;
|
||
|
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::NeonLamp), value);
|
||
|
}
|
||
|
|
||
|
unsigned char get_tt(int player, bool slow) {
|
||
|
|
||
|
// check change value for high/low precision
|
||
|
uint16_t change =
|
||
|
slow ? (uint16_t) (DIGITAL_TT_SENS / 4) : (uint16_t) DIGITAL_TT_SENS;
|
||
|
|
||
|
// check player number
|
||
|
if (player > 1)
|
||
|
return 0;
|
||
|
|
||
|
// get buttons
|
||
|
auto &buttons = get_buttons();
|
||
|
bool ttp = GameAPI::Buttons::getState(RI_MGR, buttons.at(
|
||
|
player != 0 ? Buttons::P2_TTPlus : Buttons::P1_TTPlus));
|
||
|
bool ttm = GameAPI::Buttons::getState(RI_MGR, buttons.at(
|
||
|
player != 0 ? Buttons::P2_TTMinus : Buttons::P1_TTMinus));
|
||
|
|
||
|
bool ttpm = GameAPI::Buttons::getState(RI_MGR, buttons.at(
|
||
|
player != 0 ? Buttons::P2_TTPlusMinus : Buttons::P1_TTPlusMinus));
|
||
|
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
|
||
|
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
|
||
|
|
||
|
// TT+
|
||
|
if (ttp)
|
||
|
IIDXIO_TT_STATE[player] += change;
|
||
|
|
||
|
// TT-
|
||
|
if (ttm)
|
||
|
IIDXIO_TT_STATE[player] -= change;
|
||
|
|
||
|
// TT+/-
|
||
|
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
|
||
|
bool ttpm_alt_rising_edge = !IIDXIO_TT_ALT_PRESSED[player] && ttpm_alt;
|
||
|
if (ttpm_rising_edge || ttpm_alt_rising_edge) {
|
||
|
IIDXIO_TT_DIRECTION[player] *= -1;
|
||
|
}
|
||
|
if (ttpm || ttpm_alt) {
|
||
|
IIDXIO_TT_STATE[player] += (change * IIDXIO_TT_DIRECTION[player]);
|
||
|
}
|
||
|
IIDXIO_TT_PRESSED[player] = ttpm;
|
||
|
IIDXIO_TT_ALT_PRESSED[player] = ttpm_alt;
|
||
|
|
||
|
// raw input
|
||
|
auto &analogs = get_analogs();
|
||
|
auto &analog = analogs[player != 0 ? Analogs::TT_P2 : Analogs::TT_P1];
|
||
|
auto ret_value = IIDXIO_TT_STATE[player];
|
||
|
if (analog.isSet()) {
|
||
|
ret_value = IIDXIO_TT_STATE[player];
|
||
|
ret_value += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analog) * 1023.999f);
|
||
|
}
|
||
|
|
||
|
// return higher 8 bit
|
||
|
return (uint8_t) (ret_value >> 2);
|
||
|
}
|
||
|
|
||
|
unsigned char get_slider(uint8_t slider) {
|
||
|
|
||
|
// check slide
|
||
|
if (slider > 4)
|
||
|
return 0;
|
||
|
|
||
|
// get analog
|
||
|
auto &analogs = get_analogs();
|
||
|
Analog *analog = nullptr;
|
||
|
switch (slider) {
|
||
|
case 0:
|
||
|
analog = &analogs.at(Analogs::VEFX);
|
||
|
break;
|
||
|
case 1:
|
||
|
analog = &analogs.at(Analogs::LowEQ);
|
||
|
break;
|
||
|
case 2:
|
||
|
analog = &analogs.at(Analogs::HiEQ);
|
||
|
break;
|
||
|
case 3:
|
||
|
analog = &analogs.at(Analogs::Filter);
|
||
|
break;
|
||
|
case 4:
|
||
|
analog = &analogs.at(Analogs::PlayVolume);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// if not set return max value
|
||
|
if (!analog || !analog->isSet()) {
|
||
|
return 0xF;
|
||
|
}
|
||
|
|
||
|
// return slide
|
||
|
return (unsigned char) (GameAPI::Analogs::getState(RI_MGR, *analog) * 15.999f);
|
||
|
}
|
||
|
|
||
|
const char* get_16seg() {
|
||
|
return IIDXIO_LED_TICKER;
|
||
|
}
|
||
|
|
||
|
bool is_tdj_fhd() {
|
||
|
return TDJ_MODE && avs::game::is_ext(2022101900, MAXINT);
|
||
|
}
|
||
|
}
|