A game about forced loneliness, made by TACStudios
at master 480 lines 19 kB view raw
1using System; 2using System.Collections.Generic; 3 4////TODO: array support 5////TODO: delimiter support 6////TODO: designator support 7 8#pragma warning disable CS0649 9namespace UnityEngine.InputSystem.HID 10{ 11 /// <summary> 12 /// Turns binary HID descriptors into <see cref="HID.HIDDeviceDescriptor"/> instances. 13 /// </summary> 14 /// <remarks> 15 /// For information about the format, see the <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf"> 16 /// Device Class Definition for Human Interface Devices</a> section 6.2.2. 17 /// </remarks> 18 internal static class HIDParser 19 { 20 /// <summary> 21 /// Parse a HID report descriptor as defined by section 6.2.2 of the 22 /// <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf">HID 23 /// specification</a> and add the elements and collections from the 24 /// descriptor to the given <paramref name="deviceDescriptor"/>. 25 /// </summary> 26 /// <param name="buffer">Buffer containing raw HID report descriptor.</param> 27 /// <param name="deviceDescriptor">HID device descriptor to complete with the information 28 /// from the report descriptor. Elements and collections will get added to this descriptor.</param> 29 /// <returns>True if the report descriptor was successfully parsed.</returns> 30 /// <remarks> 31 /// Will also set <see cref="HID.HIDDeviceDescriptor.inputReportSize"/>, 32 /// <see cref="HID.HIDDeviceDescriptor.outputReportSize"/>, and 33 /// <see cref="HID.HIDDeviceDescriptor.featureReportSize"/>. 34 /// </remarks> 35 public static unsafe bool ParseReportDescriptor(byte[] buffer, ref HID.HIDDeviceDescriptor deviceDescriptor) 36 { 37 if (buffer == null) 38 throw new ArgumentNullException(nameof(buffer)); 39 40 fixed(byte* bufferPtr = buffer) 41 { 42 return ParseReportDescriptor(bufferPtr, buffer.Length, ref deviceDescriptor); 43 } 44 } 45 46 public unsafe static bool ParseReportDescriptor(byte* bufferPtr, int bufferLength, ref HID.HIDDeviceDescriptor deviceDescriptor) 47 { 48 // Item state. 49 var localItemState = new HIDItemStateLocal(); 50 var globalItemState = new HIDItemStateGlobal(); 51 52 // Lists where we accumulate the data from the HID items. 53 var reports = new List<HIDReportData>(); 54 var elements = new List<HID.HIDElementDescriptor>(); 55 var collections = new List<HID.HIDCollectionDescriptor>(); 56 var currentCollection = -1; 57 58 // Parse the linear list of items. 59 var endPtr = bufferPtr + bufferLength; 60 var currentPtr = bufferPtr; 61 while (currentPtr < endPtr) 62 { 63 var firstByte = *currentPtr; 64 65 ////TODO 66 if (firstByte == 0xFE) 67 throw new NotImplementedException("long item support"); 68 69 // Read item header. 70 var itemSize = (byte)(firstByte & 0x3); 71 var itemTypeAndTag = (byte)(firstByte & 0xFC); 72 ++currentPtr; 73 74 // Process item. 75 switch (itemTypeAndTag) 76 { 77 // ------------ Global Items -------------- 78 // These set item state permanently until it is reset. 79 80 // Usage Page 81 case (int)HIDItemTypeAndTag.UsagePage: 82 globalItemState.usagePage = ReadData(itemSize, currentPtr, endPtr); 83 break; 84 85 // Report Count 86 case (int)HIDItemTypeAndTag.ReportCount: 87 globalItemState.reportCount = ReadData(itemSize, currentPtr, endPtr); 88 break; 89 90 // Report Size 91 case (int)HIDItemTypeAndTag.ReportSize: 92 globalItemState.reportSize = ReadData(itemSize, currentPtr, endPtr); 93 break; 94 95 // Report ID 96 case (int)HIDItemTypeAndTag.ReportID: 97 globalItemState.reportId = ReadData(itemSize, currentPtr, endPtr); 98 break; 99 100 // Logical Minimum 101 case (int)HIDItemTypeAndTag.LogicalMinimum: 102 globalItemState.logicalMinimum = ReadData(itemSize, currentPtr, endPtr); 103 break; 104 105 // Logical Maximum 106 case (int)HIDItemTypeAndTag.LogicalMaximum: 107 globalItemState.logicalMaximum = ReadData(itemSize, currentPtr, endPtr); 108 break; 109 110 // Physical Minimum 111 case (int)HIDItemTypeAndTag.PhysicalMinimum: 112 globalItemState.physicalMinimum = ReadData(itemSize, currentPtr, endPtr); 113 break; 114 115 // Physical Maximum 116 case (int)HIDItemTypeAndTag.PhysicalMaximum: 117 globalItemState.physicalMaximum = ReadData(itemSize, currentPtr, endPtr); 118 break; 119 120 // Unit Exponent 121 case (int)HIDItemTypeAndTag.UnitExponent: 122 globalItemState.unitExponent = ReadData(itemSize, currentPtr, endPtr); 123 break; 124 125 // Unit 126 case (int)HIDItemTypeAndTag.Unit: 127 globalItemState.unit = ReadData(itemSize, currentPtr, endPtr); 128 break; 129 130 // ------------ Local Items -------------- 131 // These set the state for the very next elements to be generated. 132 133 // Usage 134 case (int)HIDItemTypeAndTag.Usage: 135 localItemState.SetUsage(ReadData(itemSize, currentPtr, endPtr)); 136 break; 137 138 // Usage Minimum 139 case (int)HIDItemTypeAndTag.UsageMinimum: 140 localItemState.usageMinimum = ReadData(itemSize, currentPtr, endPtr); 141 break; 142 143 // Usage Maximum 144 case (int)HIDItemTypeAndTag.UsageMaximum: 145 localItemState.usageMaximum = ReadData(itemSize, currentPtr, endPtr); 146 break; 147 148 // ------------ Main Items -------------- 149 // These emit things into the descriptor based on the local and global item state. 150 151 // Collection 152 case (int)HIDItemTypeAndTag.Collection: 153 154 // Start new collection. 155 var parentCollection = currentCollection; 156 currentCollection = collections.Count; 157 collections.Add(new HID.HIDCollectionDescriptor 158 { 159 type = (HID.HIDCollectionType)ReadData(itemSize, currentPtr, endPtr), 160 parent = parentCollection, 161 usagePage = globalItemState.GetUsagePage(0, ref localItemState), 162 usage = localItemState.GetUsage(0), 163 firstChild = elements.Count 164 }); 165 166 HIDItemStateLocal.Reset(ref localItemState); 167 break; 168 169 // EndCollection 170 case (int)HIDItemTypeAndTag.EndCollection: 171 if (currentCollection == -1) 172 return false; 173 174 // Close collection. 175 var collection = collections[currentCollection]; 176 collection.childCount = elements.Count - collection.firstChild; 177 collections[currentCollection] = collection; 178 179 // Switch back to parent collection (if any). 180 currentCollection = collection.parent; 181 182 HIDItemStateLocal.Reset(ref localItemState); 183 break; 184 185 // Input/Output/Feature 186 case (int)HIDItemTypeAndTag.Input: 187 case (int)HIDItemTypeAndTag.Output: 188 case (int)HIDItemTypeAndTag.Feature: 189 190 // Determine report type. 191 var reportType = itemTypeAndTag == (int)HIDItemTypeAndTag.Input 192 ? HID.HIDReportType.Input 193 : itemTypeAndTag == (int)HIDItemTypeAndTag.Output 194 ? HID.HIDReportType.Output 195 : HID.HIDReportType.Feature; 196 197 // Find report. 198 var reportIndex = HIDReportData.FindOrAddReport(globalItemState.reportId, reportType, reports); 199 var report = reports[reportIndex]; 200 201 // If we have a report ID, then reports start with an 8 byte report ID. 202 // Shift our offsets accordingly. 203 if (report.currentBitOffset == 0 && globalItemState.reportId.HasValue) 204 report.currentBitOffset = 8; 205 206 // Add elements to report. 207 var reportCount = globalItemState.reportCount.GetValueOrDefault(1); 208 var flags = ReadData(itemSize, currentPtr, endPtr); 209 for (var i = 0; i < reportCount; ++i) 210 { 211 var element = new HID.HIDElementDescriptor 212 { 213 usage = localItemState.GetUsage(i) & 0xFFFF, // Mask off usage page, if set. 214 usagePage = globalItemState.GetUsagePage(i, ref localItemState), 215 reportType = reportType, 216 reportSizeInBits = globalItemState.reportSize.GetValueOrDefault(8), 217 reportOffsetInBits = report.currentBitOffset, 218 reportId = globalItemState.reportId.GetValueOrDefault(1), 219 flags = (HID.HIDElementFlags)flags, 220 logicalMin = globalItemState.logicalMinimum.GetValueOrDefault(0), 221 logicalMax = globalItemState.logicalMaximum.GetValueOrDefault(0), 222 physicalMin = globalItemState.GetPhysicalMin(), 223 physicalMax = globalItemState.GetPhysicalMax(), 224 unitExponent = globalItemState.unitExponent.GetValueOrDefault(0), 225 unit = globalItemState.unit.GetValueOrDefault(0), 226 }; 227 report.currentBitOffset += element.reportSizeInBits; 228 elements.Add(element); 229 } 230 reports[reportIndex] = report; 231 232 HIDItemStateLocal.Reset(ref localItemState); 233 break; 234 } 235 236 if (itemSize == 3) 237 currentPtr += 4; 238 else 239 currentPtr += itemSize; 240 } 241 242 deviceDescriptor.elements = elements.ToArray(); 243 deviceDescriptor.collections = collections.ToArray(); 244 245 // Set usage and usage page on device descriptor to what's 246 // on the toplevel application collection. 247 foreach (var collection in collections) 248 { 249 if (collection.parent == -1 && collection.type == HID.HIDCollectionType.Application) 250 { 251 deviceDescriptor.usage = collection.usage; 252 deviceDescriptor.usagePage = collection.usagePage; 253 break; 254 } 255 } 256 257 return true; 258 } 259 260 private unsafe static int ReadData(int itemSize, byte* currentPtr, byte* endPtr) 261 { 262 if (itemSize == 0) 263 return 0; 264 265 // Read byte. 266 if (itemSize == 1) 267 { 268 if (currentPtr >= endPtr) 269 return 0; 270 return *currentPtr; 271 } 272 273 // Read short. 274 if (itemSize == 2) 275 { 276 if (currentPtr + 2 >= endPtr) 277 return 0; 278 var data1 = *currentPtr; 279 var data2 = *(currentPtr + 1); 280 return (data2 << 8) | data1; 281 } 282 283 // Read int. 284 if (itemSize == 3) // Item size 3 means 4 bytes! 285 { 286 if (currentPtr + 4 >= endPtr) 287 return 0; 288 289 var data1 = *currentPtr; 290 var data2 = *(currentPtr + 1); 291 var data3 = *(currentPtr + 2); 292 var data4 = *(currentPtr + 3); 293 294 return (data4 << 24) | (data3 << 24) | (data2 << 8) | data1; 295 } 296 297 Debug.Assert(false, "Should not reach here"); 298 return 0; 299 } 300 301 private struct HIDReportData 302 { 303 public int reportId; 304 public HID.HIDReportType reportType; 305 public int currentBitOffset; 306 307 public static int FindOrAddReport(int? reportId, HID.HIDReportType reportType, List<HIDReportData> reports) 308 { 309 var id = 1; 310 if (reportId.HasValue) 311 id = reportId.Value; 312 313 for (var i = 0; i < reports.Count; ++i) 314 { 315 if (reports[i].reportId == id && reports[i].reportType == reportType) 316 return i; 317 } 318 319 reports.Add(new HIDReportData 320 { 321 reportId = id, 322 reportType = reportType 323 }); 324 325 return reports.Count - 1; 326 } 327 } 328 329 // All types and tags with size bits (low order two bits) masked out (i.e. being 0). 330 private enum HIDItemTypeAndTag 331 { 332 Input = 0x80, 333 Output = 0x90, 334 Feature = 0xB0, 335 Collection = 0xA0, 336 EndCollection = 0xC0, 337 UsagePage = 0x04, 338 LogicalMinimum = 0x14, 339 LogicalMaximum = 0x24, 340 PhysicalMinimum = 0x34, 341 PhysicalMaximum = 0x44, 342 UnitExponent = 0x54, 343 Unit = 0x64, 344 ReportSize = 0x74, 345 ReportID = 0x84, 346 ReportCount = 0x94, 347 Push = 0xA4, 348 Pop = 0xB4, 349 Usage = 0x08, 350 UsageMinimum = 0x18, 351 UsageMaximum = 0x28, 352 DesignatorIndex = 0x38, 353 DesignatorMinimum = 0x48, 354 DesignatorMaximum = 0x58, 355 StringIndex = 0x78, 356 StringMinimum = 0x88, 357 StringMaximum = 0x98, 358 Delimiter = 0xA8, 359 } 360 361 // State that needs to be defined for each main item separately. 362 // See section 6.2.2.8 363 private struct HIDItemStateLocal 364 { 365 public int? usage; 366 public int? usageMinimum; 367 public int? usageMaximum; 368 public int? designatorIndex; 369 public int? designatorMinimum; 370 public int? designatorMaximum; 371 public int? stringIndex; 372 public int? stringMinimum; 373 public int? stringMaximum; 374 375 public List<int> usageList; 376 377 // Wipe state but preserve usageList allocation. 378 public static void Reset(ref HIDItemStateLocal state) 379 { 380 var usageList = state.usageList; 381 state = new HIDItemStateLocal(); 382 if (usageList != null) 383 { 384 usageList.Clear(); 385 state.usageList = usageList; 386 } 387 } 388 389 // Usage can be set repeatedly to provide an enumeration of usages. 390 public void SetUsage(int value) 391 { 392 if (usage.HasValue) 393 { 394 if (usageList == null) 395 usageList = new List<int>(); 396 usageList.Add(usage.Value); 397 } 398 usage = value; 399 } 400 401 // Get usage for Nth element in [0-reportCount] list. 402 public int GetUsage(int index) 403 { 404 // If we have minimum and maximum usage, interpolate between that. 405 if (usageMinimum.HasValue && usageMaximum.HasValue) 406 { 407 var min = usageMinimum.Value; 408 var max = usageMaximum.Value; 409 410 var range = max - min; 411 if (range < 0) 412 return 0; 413 if (index >= range) 414 return max; 415 return min + index; 416 } 417 418 // If we have a list of usages, index into that. 419 if (usageList != null && usageList.Count > 0) 420 { 421 var usageCount = usageList.Count; 422 if (index >= usageCount) 423 return usage.Value; 424 425 return usageList[index]; 426 } 427 428 if (usage.HasValue) 429 return usage.Value; 430 431 ////TODO: min/max 432 433 return 0; 434 } 435 } 436 437 // State that is carried over from main item to main item. 438 // See section 6.2.2.7 439 private struct HIDItemStateGlobal 440 { 441 public int? usagePage; 442 public int? logicalMinimum; 443 public int? logicalMaximum; 444 public int? physicalMinimum; 445 public int? physicalMaximum; 446 public int? unitExponent; 447 public int? unit; 448 public int? reportSize; 449 public int? reportCount; 450 public int? reportId; 451 452 public HID.UsagePage GetUsagePage(int index, ref HIDItemStateLocal localItemState) 453 { 454 if (!usagePage.HasValue) 455 { 456 var usage = localItemState.GetUsage(index); 457 return (HID.UsagePage)(usage >> 16); 458 } 459 460 return (HID.UsagePage)usagePage.Value; 461 } 462 463 public int GetPhysicalMin() 464 { 465 if (physicalMinimum == null || physicalMaximum == null || 466 (physicalMinimum.Value == 0 && physicalMaximum.Value == 0)) 467 return logicalMinimum.GetValueOrDefault(0); 468 return physicalMinimum.Value; 469 } 470 471 public int GetPhysicalMax() 472 { 473 if (physicalMinimum == null || physicalMaximum == null || 474 (physicalMinimum.Value == 0 && physicalMaximum.Value == 0)) 475 return logicalMaximum.GetValueOrDefault(0); 476 return physicalMaximum.Value; 477 } 478 } 479 } 480}