/*---------------------------------------------------------*\ | CreativeSoundBlasterAE5Controller_Windows.cpp | | | | Driver for Creative SoundBlaster AE-5 (Windows) | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | \*---------------------------------------------------------*/ #include #include #include "CreativeSoundBlasterAE5Controller_Windows.h" #include "LogManager.h" #include /*---------------------------------------------------------*\ | Lifecycle | \*---------------------------------------------------------*/ CreativeSoundBlasterAE5Controller_Windows::CreativeSoundBlasterAE5Controller_Windows() { name = "Creative SoundBlaster AE-5"; location = ""; device_found = false; device_handle = INVALID_HANDLE_VALUE; device_opened = false; external_led_count = 0; led_mutex = CreateMutexA(NULL, FALSE, "OpenRGB_AE5_LED_Mutex"); } CreativeSoundBlasterAE5Controller_Windows::~CreativeSoundBlasterAE5Controller_Windows() { CloseDevice(); if(led_mutex != NULL) { CloseHandle(led_mutex); led_mutex = NULL; } } bool CreativeSoundBlasterAE5Controller_Windows::Initialize() { if(!FindDevice()) { return false; } hdaudio_device_path = FindHDAudioDevicePath(); if(hdaudio_device_path.empty()) { LOG_WARNING("[%s] Failed to find HDAudio device path", name.c_str()); return false; } return OpenDevice(); } /*---------------------------------------------------------*\ | Device Discovery | \*---------------------------------------------------------*/ bool CreativeSoundBlasterAE5Controller_Windows::FindDevice() { LOG_DEBUG("[%s] Looking for vendor 0x%04X, device 0x%04X", name.c_str(), AE5_VENDOR_ID, AE5_DEVICE_ID); HDEVINFO device_info_set = SetupDiGetClassDevs( NULL, // No class GUID TEXT("PCI"), // Enumerator NULL, // No parent window DIGCF_PRESENT | DIGCF_ALLCLASSES // Only present devices ); if(device_info_set == INVALID_HANDLE_VALUE) { LOG_ERROR("[%s] SetupDiGetClassDevs failed: %lu", name.c_str(), GetLastError()); return false; } SP_DEVINFO_DATA device_info_data; device_info_data.cbSize = sizeof(SP_DEVINFO_DATA); for(DWORD device_index = 0; SetupDiEnumDeviceInfo(device_info_set, device_index, &device_info_data); device_index++) { TCHAR hardware_id[256]; if(!SetupDiGetDeviceRegistryProperty(device_info_set, &device_info_data, SPDRP_HARDWAREID, NULL, (PBYTE)hardware_id, sizeof(hardware_id), NULL)) { continue; } std::string hw_id_str(hardware_id); std::transform(hw_id_str.begin(), hw_id_str.end(), hw_id_str.begin(), ::toupper); if(hw_id_str.find("PCI\\VEN_1102&DEV_0012") != std::string::npos) { /*---------------------------------------------*\ | Check subsystem ID to determine AE-5 variant | | Format is SUBSYS_{DEVICE_ID}{VENDOR_ID} | \*---------------------------------------------*/ if(hw_id_str.find("SUBSYS_01911102") != std::string::npos) { name = "Creative SoundBlaster AE-5 Plus"; } else if(hw_id_str.find("SUBSYS_00511102") != std::string::npos) { name = "Creative SoundBlaster AE-5"; } else { name = "Creative SoundBlaster AE-5"; LOG_WARNING("[%s] Unknown subsystem variant found in hardware ID: %s", name.c_str(), hardware_id); LOG_WARNING("[%s] Please report this to @eclipse_sol84 in the OpenRGB Discord!", name.c_str()); } LOG_INFO("[%s] Found matching device: %s", name.c_str(), hardware_id); TCHAR location_info[256]; if(SetupDiGetDeviceRegistryProperty(device_info_set, &device_info_data, SPDRP_LOCATION_INFORMATION, NULL, (PBYTE)location_info, sizeof(location_info), NULL)) { location = "PCI: " + std::string(location_info); } else { location = "PCI: " + hw_id_str; } device_found = true; SetupDiDestroyDeviceInfoList(device_info_set); LOG_INFO("[%s] Device successfully detected at %s", name.c_str(), location.c_str()); return true; } } SetupDiDestroyDeviceInfoList(device_info_set); LOG_WARNING("[%s] No matching device found", name.c_str()); return false; } std::string CreativeSoundBlasterAE5Controller_Windows::FindHDAudioDevicePath() { LOG_INFO("[%s] Searching for HDAudio device path...", name.c_str()); HKEY device_classes_key; LONG result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\DeviceClasses", 0, KEY_READ, &device_classes_key); if(result != ERROR_SUCCESS) { LOG_ERROR("[%s] Failed to open DeviceClasses registry key: %lu", name.c_str(), result); return ""; } DWORD guid_index = 0; CHAR guid_name[256]; DWORD guid_name_size; /*---------------------------------------------------------*\ | Enumerate all GUID folders in DeviceClasses | | Then enumerate device instances to find VID & PID | | Finally, check if this device has a #GPDHDA subkey | \*---------------------------------------------------------*/ while(true) { guid_name_size = sizeof(guid_name); result = RegEnumKeyExA(device_classes_key, guid_index, guid_name, &guid_name_size, NULL, NULL, NULL, NULL); if(result != ERROR_SUCCESS) break; HKEY guid_key; result = RegOpenKeyExA(device_classes_key, guid_name, 0, KEY_READ, &guid_key); if(result != ERROR_SUCCESS) { guid_index++; continue; } DWORD device_index = 0; CHAR device_name[512]; DWORD device_name_size; while(true) { device_name_size = sizeof(device_name); result = RegEnumKeyExA(guid_key, device_index, device_name, &device_name_size, NULL, NULL, NULL, NULL); if(result != ERROR_SUCCESS) break; std::string device_str(device_name); std::transform(device_str.begin(), device_str.end(), device_str.begin(), ::toupper); if(device_str.find("VEN_1102&DEV_0011") != std::string::npos) { HKEY device_key; result = RegOpenKeyExA(guid_key, device_name, 0, KEY_READ, &device_key); if(result == ERROR_SUCCESS) { HKEY gpdhda_key; result = RegOpenKeyExA(device_key, "#GPDHDA", 0, KEY_READ, &gpdhda_key); if(result == ERROR_SUCCESS) { LOG_INFO("[%s] Found HDAudio device interface", name.c_str()); RegCloseKey(gpdhda_key); RegCloseKey(device_key); /*---------------------------------------------------------*\ | Convert registry path format to device path format | \*---------------------------------------------------------*/ std::string device_path(device_name); std::transform(device_path.begin(), device_path.end(), device_path.begin(), ::tolower); if(device_path.substr(0, 4) == "##?#") { device_path = "\\\\?\\" + device_path.substr(4); } device_path += "\\gpdhda"; RegCloseKey(guid_key); RegCloseKey(device_classes_key); return device_path; } RegCloseKey(device_key); } } device_index++; } RegCloseKey(guid_key); guid_index++; } RegCloseKey(device_classes_key); LOG_WARNING("[%s] No Creative HDAudio device found in registry", name.c_str()); return ""; } bool CreativeSoundBlasterAE5Controller_Windows::OpenDevice() { if(hdaudio_device_path.empty()) { LOG_ERROR("[%s] No HDAudio device path available", name.c_str()); return false; } LOG_INFO("[%s] Opening device: %s", name.c_str(), hdaudio_device_path.c_str()); std::wstring wide_path(hdaudio_device_path.begin(), hdaudio_device_path.end()); device_handle = CreateFileW( wide_path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); if(device_handle == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); LOG_ERROR("[%s] Failed to open device: Error %lu", name.c_str(), error); if(error == ERROR_PATH_NOT_FOUND) // Error 3 { LOG_ERROR("[%s] This device requires Creative's official drivers to be installed for RGB control", name.c_str()); LOG_ERROR("[%s] Please install Creative Sound Blaster Command software or drivers from Creative's website", name.c_str()); } return false; } LOG_INFO("[%s] Device opened successfully", name.c_str()); device_opened = true; return true; } void CreativeSoundBlasterAE5Controller_Windows::CloseDevice() { if(device_handle != INVALID_HANDLE_VALUE) { CloseHandle(device_handle); device_handle = INVALID_HANDLE_VALUE; device_opened = false; LOG_INFO("[%s] Device closed", name.c_str()); } } /*---------------------------------------------------------*\ | Getters | \*---------------------------------------------------------*/ std::string CreativeSoundBlasterAE5Controller_Windows::GetDeviceLocation() { return location; } std::string CreativeSoundBlasterAE5Controller_Windows::GetDeviceName() { return name; } unsigned int CreativeSoundBlasterAE5Controller_Windows::GetLEDCount() { return AE5_INTERNAL_LED_COUNT + external_led_count; } unsigned int CreativeSoundBlasterAE5Controller_Windows::GetExternalLEDCount() { return external_led_count; } void CreativeSoundBlasterAE5Controller_Windows::SetExternalLEDCount(unsigned int count) { external_led_count = count; } /*---------------------------------------------------------*\ | LED Control | \*---------------------------------------------------------*/ void CreativeSoundBlasterAE5Controller_Windows::SetLEDColors(unsigned char led_count, unsigned char* red_values, unsigned char* green_values, unsigned char* blue_values) { if(!device_opened || device_handle == INVALID_HANDLE_VALUE) { LOG_ERROR("[%s] Device not opened, cannot set LED colors", name.c_str()); return; } if(led_count == 0) { return; } /*----------------------------------------------------------*\ | Wait for mutex to prevent conflicts with Creative software | \*----------------------------------------------------------*/ if(led_mutex != NULL) { DWORD wait_result = WaitForSingleObject(led_mutex, 5000); // 5 second timeout if(wait_result != WAIT_OBJECT_0) { LOG_WARNING("[%s] Failed to acquire LED mutex, proceeding anyway", name.c_str()); } } /*---------------------------------------------------------------*\ | Internal LEDs - Setting LEDs can sometimes | | not change them fully, we send this command twice to ensure | | they fully change colors. It's particularly visable when | | switching to dim colors or turning off the LEDs. | \*---------------------------------------------------------------*/ if(led_count > 0) { SendLEDCommand(0x03, AE5_INTERNAL_LED_COUNT, red_values, green_values, blue_values); SendLEDCommand(0x03, AE5_INTERNAL_LED_COUNT, red_values, green_values, blue_values); } /*---------------------------------------------------------*\ | External LEDs | \*---------------------------------------------------------*/ if(led_count > AE5_INTERNAL_LED_COUNT && external_led_count > 0) { SendLEDCommand(0x02, external_led_count, red_values + AE5_INTERNAL_LED_COUNT, green_values + AE5_INTERNAL_LED_COUNT, blue_values + AE5_INTERNAL_LED_COUNT); } if(led_mutex != NULL) { ReleaseMutex(led_mutex); } } bool CreativeSoundBlasterAE5Controller_Windows::SendLEDCommand(BYTE command_byte, unsigned int led_count_to_set, unsigned char* red_values, unsigned char* green_values, unsigned char* blue_values) { AE5_LED_Command cmd; memset(&cmd, 0, sizeof(AE5_LED_Command)); cmd.command = command_byte; cmd.packet_led_count = led_count_to_set; /*---------------------------------------------------------*\ | Calculate data length | \*---------------------------------------------------------*/ unsigned int data_length = led_count_to_set * 4; cmd.data_length_low = data_length & 0xFF; cmd.data_length_high = (data_length >> 8) & 0xFF; /*---------------------------------------------------------*\ | Fill LED data | \*---------------------------------------------------------*/ for(unsigned int i = 0; i < led_count_to_set; i++) { unsigned int offset = i * 4; if(command_byte == 0x02) // External WS2812B LED Strip { cmd.led_data[offset + 0] = blue_values[i]; cmd.led_data[offset + 1] = red_values[i]; cmd.led_data[offset + 2] = green_values[i]; cmd.led_data[offset + 3] = 0x00; } else // Internal APA102 LED Strip { cmd.led_data[offset + 0] = red_values[i]; cmd.led_data[offset + 1] = green_values[i]; cmd.led_data[offset + 2] = blue_values[i]; cmd.led_data[offset + 3] = 0xFF; } } AE5_LED_Command output_cmd; memset(&output_cmd, 0, sizeof(AE5_LED_Command)); DWORD bytes_returned = 0; BOOL result = DeviceIoControl( device_handle, 0x77772400, // Custom IOCTL command from Creative Lab's drivers &cmd, sizeof(AE5_LED_Command), &output_cmd, sizeof(AE5_LED_Command), &bytes_returned, NULL ); if(!result) { DWORD error = GetLastError(); LOG_ERROR("[%s] DeviceIoControl failed: Error %lu", name.c_str(), error); return false; } return true; }