#include "hid_device_analyzer.h" #include #include #include #include #include #include #include #include #if defined(_WIN32) #include #include #include #pragma comment(lib, "hid.lib") #pragma comment(lib, "setupapi.lib") #elif defined(__linux__) || defined(__APPLE__) // Linux/macOS specific headers would go here #endif // HID Report Descriptor Item Type definitions #define HID_ITEM_TYPE_MAIN 0x0 #define HID_ITEM_TYPE_GLOBAL 0x1 #define HID_ITEM_TYPE_LOCAL 0x2 #define HID_ITEM_TYPE_RESERVED 0x3 // HID Report Descriptor Main Item Tags #define HID_MAIN_ITEM_TAG_INPUT 0x8 #define HID_MAIN_ITEM_TAG_OUTPUT 0x9 #define HID_MAIN_ITEM_TAG_FEATURE 0xB #define HID_MAIN_ITEM_TAG_COLLECTION 0xA #define HID_MAIN_ITEM_TAG_END_COLLECTION 0xC // HID Report Descriptor Global Item Tags #define HID_GLOBAL_ITEM_TAG_USAGE_PAGE 0x0 #define HID_GLOBAL_ITEM_TAG_LOGICAL_MIN 0x1 #define HID_GLOBAL_ITEM_TAG_LOGICAL_MAX 0x2 #define HID_GLOBAL_ITEM_TAG_PHYSICAL_MIN 0x3 #define HID_GLOBAL_ITEM_TAG_PHYSICAL_MAX 0x4 #define HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT 0x5 #define HID_GLOBAL_ITEM_TAG_UNIT 0x6 #define HID_GLOBAL_ITEM_TAG_REPORT_SIZE 0x7 #define HID_GLOBAL_ITEM_TAG_REPORT_ID 0x8 #define HID_GLOBAL_ITEM_TAG_REPORT_COUNT 0x9 // HID Report Descriptor Local Item Tags #define HID_LOCAL_ITEM_TAG_USAGE 0x0 #define HID_LOCAL_ITEM_TAG_USAGE_MIN 0x1 #define HID_LOCAL_ITEM_TAG_USAGE_MAX 0x2 // HID Usage Pages #define HID_USAGE_PAGE_GENERIC_DESKTOP 0x01 #define HID_USAGE_PAGE_SENSOR 0x20 // HID Usages - Generic Desktop Page #define HID_USAGE_GENERIC_X 0x30 #define HID_USAGE_GENERIC_Y 0x31 #define HID_USAGE_GENERIC_Z 0x32 // Valve's Vendor ID #define VALVE_VID 0x28DE // Maximum buffer sizes #define MAX_REPORT_SIZE 1024 #define MAX_DESCRIPTOR_SIZE 4096 HidDeviceAnalyzer::HidDeviceAnalyzer() : m_deviceHandle(nullptr) , m_vendorId(0) , m_productId(0) , m_initialized(false) { } HidDeviceAnalyzer::~HidDeviceAnalyzer() { Shutdown(); } bool HidDeviceAnalyzer::Initialize() { if (m_initialized) { return true; } std::cout << "Initializing HID Device Analyzer..." << std::endl; #if defined(_WIN32) // Initialize Windows HID API // Nothing specific needed for Windows #elif defined(__linux__) || defined(__APPLE__) // Initialize Linux/macOS HID API // Implementation would go here #endif m_initialized = true; std::cout << "HID Device Analyzer initialized successfully." << std::endl; return true; } void HidDeviceAnalyzer::Shutdown() { if (!m_initialized) { return; } // Close any open device CloseDevice(); m_initialized = false; std::cout << "HID Device Analyzer shut down." << std::endl; } std::vector HidDeviceAnalyzer::EnumerateHidDevices(uint16_t filterVendorId) { std::vector devices; if (!m_initialized) { std::cerr << "Error: HID Device Analyzer not initialized." << std::endl; return devices; } std::cout << "Enumerating HID devices" << (filterVendorId ? " with VID 0x" + std::to_string(filterVendorId) : "") << "..." << std::endl; #if defined(_WIN32) // Get the GUID for HID devices GUID hidGuid; HidD_GetHidGuid(&hidGuid); // Get a handle to the device information set HDEVINFO deviceInfoSet = SetupDiGetClassDevs(&hidGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (deviceInfoSet == INVALID_HANDLE_VALUE) { std::cerr << "Error: Failed to get device information set." << std::endl; return devices; } // Enumerate all HID devices SP_DEVICE_INTERFACE_DATA deviceInterfaceData; deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); // Enumerate all HID devices for (DWORD deviceIndex = 0; SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &hidGuid, deviceIndex, &deviceInterfaceData); deviceIndex++) { // Get the device path DWORD requiredSize = 0; SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, NULL, 0, &requiredSize, NULL); PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(requiredSize); deviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, deviceInterfaceDetailData, requiredSize, NULL, NULL)) { // Get the device path std::string devicePath = (char*)deviceInterfaceDetailData->DevicePath; // Open the device HANDLE deviceHandle = CreateFile(deviceInterfaceDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (deviceHandle != INVALID_HANDLE_VALUE) { // Get the device attributes HIDD_ATTRIBUTES deviceAttributes; deviceAttributes.Size = sizeof(HIDD_ATTRIBUTES); if (HidD_GetAttributes(deviceHandle, &deviceAttributes)) { // Check if we need to filter by vendor ID if (filterVendorId == 0 || deviceAttributes.VendorID == filterVendorId) { HidDeviceInfo deviceInfo; deviceInfo.path = devicePath; deviceInfo.vendorId = deviceAttributes.VendorID; deviceInfo.productId = deviceAttributes.ProductID; // Get the product string wchar_t productString[256] = {0}; if (HidD_GetProductString(deviceHandle, productString, sizeof(productString))) { // Convert to ASCII char productStringA[256] = {0}; WideCharToMultiByte(CP_ACP, 0, productString, -1, productStringA, sizeof(productStringA), NULL, NULL); deviceInfo.product = productStringA; } // Get the manufacturer string wchar_t manufacturerString[256] = {0}; if (HidD_GetManufacturerString(deviceHandle, manufacturerString, sizeof(manufacturerString))) { // Convert to ASCII char manufacturerStringA[256] = {0}; WideCharToMultiByte(CP_ACP, 0, manufacturerString, -1, manufacturerStringA, sizeof(manufacturerStringA), NULL, NULL); deviceInfo.manufacturer = manufacturerStringA; } // Get the serial number string wchar_t serialNumberString[256] = {0}; if (HidD_GetSerialNumberString(deviceHandle, serialNumberString, sizeof(serialNumberString))) { // Convert to ASCII char serialNumberStringA[256] = {0}; WideCharToMultiByte(CP_ACP, 0, serialNumberString, -1, serialNumberStringA, sizeof(serialNumberStringA), NULL, NULL); deviceInfo.serialNumber = serialNumberStringA; } // Construct a name from the available information std::stringstream ss; ss << "VID: 0x" << std::hex << std::setw(4) << std::setfill('0') << deviceInfo.vendorId << ", PID: 0x" << std::hex << std::setw(4) << std::setfill('0') << deviceInfo.productId; if (!deviceInfo.manufacturer.empty()) ss << ", Manufacturer: " << deviceInfo.manufacturer; if (!deviceInfo.product.empty()) ss << ", Product: " << deviceInfo.product; deviceInfo.name = ss.str(); devices.push_back(deviceInfo); } } CloseHandle(deviceHandle); } } free(deviceInterfaceDetailData); } // Clean up SetupDiDestroyDeviceInfoList(deviceInfoSet); #elif defined(__linux__) || defined(__APPLE__) // Linux/macOS implementation would go here #endif std::cout << "Found " << devices.size() << " HID devices." << std::endl; return devices; } bool HidDeviceAnalyzer::OpenDevice(const std::string& devicePath) { if (!m_initialized) { std::cerr << "Error: HID Device Analyzer not initialized." << std::endl; return false; } // Close any previously open device CloseDevice(); std::cout << "Opening HID device: " << devicePath << std::endl; #if defined(_WIN32) // Open the device m_deviceHandle = CreateFileA(devicePath.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (m_deviceHandle == INVALID_HANDLE_VALUE) { std::cerr << "Error: Failed to open device. Error code: " << GetLastError() << std::endl; m_deviceHandle = nullptr; return false; } // Get the device attributes HIDD_ATTRIBUTES deviceAttributes; deviceAttributes.Size = sizeof(HIDD_ATTRIBUTES); if (HidD_GetAttributes(m_deviceHandle, &deviceAttributes)) { m_vendorId = deviceAttributes.VendorID; m_productId = deviceAttributes.ProductID; } // Get the product string wchar_t productString[256] = {0}; if (HidD_GetProductString(m_deviceHandle, productString, sizeof(productString))) { // Convert to ASCII char productStringA[256] = {0}; WideCharToMultiByte(CP_ACP, 0, productString, -1, productStringA, sizeof(productStringA), NULL, NULL); m_deviceName = productStringA; } m_devicePath = devicePath; #elif defined(__linux__) || defined(__APPLE__) // Linux/macOS implementation would go here #endif std::cout << "Device opened successfully: VID=0x" << std::hex << std::setw(4) << std::setfill('0') << m_vendorId << ", PID=0x" << std::hex << std::setw(4) << std::setfill('0') << m_productId << ", Name=" << m_deviceName << std::endl; return true; } void HidDeviceAnalyzer::CloseDevice() { if (m_deviceHandle) { std::cout << "Closing HID device: " << m_devicePath << std::endl; #if defined(_WIN32) CloseHandle(m_deviceHandle); #elif defined(__linux__) || defined(__APPLE__) // Linux/macOS implementation would go here #endif m_deviceHandle = nullptr; m_devicePath.clear(); m_deviceName.clear(); m_vendorId = 0; m_productId = 0; m_rawReportDescriptor.clear(); } } bool HidDeviceAnalyzer::IsDeviceOpen() const { return m_deviceHandle != nullptr; } std::vector HidDeviceAnalyzer::GetReportDescriptor() { std::vector items; if (!IsDeviceOpen()) { std::cerr << "Error: No device is open." << std::endl; return items; } // If we haven't already read the report descriptor, read it now if (m_rawReportDescriptor.empty()) { #if defined(_WIN32) // Get the report descriptor size PHIDP_PREPARSED_DATA preparsedData = NULL; if (!HidD_GetPreparsedData(m_deviceHandle, &preparsedData)) { std::cerr << "Error: Failed to get preparsed data." << std::endl; return items; } // Get the capabilities HIDP_CAPS caps; if (HidP_GetCaps(preparsedData, &caps) != HIDP_STATUS_SUCCESS) { std::cerr << "Error: Failed to get capabilities." << std::endl; HidD_FreePreparsedData(preparsedData); return items; } // Windows doesn't provide a direct way to get the raw report descriptor // We'll use a fixed size buffer for now m_rawReportDescriptor.resize(MAX_DESCRIPTOR_SIZE); // Note: HidD_GetReportDescriptor doesn't exist in the Windows API // We would need to use HidP_GetData or other functions to get report data // For simplicity, we'll just log this limitation std::cerr << "Note: Windows API doesn't provide direct access to raw report descriptors." << std::endl; m_rawReportDescriptor.clear(); HidD_FreePreparsedData(preparsedData); return items; } HidD_FreePreparsedData(preparsedData); #elif defined(__linux__) || defined(__APPLE__) // Linux/macOS implementation would go here #endif } // Parse the report descriptor ParseReportDescriptor(); // TODO: Implement parsing of the raw report descriptor into items // This is a complex task that requires understanding the HID report descriptor format // For now, we'll just return an empty vector return items; } void HidDeviceAnalyzer::ParseReportDescriptor() { // This is a placeholder for the actual implementation // Parsing HID report descriptors is complex and beyond the scope of this example std::cout << "Raw report descriptor size: " << m_rawReportDescriptor.size() << " bytes" << std::endl; // Print the raw descriptor as hex std::cout << "Raw report descriptor: "; for (size_t i = 0; i < m_rawReportDescriptor.size(); i++) { std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)m_rawReportDescriptor[i] << " "; } std::cout << std::dec << std::endl; } void HidDeviceAnalyzer::PrintReportDescriptor() { if (!IsDeviceOpen()) { std::cerr << "Error: No device is open." << std::endl; return; } // Get the report descriptor GetReportDescriptor(); // Print the raw descriptor std::cout << "Report Descriptor for device: " << m_deviceName << std::endl; std::cout << "VID: 0x" << std::hex << std::setw(4) << std::setfill('0') << m_vendorId << ", PID: 0x" << std::hex << std::setw(4) << std::setfill('0') << m_productId << std::endl; // Print the raw descriptor as hex std::cout << "Raw descriptor (" << m_rawReportDescriptor.size() << " bytes):" << std::endl; for (size_t i = 0; i < m_rawReportDescriptor.size(); i++) { if (i % 16 == 0) { std::cout << std::endl << std::hex << std::setw(4) << std::setfill('0') << i << ": "; } std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)m_rawReportDescriptor[i] << " "; } std::cout << std::dec << std::endl; } HidReport HidDeviceAnalyzer::ReadReport(uint8_t reportId) { HidReport report; report.reportId = reportId; report.data.resize(MAX_REPORT_SIZE); report.dataLength = 0; if (!IsDeviceOpen()) { std::cerr << "Error: No device is open." << std::endl; return report; } #if defined(_WIN32) // Read a report from the device report.data[0] = reportId; // Set the report ID DWORD bytesRead = 0; if (ReadFile(m_deviceHandle, report.data.data(), report.data.size(), &bytesRead, NULL)) { if (bytesRead > 0) { report.dataLength = bytesRead; // Resize the data vector to the actual size report.data.resize(bytesRead); } else { std::cerr << "Error: No data read from device." << std::endl; } } else { std::cerr << "Error: Failed to read from device. Error code: " << GetLastError() << std::endl; } #elif defined(__linux__) || defined(__APPLE__) // Linux/macOS implementation would go here #endif return report; } std::vector HidDeviceAnalyzer::ReadReports(int count, uint8_t reportId) { std::vector reports; if (!IsDeviceOpen()) { std::cerr << "Error: No device is open." << std::endl; return reports; } std::cout << "Reading " << count << " reports with ID 0x" << std::hex << (int)reportId << std::dec << "..." << std::endl; for (int i = 0; i < count; i++) { HidReport report = ReadReport(reportId); if (report.dataLength > 0) { reports.push_back(report); } // Sleep a bit to avoid flooding the device std::this_thread::sleep_for(std::chrono::milliseconds(10)); } std::cout << "Read " << reports.size() << " reports." << std::endl; return reports; } void HidDeviceAnalyzer::PrintReport(const HidReport& report) { std::cout << "Report ID: 0x" << std::hex << (int)report.reportId << std::dec << std::endl; std::cout << "Data Length: " << report.dataLength << " bytes" << std::endl; // Print the data as hex std::cout << "Data: "; for (size_t i = 0; i < report.dataLength; i++) { if (i % 16 == 0 && i > 0) { std::cout << std::endl << " "; } std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)report.data[i] << " "; } std::cout << std::dec << std::endl; // Try to interpret the data as IMU data if (report.dataLength >= 13) // Assuming at least 1 byte for report ID and 12 bytes for IMU data { // Extract accelerometer data (assuming 16-bit values at offsets 1, 3, 5) int16_t accelX = (report.data[2] << 8) | report.data[1]; int16_t accelY = (report.data[4] << 8) | report.data[3]; int16_t accelZ = (report.data[6] << 8) | report.data[5]; // Extract gyroscope data (assuming 16-bit values at offsets 7, 9, 11) int16_t gyroX = (report.data[8] << 8) | report.data[7]; int16_t gyroY = (report.data[10] << 8) | report.data[9]; int16_t gyroZ = (report.data[12] << 8) | report.data[11]; std::cout << "Possible IMU data interpretation:" << std::endl; std::cout << " Accelerometer: X=" << accelX << ", Y=" << accelY << ", Z=" << accelZ << std::endl; std::cout << " Gyroscope: X=" << gyroX << ", Y=" << gyroY << ", Z=" << gyroZ << std::endl; // Also try different offsets std::cout << "Alternative interpretations:" << std::endl; // Try offsets 2, 4, 6 for accelerometer and 8, 10, 12 for gyroscope if (report.dataLength >= 14) { accelX = (report.data[3] << 8) | report.data[2]; accelY = (report.data[5] << 8) | report.data[4]; accelZ = (report.data[7] << 8) | report.data[6]; gyroX = (report.data[9] << 8) | report.data[8]; gyroY = (report.data[11] << 8) | report.data[10]; gyroZ = (report.data[13] << 8) | report.data[12]; std::cout << " Offset +1: Accel(X=" << accelX << ", Y=" << accelY << ", Z=" << accelZ << "), Gyro(X=" << gyroX << ", Y=" << gyroY << ", Z=" << gyroZ << ")" << std::endl; } } } void HidDeviceAnalyzer::AnalyzeReports(const std::vector& reports) { if (reports.empty()) { std::cerr << "Error: No reports to analyze." << std::endl; return; } std::cout << "Analyzing " << reports.size() << " reports..." << std::endl; // Group reports by report ID // Group reports by report ID std::vector>> reportGroups; // First, collect all unique report IDs std::vector uniqueReportIds; for (const auto& report : reports) { if (std::find(uniqueReportIds.begin(), uniqueReportIds.end(), report.reportId) == uniqueReportIds.end()) { uniqueReportIds.push_back(report.reportId); } } // Then group reports by ID for (uint8_t reportId : uniqueReportIds) { std::vector reportsWithId; for (const auto& report : reports) { if (report.reportId == reportId) { reportsWithId.push_back(report); } } reportGroups.push_back(std::make_pair(reportId, reportsWithId)); } // Analyze each group of reports for (const auto& pair : reportGroups) { uint8_t reportId = pair.first; const auto& reportsWithID = pair.second; std::cout << "Report ID 0x" << std::hex << (int)reportId << std::dec << " (" << reportsWithID.size() << " reports)" << std::endl; // Check if all reports have the same length bool sameLengths = true; size_t firstLength = reportsWithID[0].dataLength; for (const auto& report : reportsWithID) { if (report.dataLength != firstLength) { sameLengths = false; break; } } std::cout << " All reports have same length: " << (sameLengths ? "Yes" : "No") << std::endl; if (sameLengths) { std::cout << " Report length: " << firstLength << " bytes" << std::endl; } // Analyze byte patterns if (reportsWithID.size() >= 2 && sameLengths) { std::vector byteChanges(firstLength, false); // Check which bytes change between reports for (size_t i = 1; i < reportsWithID.size(); i++) { for (size_t j = 0; j < firstLength; j++) { if (reportsWithID[i].data[j] != reportsWithID[0].data[j]) { byteChanges[j] = true; } } } // Print which bytes change std::cout << " Bytes that change between reports: "; for (size_t j = 0; j < firstLength; j++) { if (byteChanges[j]) { std::cout << j << " "; } } std::cout << std::endl; // Try to identify patterns of 16-bit values (common for IMU data) std::cout << " Possible 16-bit value patterns:" << std::endl; for (size_t j = 0; j < firstLength - 1; j++) { if (byteChanges[j] && byteChanges[j+1]) { // This could be a 16-bit value std::cout << " Bytes " << j << "-" << (j+1) << " could be a 16-bit value" << std::endl; // Print the values from a few reports std::cout << " Values from first 3 reports: "; for (size_t i = 0; i < (reportsWithID.size() < 3 ? reportsWithID.size() : 3); i++) { int16_t value = (reportsWithID[i].data[j+1] << 8) | reportsWithID[i].data[j]; std::cout << value << " "; } std::cout << std::endl; } } // Try to identify accelerometer and gyroscope data // Typically, accelerometer and gyroscope data are 3 consecutive 16-bit values each std::cout << " Possible accelerometer/gyroscope patterns:" << std::endl; for (size_t j = 0; j < firstLength - 5; j++) { if (byteChanges[j] && byteChanges[j+1] && byteChanges[j+2] && byteChanges[j+3] && byteChanges[j+4] && byteChanges[j+5]) { // This could be 3 consecutive 16-bit values (X, Y, Z) std::cout << " Bytes " << j << "-" << (j+5) << " could be X, Y, Z values" << std::endl; // Print the values from a few reports std::cout << " Values from first 3 reports:" << std::endl; for (size_t i = 0; i < (reportsWithID.size() < 3 ? reportsWithID.size() : 3); i++) { int16_t x = (reportsWithID[i].data[j+1] << 8) | reportsWithID[i].data[j]; int16_t y = (reportsWithID[i].data[j+3] << 8) | reportsWithID[i].data[j+2]; int16_t z = (reportsWithID[i].data[j+5] << 8) | reportsWithID[i].data[j+4]; std::cout << " Report " << i << ": X=" << x << ", Y=" << y << ", Z=" << z << std::endl; } } } } } }