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
<img width="329" height="309" alt="image"
src="https://github.com/user-attachments/assets/7abecf76-6835-4df5-8c2e-e7b425130f4c"
/>

Checked both 32-bit and 64-bit configurator, identical results.
This commit is contained in:
bicarus
2026-06-05 02:05:03 -07:00
committed by GitHub
parent 4b7f68f920
commit ece03cabba
4 changed files with 103 additions and 19 deletions
+1
View File
@@ -494,6 +494,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
# hooks # hooks
hooks/audio/acm.cpp hooks/audio/acm.cpp
hooks/audio/audio.cpp hooks/audio/audio.cpp
hooks/audio/asio_driver_scan.cpp
hooks/audio/asio_proxy.cpp hooks/audio/asio_proxy.cpp
hooks/audio/buffer.cpp hooks/audio/buffer.cpp
hooks/audio/mme.cpp hooks/audio/mme.cpp
@@ -0,0 +1,74 @@
#include "asio_driver_scan.h"
#include <algorithm>
#include <windows.h>
#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<AsioDriverScanEntry> &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<AsioDriverScanEntry> scan_asio_drivers() {
std::vector<AsioDriverScanEntry> 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;
}
}
@@ -0,0 +1,15 @@
#pragma once
#include <string>
#include <vector>
namespace hooks::audio {
struct AsioDriverScanEntry {
std::string name;
bool found_32bit = false;
bool found_64bit = false;
};
std::vector<AsioDriverScanEntry> scan_asio_drivers();
}
+13 -19
View File
@@ -12,9 +12,9 @@
#include "build/resource.h" #include "build/resource.h"
#include "cfg/config.h" #include "cfg/config.h"
#include "cfg/configurator.h" #include "cfg/configurator.h"
#include "external/asio/asiolist.h"
#include "external/imgui/imgui_internal.h" #include "external/imgui/imgui_internal.h"
#include "external/imgui/misc/cpp/imgui_stdlib.h" #include "external/imgui/misc/cpp/imgui_stdlib.h"
#include "hooks/audio/asio_driver_scan.h"
#include "games/io.h" #include "games/io.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "games/popn/popn.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 ImVec4 TEXT_COLOR_RED(1.f, 0.f, 0.f, 1.f);
constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512; constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512;
std::unique_ptr<AsioDriverList> asio_driver_list; std::optional<std::vector<hooks::audio::AsioDriverScanEntry>> asio_driver_list;
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) { Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
this->title = "Configuration"; this->title = "Configuration";
@@ -4426,30 +4426,24 @@ namespace overlay::windows {
void Config::build_option_value_picker(Option& option) { void Config::build_option_value_picker(Option& option) {
auto &definition = option.get_definition(); auto &definition = option.get_definition();
if (definition.picker == OptionPickerType::AsioDriver) { if (definition.picker == OptionPickerType::AsioDriver) {
if (asio_driver_list == nullptr) { if (!asio_driver_list.has_value()) {
asio_driver_list = std::make_unique<AsioDriverList>(); asio_driver_list = hooks::audio::scan_asio_drivers();
} }
ImGui::TextUnformatted("If your ASIO driver is not shown here, close this"); if (asio_driver_list->empty()) {
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()) {
ImGui::TextUnformatted("No ASIO drivers found."); ImGui::TextUnformatted("No ASIO drivers found.");
} else { } else {
ImGui::TextUnformatted("Pick from ASIO drivers:"); ImGui::TextUnformatted("Pick from ASIO drivers:");
ImGui::SetNextItemWidth(300.f); ImGui::SetNextItemWidth(300.f);
if (ImGui::BeginListBox("##asiodrivers")) { if (ImGui::BeginListBox("##asiodrivers")) {
for (const auto &driver : asio_driver_list->driver_list) { for (const auto &driver : *asio_driver_list) {
const bool is_selected = option.value == std::string(driver.name); const bool is_selected = option.value == driver.name;
if (ImGui::Selectable(fmt::format("[{}] {}", driver.id, driver.name).c_str(), is_selected)) { 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; option.value = driver.name;
} }
} }