mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
gitadora: (arena model) asio option (#718)
## Link to GitHub Issue or related Pull Request, if one exists #717 ## Description of change Add an option to override the ASIO device. By default the game will try to use any ASIO driver that contains the string `XONAR` in it. ## Testing Tested with FlexASIO.
This commit is contained in:
@@ -416,6 +416,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
games/nost/io.cpp
|
games/nost/io.cpp
|
||||||
games/nost/poke.cpp
|
games/nost/poke.cpp
|
||||||
games/gitadora/gitadora.cpp
|
games/gitadora/gitadora.cpp
|
||||||
|
games/gitadora/asio.cpp
|
||||||
games/gitadora/io.cpp
|
games/gitadora/io.cpp
|
||||||
games/gitadora/handle.cpp
|
games/gitadora/handle.cpp
|
||||||
games/gitadora/j32d.cpp
|
games/gitadora/j32d.cpp
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
#include "asio.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
|
#include "gitadora.h"
|
||||||
|
#include "util/detour.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
namespace games::gitadora {
|
||||||
|
|
||||||
|
// Redirects the game's hard-coded "XONAR" ASIO driver lookup to the
|
||||||
|
// driver name in ASIO_DRIVER by intercepting registry calls to
|
||||||
|
// HKLM\SOFTWARE\ASIO. Sentinel HKEY values mark the redirected handles
|
||||||
|
// so we can recognise them on subsequent reg* calls.
|
||||||
|
|
||||||
|
static const HKEY PARENT_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x4001);
|
||||||
|
static const HKEY DEVICE_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x4002);
|
||||||
|
static const char *FAKE_ASIO_DEVICE_NAME = "XONAR";
|
||||||
|
|
||||||
|
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
|
||||||
|
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
|
||||||
|
static decltype(RegOpenKeyExA) *RegOpenKeyExA_orig = nullptr;
|
||||||
|
static decltype(RegQueryValueExA) *RegQueryValueExA_orig = nullptr;
|
||||||
|
|
||||||
|
static HKEY real_asio_reg_handle = nullptr;
|
||||||
|
static HKEY real_asio_device_reg_handle = nullptr;
|
||||||
|
|
||||||
|
static LONG WINAPI RegOpenKeyExA_hook(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
|
||||||
|
PHKEY phkResult)
|
||||||
|
{
|
||||||
|
// ASIO\XONAR redirect to ASIO\<configured>
|
||||||
|
if (ASIO_DRIVER.has_value() &&
|
||||||
|
lpSubKey != nullptr &&
|
||||||
|
phkResult != nullptr &&
|
||||||
|
hKey == PARENT_ASIO_REG_HANDLE &&
|
||||||
|
_stricmp(lpSubKey, FAKE_ASIO_DEVICE_NAME) == 0) {
|
||||||
|
|
||||||
|
*phkResult = DEVICE_ASIO_REG_HANDLE;
|
||||||
|
|
||||||
|
log_info("gitadora::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
|
||||||
|
const auto result = RegOpenKeyExA_orig(
|
||||||
|
real_asio_reg_handle,
|
||||||
|
ASIO_DRIVER.value().c_str(),
|
||||||
|
ulOptions,
|
||||||
|
samDesired,
|
||||||
|
&real_asio_device_reg_handle);
|
||||||
|
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
log_warning(
|
||||||
|
"gitadora::asio",
|
||||||
|
"failed to open registry subkey '{}', error=0x{:x}",
|
||||||
|
ASIO_DRIVER.value(), result);
|
||||||
|
log_warning(
|
||||||
|
"gitadora::asio",
|
||||||
|
"due to improper ASIO setting, audio init will fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open of the ASIO root: hand back a sentinel
|
||||||
|
if (ASIO_DRIVER.has_value() &&
|
||||||
|
lpSubKey != nullptr &&
|
||||||
|
phkResult != nullptr &&
|
||||||
|
hKey == HKEY_LOCAL_MACHINE &&
|
||||||
|
_stricmp(lpSubKey, "software\\asio") == 0)
|
||||||
|
{
|
||||||
|
*phkResult = PARENT_ASIO_REG_HANDLE;
|
||||||
|
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, &real_asio_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) {
|
||||||
|
// forward to real handle just to verify the key exists; we
|
||||||
|
// overwrite the name with our fake driver string regardless
|
||||||
|
auto ret = RegEnumKeyA_orig(real_asio_reg_handle, dwIndex, lpName, cchName);
|
||||||
|
if (ret == ERROR_SUCCESS && lpName != nullptr && cchName > 0) {
|
||||||
|
log_info("gitadora::asio", "stubbing '{}' with '{}'", lpName, FAKE_ASIO_DEVICE_NAME);
|
||||||
|
strncpy(lpName, FAKE_ASIO_DEVICE_NAME, cchName);
|
||||||
|
lpName[cchName - 1] = '\0';
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
return ERROR_NO_MORE_ITEMS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegEnumKeyA_orig(hKey, dwIndex, lpName, cchName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
|
||||||
|
LPBYTE lpData, LPDWORD lpcbData)
|
||||||
|
{
|
||||||
|
HKEY target = hKey;
|
||||||
|
|
||||||
|
if (ASIO_DRIVER.has_value() &&
|
||||||
|
lpValueName != nullptr &&
|
||||||
|
lpData != nullptr &&
|
||||||
|
lpcbData != nullptr &&
|
||||||
|
hKey == DEVICE_ASIO_REG_HANDLE) {
|
||||||
|
if (_stricmp(lpValueName, "Description") == 0) {
|
||||||
|
// engine may verify the driver name after open; ensure it still
|
||||||
|
// sees something containing "XONAR" so the substring check passes
|
||||||
|
const size_t len = strlen(FAKE_ASIO_DEVICE_NAME) + 1;
|
||||||
|
if (*lpcbData < len) {
|
||||||
|
*lpcbData = static_cast<DWORD>(len);
|
||||||
|
return ERROR_MORE_DATA;
|
||||||
|
}
|
||||||
|
memcpy(lpData, FAKE_ASIO_DEVICE_NAME, len);
|
||||||
|
*lpcbData = static_cast<DWORD>(len);
|
||||||
|
if (lpType != nullptr) {
|
||||||
|
*lpType = REG_SZ;
|
||||||
|
}
|
||||||
|
return ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for everything else (CLSID etc.) defer to the real driver subkey
|
||||||
|
target = real_asio_device_reg_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegQueryValueExA_orig(target, lpValueName, lpReserved, lpType, lpData, lpcbData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LONG WINAPI RegCloseKey_hook(HKEY hKey) {
|
||||||
|
if (hKey == PARENT_ASIO_REG_HANDLE) {
|
||||||
|
if (real_asio_reg_handle != nullptr) {
|
||||||
|
RegCloseKey_orig(real_asio_reg_handle);
|
||||||
|
real_asio_reg_handle = nullptr;
|
||||||
|
}
|
||||||
|
return ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hKey == DEVICE_ASIO_REG_HANDLE) {
|
||||||
|
if (real_asio_device_reg_handle != nullptr) {
|
||||||
|
RegCloseKey_orig(real_asio_device_reg_handle);
|
||||||
|
real_asio_device_reg_handle = nullptr;
|
||||||
|
}
|
||||||
|
return ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegCloseKey_orig(hKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void asio_hook_init() {
|
||||||
|
if (!ASIO_DRIVER.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("gitadora::asio", "installing ASIO driver redirect: XONAR -> {}", ASIO_DRIVER.value());
|
||||||
|
|
||||||
|
RegCloseKey_orig = detour::iat_try(
|
||||||
|
"RegCloseKey", RegCloseKey_hook, avs::game::DLL_INSTANCE);
|
||||||
|
RegEnumKeyA_orig = detour::iat_try(
|
||||||
|
"RegEnumKeyA", RegEnumKeyA_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace games::gitadora {
|
||||||
|
|
||||||
|
// installs IAT registry hooks in gfdm.dll that redirect the game's
|
||||||
|
// ASIO driver lookup (hard-coded "XONAR" substring) to a user-chosen
|
||||||
|
// driver name read from games::gitadora::ASIO_DRIVER.
|
||||||
|
//
|
||||||
|
// safe to call unconditionally; if ASIO_DRIVER is unset the hooks
|
||||||
|
// forward every call straight through to advapi32.
|
||||||
|
void asio_hook_init();
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "gitadora.h"
|
#include "gitadora.h"
|
||||||
|
#include "asio.h"
|
||||||
#include "handle.h"
|
#include "handle.h"
|
||||||
#include "bi2x_hook.h"
|
#include "bi2x_hook.h"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -28,6 +29,7 @@ namespace games::gitadora {
|
|||||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||||
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
||||||
std::optional<uint8_t> ARENA_WINDOW_COUNT = std::nullopt;
|
std::optional<uint8_t> ARENA_WINDOW_COUNT = std::nullopt;
|
||||||
|
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prevent GitaDora from creating folders on F drive
|
* Prevent GitaDora from creating folders on F drive
|
||||||
@@ -612,6 +614,9 @@ namespace games::gitadora {
|
|||||||
detour::iat_try("GetDriveTypeA", GetDriveTypeA_hook, avs::game::DLL_INSTANCE);
|
detour::iat_try("GetDriveTypeA", GetDriveTypeA_hook, avs::game::DLL_INSTANCE);
|
||||||
detour::iat_try("CreateDirectoryA", CreateDirectoryA_hook, avs::game::DLL_INSTANCE);
|
detour::iat_try("CreateDirectoryA", CreateDirectoryA_hook, avs::game::DLL_INSTANCE);
|
||||||
|
|
||||||
|
// ASIO driver redirect (XONAR -> user-configured driver)
|
||||||
|
asio_hook_init();
|
||||||
|
|
||||||
// volume change prevention
|
// volume change prevention
|
||||||
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
|
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace games::gitadora {
|
|||||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||||
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
||||||
extern std::optional<uint8_t> ARENA_WINDOW_COUNT;
|
extern std::optional<uint8_t> ARENA_WINDOW_COUNT;
|
||||||
|
extern std::optional<std::string> ASIO_DRIVER;
|
||||||
|
|
||||||
class GitaDoraGame : public games::Game {
|
class GitaDoraGame : public games::Game {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -677,6 +677,9 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::GitaDoraSubOverlaySize].is_active()) {
|
if (options[launcher::Options::GitaDoraSubOverlaySize].is_active()) {
|
||||||
games::gitadora::SUBSCREEN_OVERLAY_SIZE = options[launcher::Options::GitaDoraSubOverlaySize].value_text();
|
games::gitadora::SUBSCREEN_OVERLAY_SIZE = options[launcher::Options::GitaDoraSubOverlaySize].value_text();
|
||||||
}
|
}
|
||||||
|
if (options[launcher::Options::GitaDoraArenaAsioDriver].is_active()) {
|
||||||
|
games::gitadora::ASIO_DRIVER = options[launcher::Options::GitaDoraArenaAsioDriver].value_text();
|
||||||
|
}
|
||||||
if (options[launcher::Options::LoadNostalgiaModule].value_bool()) {
|
if (options[launcher::Options::LoadNostalgiaModule].value_bool()) {
|
||||||
attach_nostalgia = true;
|
attach_nostalgia = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1092,38 +1092,6 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.category = "Game Options",
|
.category = "Game Options",
|
||||||
.elements = {{"1", "DX"}, {"2", "SD"}, {"3", "SD2 - white cab"}},
|
.elements = {{"1", "DX"}, {"2", "SD"}, {"3", "SD2 - white cab"}},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// GitaDoraArenaSingleWindow
|
|
||||||
.title = "GitaDora Arena Disable Subscreens (DEPRECATED - use -gdalayout)",
|
|
||||||
.name = "gdaonewindow",
|
|
||||||
.desc = "DEPRECATED - use -gdalayout.\n\n"
|
|
||||||
"For Arena Model:\n\n"
|
|
||||||
"Windowed mode: instead of 4 windows, create 1 window.\n\n"
|
|
||||||
"Fullscreen mode: instead of requiring 4 monitors, use only the primary monitor. WARNING: requires 4K monitor.\n\n"
|
|
||||||
"To access the subscreen, use the subscreen overlay.",
|
|
||||||
.type = OptionType::Bool,
|
|
||||||
.hidden = true,
|
|
||||||
.game_name = "GitaDora",
|
|
||||||
.category = "Game Options",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// GitaDoraArenaWindowLayout
|
|
||||||
.title = "GitaDora Arena Layout (EXPERIMENTAL)",
|
|
||||||
.name = "gdalayout",
|
|
||||||
.desc = "For Arena Model: select how many windows to create.\n\n"
|
|
||||||
"1 window: main only; use the subscreen overlay for SMALL.\n\n"
|
|
||||||
"2 windows: main + SMALL. Note: currently only works for windowed mode, broken for fullscreen.\n\n"
|
|
||||||
"4 windows: main + LEFT + RIGHT + SMALL. Fullscreen requires exactly 4 monitors in the "
|
|
||||||
"right resolution; see wiki for details.",
|
|
||||||
.type = OptionType::Enum,
|
|
||||||
.game_name = "GitaDora",
|
|
||||||
.category = "Game Options",
|
|
||||||
.elements = {
|
|
||||||
{"1", "1 window"},
|
|
||||||
{"2", "2 windows"},
|
|
||||||
{"4", "4 windows"}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// GitaDoraLefty
|
// GitaDoraLefty
|
||||||
.title = "GitaDora Lefty Guitar (for Digital Wailing)",
|
.title = "GitaDora Lefty Guitar (for Digital Wailing)",
|
||||||
@@ -1189,6 +1157,51 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
{"large", ""}
|
{"large", ""}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// GitaDoraArenaSingleWindow
|
||||||
|
.title = "GitaDora Arena Disable Subscreens (DEPRECATED - use -gdalayout)",
|
||||||
|
.name = "gdaonewindow",
|
||||||
|
.desc = "DEPRECATED - use -gdalayout.\n\n"
|
||||||
|
"For Arena Model:\n\n"
|
||||||
|
"Windowed mode: instead of 4 windows, create 1 window.\n\n"
|
||||||
|
"Fullscreen mode: instead of requiring 4 monitors, use only the primary monitor. WARNING: requires 4K monitor.\n\n"
|
||||||
|
"To access the subscreen, use the subscreen overlay.",
|
||||||
|
.type = OptionType::Bool,
|
||||||
|
.hidden = true,
|
||||||
|
.game_name = "GitaDora",
|
||||||
|
.category = "Game Options",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// GitaDoraArenaWindowLayout
|
||||||
|
.title = "GitaDora Arena Layout (EXPERIMENTAL)",
|
||||||
|
.name = "gdalayout",
|
||||||
|
.desc = "For Arena Model: select how many windows to create.\n\n"
|
||||||
|
"1 window: main only; use the subscreen overlay for SMALL.\n\n"
|
||||||
|
"2 windows: main + SMALL. Note: currently only works for windowed mode, broken for fullscreen.\n\n"
|
||||||
|
"4 windows: main + LEFT + RIGHT + SMALL. Fullscreen requires exactly 4 monitors in the "
|
||||||
|
"right resolution; see wiki for details.",
|
||||||
|
.type = OptionType::Enum,
|
||||||
|
.game_name = "GitaDora",
|
||||||
|
.category = "Game Options",
|
||||||
|
.elements = {
|
||||||
|
{"1", "1 window"},
|
||||||
|
{"2", "2 windows"},
|
||||||
|
{"4", "4 windows"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// GitaDoraArenaAsioDriver
|
||||||
|
.title = "GitaDora ASIO driver",
|
||||||
|
.name = "gdaasio",
|
||||||
|
.desc = "For Arena Model: ASIO driver name to use in place of XONAR. "
|
||||||
|
"String should match a subkey under HKLM\\SOFTWARE\\ASIO\\\n\n"
|
||||||
|
"Requires 7.1 @ 48kHz; if the game rejects your ASIO device, it will automatically "
|
||||||
|
"fall back to using WASAPI.",
|
||||||
|
.type = OptionType::Text,
|
||||||
|
.game_name = "GitaDora",
|
||||||
|
.category = "Game Options",
|
||||||
|
.picker = OptionPickerType::AsioDriver,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.title = "Force Load Jubeat Module",
|
.title = "Force Load Jubeat Module",
|
||||||
.name = "jb",
|
.name = "jb",
|
||||||
|
|||||||
@@ -109,12 +109,13 @@ namespace launcher {
|
|||||||
LoadGitaDoraModule,
|
LoadGitaDoraModule,
|
||||||
GitaDoraTwoChannelAudio,
|
GitaDoraTwoChannelAudio,
|
||||||
GitaDoraCabinetType,
|
GitaDoraCabinetType,
|
||||||
GitaDoraArenaSingleWindow,
|
|
||||||
GitaDoraArenaWindowLayout,
|
|
||||||
GitaDoraLefty,
|
GitaDoraLefty,
|
||||||
GitaDoraWailHold,
|
GitaDoraWailHold,
|
||||||
GitaDoraPickAlgo,
|
GitaDoraPickAlgo,
|
||||||
GitaDoraSubOverlaySize,
|
GitaDoraSubOverlaySize,
|
||||||
|
GitaDoraArenaSingleWindow,
|
||||||
|
GitaDoraArenaWindowLayout,
|
||||||
|
GitaDoraArenaAsioDriver,
|
||||||
LoadJubeatModule,
|
LoadJubeatModule,
|
||||||
LoadReflecBeatModule,
|
LoadReflecBeatModule,
|
||||||
LoadShogikaiModule,
|
LoadShogikaiModule,
|
||||||
|
|||||||
Reference in New Issue
Block a user