// set version to Windows 7 to enable Media Foundation functions #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0601 // turn on C-style COM objects #define CINTERFACE #include "iidx.h" #include #include #include "acioemu/handle.h" #include "avs/core.h" #include "avs/game.h" #include "cfg/configurator.h" #include "games/io.h" #include "hooks/avshook.h" #include "hooks/cfgmgr32hook.h" #include "hooks/devicehook.h" #include "hooks/graphics/graphics.h" #include "hooks/setupapihook.h" #include "hooks/sleephook.h" #include "launcher/options.h" #include "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/utils.h" #include "bi2a.h" #include "bi2x_hook.h" #include "ezusb.h" #include "io.h" static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr; static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr; static decltype(RegCloseKey) *RegCloseKey_orig = nullptr; static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr; static decltype(RegOpenKeyA) *RegOpenKeyA_orig = nullptr; static decltype(RegOpenKeyExA) *RegOpenKeyExA_orig = nullptr; static decltype(RegQueryValueExA) *RegQueryValueExA_orig = nullptr; namespace games::iidx { // constants const HKEY PARENT_ASIO_REG_HANDLE = reinterpret_cast(0x3001); const HKEY DEVICE_ASIO_REG_HANDLE = reinterpret_cast(0x3002); const char *ORIGINAL_ASIO_DEVICE_NAME = "XONAR SOUND CARD(64)"; // settings bool FLIP_CAMS = false; bool DISABLE_CAMS = false; bool TDJ_MODE = false; bool FORCE_720P = false; std::optional SOUND_OUTPUT_DEVICE = std::nullopt; std::optional ASIO_DRIVER = std::nullopt; uint8_t DIGITAL_TT_SENS = 4; std::optional SUBSCREEN_OVERLAY_SIZE = 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); } /* * Dirty Workaround for IO Device * Game gets registry object via SetupDiOpenDevRegKey, then looks up "PortName" via RegQueryValueExA * We ignore the first and just cheat on the second. */ static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCTSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData) { if (lpValueName != nullptr && lpData != nullptr && lpcbData != nullptr) { if (hKey == DEVICE_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) { log_info("iidx::asio", "RegQueryValueExA({}, \"{}\")", fmt::ptr((void *) hKey), lpValueName); if (_stricmp(lpValueName, "Description") == 0) { memcpy(lpData, ASIO_DRIVER.value().c_str(), ASIO_DRIVER.value().size() + 1); return ERROR_SUCCESS; } else { hKey = real_asio_device_reg_handle; } } // check for port name lookup if (_stricmp(lpValueName, "PortName") == 0) { const char port[] = "COM2"; memcpy(lpData, port, sizeof(port)); return ERROR_SUCCESS; } } // fallback return RegQueryValueExA_orig(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); } static LONG WINAPI RegCloseKey_hook(HKEY hKey) { if (hKey == PARENT_ASIO_REG_HANDLE || hKey == DEVICE_ASIO_REG_HANDLE) { return ERROR_SUCCESS; } return RegCloseKey_orig(hKey); } /* * Camera related stuff */ static std::wstring CAMERA0_ID; static std::wstring CAMERA1_ID; static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue, UINT32 *pcchLength) { // call the original HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength); // log the cam name log_misc("iidx::cam", "obtained {}", ws2s(*ppwszValue)); // try first camera wchar_t *pwc = nullptr; if (CAMERA0_ID.length() == 23) { pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str()); } // try second camera if first wasn't found if (!pwc && CAMERA1_ID.length() == 23) { pwc = wcsstr(*ppwszValue, CAMERA1_ID.c_str()); } // check if camera could be identified if (pwc) { // fake the USB IDs pwc[4] = L'2'; pwc[5] = L'8'; pwc[6] = L'8'; pwc[7] = L'c'; pwc[13] = L'0'; pwc[14] = L'0'; pwc[15] = L'0'; pwc[16] = L'2'; pwc[21] = L'0'; pwc[22] = L'0'; log_misc("iidx::cam", "replaced {}", ws2s(*ppwszValue)); } // return original result return result; } static void hook_camera_vtable(IMFActivate *camera) { // hook allocated string method for camera identification memutils::VProtectGuard camera_guard(camera->lpVtbl); camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook; } static void hook_camera(size_t no, std::wstring camera_id, std::string camera_instance) { // logic based on camera no if (no == 0) { // don't hook if camera 0 is already hooked if (CAMERA0_ID.length() > 0) { return; } // save the camera ID CAMERA0_ID = camera_id; // cfgmgr hook CFGMGR32_HOOK_SETTING camera_setting; camera_setting.device_instance = 0xDEADBEEF; camera_setting.parent_instance = ~camera_setting.device_instance; camera_setting.device_id = "USB\\VEN_1022&DEV_7908"; camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????"; if (camera_instance.length() == 17) { for (int i = 0; i < 17; i++) { camera_setting.device_node_id[28 + i] = camera_instance[i]; } } cfgmgr32hook_add(camera_setting); log_info("iidx::cam", "hooked camera 1 @ {}", ws2s(camera_id)); } else if (no == 1) { // don't hook if camera 1 is already hooked if (CAMERA1_ID.length() > 0) { return; } // save the camera ID CAMERA1_ID = camera_id; // cfgmgr hook CFGMGR32_HOOK_SETTING camera_setting; camera_setting.device_instance = 0xBEEFDEAD; camera_setting.parent_instance = ~camera_setting.device_instance; camera_setting.device_id = "USB\\VEN_1022&DEV_7914"; camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????"; if (camera_instance.length() == 17) { for (int i = 0; i < 17; i++) { camera_setting.device_node_id[28 + i] = camera_instance[i]; } } cfgmgr32hook_add(camera_setting); log_info("iidx::cam", "hooked camera 2 @ {}", ws2s(camera_id)); } } static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate, UINT32 *pcSourceActivate) { // call original function HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate); // check for capture devices if (FAILED(result_orig) || !*pcSourceActivate) { return result_orig; } // iterate cameras size_t cam_hook_num = 0; for (size_t cam_num = 0; cam_num < *pcSourceActivate && cam_hook_num < 2; cam_num++) { // flip size_t cam_num_flipped = cam_num; if (FLIP_CAMS) { cam_num_flipped = *pcSourceActivate - cam_num - 1; } // get camera IMFActivate *camera = (*pppSourceActivate)[cam_num_flipped]; // save original method for later use if (GetAllocatedString_orig == nullptr) { GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString; } // hook allocated string method for camera identification hook_camera_vtable(camera); // get camera link LPWSTR camera_link_lpwstr; UINT32 camera_link_length; if (SUCCEEDED(GetAllocatedString_orig( camera, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &camera_link_lpwstr, &camera_link_length))) { // cut name to make ID std::wstring camera_link_ws = std::wstring(camera_link_lpwstr); std::wstring camera_id = camera_link_ws.substr(8, 23); log_info("iidx::cam", "found video capture device: {}", ws2s(camera_link_ws)); // only support cameras that start with \\?\usb if (!string_begins_with(camera_link_ws, L"\\\\?\\usb")) { continue; } // get camera instance std::string camera_link = ws2s(camera_link_ws); std::string camera_instance = camera_link.substr(32, 17); // hook the camera hook_camera(cam_hook_num, camera_id, camera_instance); // increase camera hook number cam_hook_num++; } else { log_warning("iidx::cam", "failed to open camera {}", cam_num_flipped); } } // return result return result_orig; } static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) { // get rid of the log spam if (data.find(" I:graphic: adapter mode ") != std::string::npos) { out.clear(); return true; } else { return false; } } typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int); static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr; static void* aioIob2Bi2x_CreateWriteFirmContext_hook(unsigned int a1, int a2) { if (aioIob2Bi2x_CreateWriteFirmContext_orig) { auto options = games::get_options(eamuse_get_game()); if (options->at(launcher::Options::IIDXBIO2FW).value_bool()) { log_info("iidx", "CreateWriteFirmContext({}, {} -> 2)", a1, a2); return aioIob2Bi2x_CreateWriteFirmContext_orig(a1, 2); } else { log_info("iidx", "CreateWriteFirmContext({}, {})", a1, a2); return aioIob2Bi2x_CreateWriteFirmContext_orig(a1, a2); } } log_warning("iidx", "CreateWriteFirmContext == nullptr"); return nullptr; } IIDXGame::IIDXGame() : Game("Beatmania IIDX") { logger::hook_add(log_hook, this); } IIDXGame::~IIDXGame() { logger::hook_remove(log_hook, this); } void IIDXGame::attach() { Game::attach(); // IO boards auto options = games::get_options(eamuse_get_game()); if (!options->at(launcher::Options::IIDXBIO2FW).value_bool()) { // reduce boot wait time hooks::sleep::init(1000, 1); // add old IO board SETUPAPI_SETTINGS settings1 {}; settings1.class_guid[0] = 0xAE18AA60; settings1.class_guid[1] = 0x11D47F6A; settings1.class_guid[2] = 0x0100DD97; settings1.class_guid[3] = 0x59B92902; const char property1[] = "Cypress EZ-USB (2235 - EEPROM missing)"; const char interface_detail1[] = "\\\\.\\Ezusb-0"; memcpy(settings1.property_devicedesc, property1, sizeof(property1)); memcpy(settings1.interface_detail, interface_detail1, sizeof(interface_detail1)); setupapihook_init(avs::game::DLL_INSTANCE); setupapihook_add(settings1); // IIDX <25 with EZUSB input device devicehook_init(); devicehook_add(new EZUSBHandle()); // add new BIO2 I/O board SETUPAPI_SETTINGS settings2 {}; settings2.class_guid[0] = 0x4D36E978; settings2.class_guid[1] = 0x11CEE325; settings2.class_guid[2] = 0x0008C1BF; settings2.class_guid[3] = 0x1803E12B; const char property2[] = "BIO2(VIDEO)"; const char interface_detail2[] = "COM2"; memcpy(settings2.property_devicedesc, property2, sizeof(property2)); memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2)); setupapihook_add(settings2); // IIDX 25-27 BIO2 BI2A input device devicehook_add(new IIDXFMSerialHandle()); } // check for new I/O DLL auto aio = libutils::try_library("libaio.dll"); if (aio != nullptr) { // check TDJ mode TDJ_MODE |= fileutils::text_read("D:\\001rom.txt") == "TDJ"; // force TDJ mode if (TDJ_MODE) { // ensure game starts in the desired mode hooks::avs::set_rom("/d_drv/001rom.txt", "TDJ"); // need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook devicehook_init(avs::core::DLL_INSTANCE); 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) { // camera media framework hook MFEnumDeviceSources_orig = detour::iat_try( "MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE); // camera settings SETUPAPI_SETTINGS settings3 {}; settings3.class_guid[0] = 0x00000000; settings3.class_guid[1] = 0x00000000; settings3.class_guid[2] = 0x00000000; settings3.class_guid[3] = 0x00000000; const char property3[] = "USB Composite Device"; memcpy(settings3.property_devicedesc, property3, sizeof(property3)); settings3.property_address[0] = 1; settings3.property_address[1] = 7; setupapihook_add(settings3); } // init cfgmgr32 hooks cfgmgr32hook_init(avs::game::DLL_INSTANCE); } void IIDXGame::pre_attach() { Game::pre_attach(); // environment variables must be set before the DLL is loaded as the VC++ runtime copies all // environment variables at startup if (DISABLE_CAMS) { SetEnvironmentVariable("CONNECT_CAMERA", "0"); } this->detect_sound_output_device(); // 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" "If you are trying to boot IIDX with Lightning Model mode, please do the following instead:\n" " Revert your changes to XML file so it says LDJ\n" " In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag in command line\n" " Apply any applicable settings / patches / hex edits\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); } }