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
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;
}
}