From ece03cabbafc4b8ceae2c5d21b3166876cf143c7 Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Fri, 5 Jun 2026 02:05:03 -0700 Subject: [PATCH] cfg: asio driver selector must list both 32-bit and 64-bit drivers (#734) ## Link to GitHub Issue or related Pull Request, if one exists #730 ## Description of change spicecfg is a 32-bit application. When it uses the ASIO SDK it only saw the 32-bit drivers. There are some ASIO drivers that have different names under 32-bit and 64-bit (Xonar AE is one of them)... so we have to manually scan the registry and surface both the WOW32 and WOW64 nodes. ## Testing image Checked both 32-bit and 64-bit configurator, identical results. --- src/spice2x/CMakeLists.txt | 1 + src/spice2x/hooks/audio/asio_driver_scan.cpp | 74 ++++++++++++++++++++ src/spice2x/hooks/audio/asio_driver_scan.h | 15 ++++ src/spice2x/overlay/windows/config.cpp | 32 ++++----- 4 files changed, 103 insertions(+), 19 deletions(-) create mode 100644 src/spice2x/hooks/audio/asio_driver_scan.cpp create mode 100644 src/spice2x/hooks/audio/asio_driver_scan.h diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index 5d39d7a..87ae498 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -494,6 +494,7 @@ set(SOURCE_FILES ${SOURCE_FILES} # hooks hooks/audio/acm.cpp hooks/audio/audio.cpp + hooks/audio/asio_driver_scan.cpp hooks/audio/asio_proxy.cpp hooks/audio/buffer.cpp hooks/audio/mme.cpp diff --git a/src/spice2x/hooks/audio/asio_driver_scan.cpp b/src/spice2x/hooks/audio/asio_driver_scan.cpp new file mode 100644 index 0000000..ef8d425 --- /dev/null +++ b/src/spice2x/hooks/audio/asio_driver_scan.cpp @@ -0,0 +1,74 @@ +#include "asio_driver_scan.h" + +#include + +#include + +#include "util/utils.h" + +namespace hooks::audio { + + static constexpr char ASIO_REG_PATH[] = "software\\asio"; + static constexpr char ASIO_REG_DESC[] = "description"; + + // enumerate a single registry view, appending to entries while merging + // duplicates discovered in another view. Drivers are matched by name (not + // CLSID): the game's ASIO loader selects drivers by name, and some vendors + // register the same CLSID under different 32-bit/64-bit names (e.g. "XONAR + // SOUND CARD" vs "XONAR SOUND CARD(64)"), which are distinct user choices. + static void scan_view( + REGSAM wow64_flag, + bool is_64bit, + std::vector &entries) { + + HKEY hkEnum = nullptr; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, ASIO_REG_PATH, 0, + KEY_READ | wow64_flag, &hkEnum) != ERROR_SUCCESS) { + return; + } + + char key_name[256]; + for (DWORD index = 0; + RegEnumKeyA(hkEnum, index, key_name, sizeof(key_name)) == ERROR_SUCCESS; + index++) { + + // read description (display name), fall back to the key name + char desc[256] = { 0 }; + DWORD size = sizeof(desc); + std::string name = key_name; + if (RegGetValueA(hkEnum, + key_name, + ASIO_REG_DESC, + RRF_RT_REG_SZ | wow64_flag, + nullptr, + desc, + &size) == ERROR_SUCCESS && desc[0]) { + name = desc; + } + + // merge with an existing entry from the other view (match by name) + const std::string name_lower = strtolower(name); + auto it = std::find_if(entries.begin(), entries.end(), [&](const auto &e) { + return strtolower(e.name) == name_lower; + }); + if (it == entries.end()) { + entries.push_back({ name }); + it = entries.end() - 1; + } + it->found_32bit |= !is_64bit; + it->found_64bit |= is_64bit; + } + + RegCloseKey(hkEnum); + } + + std::vector scan_asio_drivers() { + std::vector entries; + + // 64-bit view first so it wins ordering when present in both + scan_view(KEY_WOW64_64KEY, true, entries); + scan_view(KEY_WOW64_32KEY, false, entries); + + return entries; + } +} diff --git a/src/spice2x/hooks/audio/asio_driver_scan.h b/src/spice2x/hooks/audio/asio_driver_scan.h new file mode 100644 index 0000000..25a0c47 --- /dev/null +++ b/src/spice2x/hooks/audio/asio_driver_scan.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace hooks::audio { + + struct AsioDriverScanEntry { + std::string name; + bool found_32bit = false; + bool found_64bit = false; + }; + + std::vector scan_asio_drivers(); +} diff --git a/src/spice2x/overlay/windows/config.cpp b/src/spice2x/overlay/windows/config.cpp index c30301a..faee123 100644 --- a/src/spice2x/overlay/windows/config.cpp +++ b/src/spice2x/overlay/windows/config.cpp @@ -12,9 +12,9 @@ #include "build/resource.h" #include "cfg/config.h" #include "cfg/configurator.h" -#include "external/asio/asiolist.h" #include "external/imgui/imgui_internal.h" #include "external/imgui/misc/cpp/imgui_stdlib.h" +#include "hooks/audio/asio_driver_scan.h" #include "games/io.h" #include "games/sdvx/sdvx.h" #include "games/popn/popn.h" @@ -63,7 +63,7 @@ namespace overlay::windows { constexpr ImVec4 TEXT_COLOR_RED(1.f, 0.f, 0.f, 1.f); constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512; - std::unique_ptr asio_driver_list; + std::optional> asio_driver_list; Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) { this->title = "Configuration"; @@ -4426,30 +4426,24 @@ namespace overlay::windows { void Config::build_option_value_picker(Option& option) { auto &definition = option.get_definition(); if (definition.picker == OptionPickerType::AsioDriver) { - if (asio_driver_list == nullptr) { - asio_driver_list = std::make_unique(); + if (!asio_driver_list.has_value()) { + asio_driver_list = hooks::audio::scan_asio_drivers(); } - ImGui::TextUnformatted("If your ASIO driver is not shown here, close this"); - ImGui::TextUnformatted("popup and enter the driver name manually."); - ImGui::SameLine(); - ImGui::HelpMarker( - "This list is populated by scanning the registry for ASIO drivers.\n\n" - "If your driver is not showing up, it may be because it is not properly registered in the system.\n\n" - "For 64-bit games, check in HKLM\\SOFTWARE\\ASIO\\.\n\n" - "For 32-bit games on 64-bit Windows, check in HKLM\\SOFTWARE\\WOW6432Node\\ASIO\\.\n\n" - "spicecfg runs in 32-bit, so it may not see 64-bit-only drivers."); - - ImGui::TextUnformatted(""); - if (asio_driver_list->driver_list.empty()) { + if (asio_driver_list->empty()) { ImGui::TextUnformatted("No ASIO drivers found."); } else { ImGui::TextUnformatted("Pick from ASIO drivers:"); ImGui::SetNextItemWidth(300.f); if (ImGui::BeginListBox("##asiodrivers")) { - for (const auto &driver : asio_driver_list->driver_list) { - const bool is_selected = option.value == std::string(driver.name); - if (ImGui::Selectable(fmt::format("[{}] {}", driver.id, driver.name).c_str(), is_selected)) { + for (const auto &driver : *asio_driver_list) { + const bool is_selected = option.value == driver.name; + const char *arch = (driver.found_32bit && driver.found_64bit) + ? "32/64-bit" + : (driver.found_64bit ? "64-bit" : "32-bit"); + if (ImGui::Selectable( + fmt::format("{} ({})", driver.name, arch).c_str(), + is_selected)) { option.value = driver.name; } }