git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

JoyKit/JOYController.m

      1 #import "JOYController.h"
      2 #import "JOYMultiplayerController.h"
      3 #import "JOYElement.h"
      4 #import "JOYSubElement.h"
      5 #import "JOYFullReportElement.h"
      6 #import "JOYButton.h"
      7 #import "JOYEmulatedButton.h"
      8 #import <IOKit/hid/IOHIDLib.h>
      9 
     10 #import <AppKit/AppKit.h>
     11 extern NSTextField *globalDebugField;
     12 
     13 #define PWM_RESOLUTION 16
     14 
     15 static NSString const *JOYAxisGroups = @"JOYAxisGroups";
     16 static NSString const *JOYReportIDFilters = @"JOYReportIDFilters";
     17 static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping";
     18 static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping";
     19 static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping";
     20 static NSString const *JOYCustomReports = @"JOYCustomReports";
     21 static NSString const *JOYIsSwitch = @"JOYIsSwitch";
     22 static NSString const *JOYJoyCon = @"JOYJoyCon";
     23 static NSString const *JOYRumbleUsage = @"JOYRumbleUsage";
     24 static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage";
     25 static NSString const *JOYConnectedUsage = @"JOYConnectedUsage";
     26 static NSString const *JOYConnectedUsagePage = @"JOYConnectedUsagePage";
     27 static NSString const *JOYRumbleMin = @"JOYRumbleMin";
     28 static NSString const *JOYRumbleMax = @"JOYRumbleMax";
     29 static NSString const *JOYSwapZRz = @"JOYSwapZRz";
     30 static NSString const *JOYActivationReport = @"JOYActivationReport";
     31 static NSString const *JOYIgnoredReports = @"JOYIgnoredReports";
     32 static NSString const *JOYIsDualShock3 = @"JOYIsDualShock3";
     33 static NSString const *JOYIsSony = @"JOYIsSony";
     34 static NSString const *JOYEmulateAxisButtons = @"JOYEmulateAxisButtons";
     35 
     36 static NSMutableDictionary<id, JOYController *> *controllers; // Physical controllers
     37 static NSMutableArray<JOYController *> *exposedControllers; // Logical controllers
     38 
     39 static NSDictionary *hacksByName = nil;
     40 static NSDictionary *hacksByManufacturer = nil;
     41 
     42 static NSMutableSet<id<JOYListener>> *listeners = nil;
     43 
     44 static bool axes2DEmulateButtons = false;
     45 static bool hatsEmulateButtons = false;
     46 
     47 @interface JOYController ()
     48 + (void)controllerAdded:(IOHIDDeviceRef) device;
     49 + (void)controllerRemoved:(IOHIDDeviceRef) device;
     50 - (void)elementChanged:(IOHIDElementRef) element;
     51 - (void)gotReport:(NSData *)report;
     52 
     53 @end
     54 
     55 @interface JOYButton ()
     56 - (instancetype)initWithElement:(JOYElement *)element;
     57 - (bool)updateState;
     58 @property JOYButtonUsage originalUsage;
     59 @end
     60 
     61 @interface JOYAxis ()
     62 - (instancetype)initWithElement:(JOYElement *)element;
     63 - (bool)updateState;
     64 @end
     65 
     66 @interface JOYHat ()
     67 - (instancetype)initWithElement:(JOYElement *)element;
     68 - (bool)updateState;
     69 @end
     70 
     71 @interface JOYAxes2D ()
     72 - (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2;
     73 - (bool)updateState;
     74 @property unsigned rotation; // in 90 degrees units, clockwise
     75 @end
     76 
     77 @interface JOYAxes3D ()
     78 {
     79     @public JOYElement *_element1, *_element2, *_element3;
     80 }
     81 - (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2 thirdElement:(JOYElement *)element2;
     82 - (bool)updateState;
     83 @property unsigned rotation; // in 90 degrees units, clockwise
     84 @end
     85 
     86 @interface JOYInput ()
     87 @property unsigned combinedIndex;
     88 @end
     89 
     90 static NSDictionary *CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage)
     91 {
     92     return @{
     93         @kIOHIDDeviceUsagePageKey: @(page),
     94         @kIOHIDDeviceUsageKey: @(usage),
     95     };
     96 }
     97 
     98 static void HIDDeviceAdded(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
     99 {
    100     [JOYController controllerAdded:device];
    101 }
    102 
    103 static void HIDDeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
    104 {
    105     [JOYController controllerRemoved:device];
    106 }
    107 
    108 static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef value)
    109 {
    110     [(__bridge JOYController *)context elementChanged:IOHIDValueGetElement(value)];
    111 }
    112 
    113 static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type,
    114                       uint32_t reportID, uint8_t *report, CFIndex reportLength)
    115 {
    116     if (reportLength) {
    117         [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:false]];
    118     }
    119 }
    120 
    121 typedef struct __attribute__((packed)) {
    122     uint8_t reportID;
    123     uint8_t sequence;
    124     uint8_t rumbleData[8];
    125     uint8_t command;
    126     uint8_t commandData[26];
    127 } JOYSwitchPacket;
    128 
    129 typedef struct __attribute__((packed)) {
    130     uint8_t reportID;
    131     uint8_t padding;
    132     uint8_t rumbleRightDuration;
    133     uint8_t rumbleRightStrength;
    134     uint8_t rumbleLeftDuration;
    135     uint8_t rumbleLeftStrength;
    136     uint32_t padding2;
    137     uint8_t ledsEnabled;
    138     struct {
    139         uint8_t timeEnabled;
    140         uint8_t dutyLength;
    141         uint8_t enabled;
    142         uint8_t dutyOff;
    143         uint8_t dutyOn;
    144     } __attribute__((packed)) led[5];
    145     uint8_t padding3[13];
    146 } JOYDualShock3Output;
    147 
    148 typedef struct __attribute__((packed)) {
    149     uint8_t reportID;
    150     uint8_t sequence;
    151     union {
    152         uint8_t tag;
    153         uint8_t reportIDOnUSB;
    154     };
    155     uint16_t flags;
    156     uint8_t rumbleRightStrength; // Weak
    157     uint8_t rumbleLeftStrength; // Strong
    158     uint8_t reserved[4];
    159     uint8_t muteButtonLED;
    160     uint8_t powerSaveControl;
    161     uint8_t reserved2[28];
    162     uint8_t flags2;
    163     uint8_t reserved3[2];
    164     uint8_t lightbarSetup;
    165     uint8_t LEDBrightness;
    166     uint8_t playerLEDs;
    167     uint8_t lightbarRed;
    168     uint8_t lightbarGreen;
    169     uint8_t lightbarBlue;
    170     uint8_t bluetoothSpecific[24];
    171     uint32_t crc32;
    172 } JOYDualSenseOutput;
    173 
    174 
    175 typedef union {
    176     JOYSwitchPacket switchPacket;
    177     JOYDualShock3Output ds3Output;
    178     JOYDualSenseOutput dualsenseOutput;
    179 } JOYVendorSpecificOutput;
    180 
    181 @implementation JOYController
    182 {
    183     @public // Let JOYCombinedController access everything
    184     IOHIDDeviceRef _device;
    185     NSMutableDictionary<JOYElement *, JOYButton *> *_buttons;
    186     NSMutableDictionary<JOYElement *, JOYAxis *> *_axes;
    187     NSMutableDictionary<JOYElement *, JOYAxes2D *> *_axes2D;
    188     NSMutableDictionary<JOYElement *, JOYAxes3D *> *_axes3D;
    189     NSMutableDictionary<JOYElement *, JOYHat *> *_hats;
    190     NSMutableDictionary<NSNumber *, JOYFullReportElement *> *_fullReportElements;
    191     NSMutableDictionary<JOYFullReportElement *, NSArray<JOYElement *> *> *_multiElements;
    192     JOYAxes3D *_lastAxes3D;
    193     
    194     // Button emulation
    195     NSMutableDictionary<NSNumber *, JOYEmulatedButton *> *_axisEmulatedButtons;
    196     NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_axes2DEmulatedButtons;
    197     NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_hatEmulatedButtons;
    198 
    199     JOYElement *_rumbleElement;
    200     JOYElement *_connectedElement;
    201     NSMutableDictionary<NSValue *, JOYElement *> *_iokitToJOY;
    202     NSString *_serialSuffix;
    203     bool _isSwitch; // Does this controller use the Switch protocol?
    204     bool _isDualShock3; // Does this controller use DS3 outputs?
    205     bool _isSony; // Is this a DS4 or newer Sony controller?
    206     bool _isDualSense;
    207     bool _isUSBDualSense;
    208 
    209     JOYVendorSpecificOutput _lastVendorSpecificOutput;
    210     volatile double _rumbleAmplitude;
    211     bool _physicallyConnected;
    212     bool _logicallyConnected;
    213     
    214     NSDictionary *_hacks;
    215     NSMutableData *_lastReport;
    216     
    217     // Used when creating inputs
    218     JOYElement *_previousAxisElement;
    219     
    220     uint8_t _playerLEDs;
    221     double _sentRumbleAmp;
    222     unsigned _rumbleCounter;
    223     bool _deviceCantSendReports;
    224     dispatch_queue_t _rumbleQueue;
    225     JOYCombinedController *_parent;
    226 }
    227 
    228 - (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks
    229 {
    230     return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil hacks:hacks];
    231 }
    232 
    233 -(void)createOutputForElement:(JOYElement *)element
    234 {
    235     uint16_t rumbleUsagePage = (uint16_t)[_hacks[JOYRumbleUsagePage] unsignedIntValue];
    236     uint16_t rumbleUsage = (uint16_t)[_hacks[JOYRumbleUsage] unsignedIntValue];
    237 
    238     if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) {
    239         if (_hacks[JOYRumbleMin]) {
    240             element.min = [_hacks[JOYRumbleMin] unsignedIntValue];
    241         }
    242         if (_hacks[JOYRumbleMax]) {
    243             element.max = [_hacks[JOYRumbleMax] unsignedIntValue];
    244         }
    245         _rumbleElement = element;
    246     }
    247 }
    248 
    249 -(void)createInputForElement:(JOYElement *)element
    250 {
    251     uint16_t connectedUsagePage = (uint16_t)[_hacks[JOYConnectedUsagePage] unsignedIntValue];
    252     uint16_t connectedUsage = (uint16_t)[_hacks[JOYConnectedUsage] unsignedIntValue];
    253 
    254     if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) {
    255         _connectedElement = element;
    256         _logicallyConnected = element.value != element.min;
    257         return;
    258     }
    259     
    260     NSDictionary *axisGroups = @{
    261         @(kHIDUsage_GD_X): @(0),
    262         @(kHIDUsage_GD_Y): @(0),
    263         @(kHIDUsage_GD_Z): @(1),
    264         @(kHIDUsage_GD_Rx): @(2),
    265         @(kHIDUsage_GD_Ry): @(2),
    266         @(kHIDUsage_GD_Rz): @(1),
    267     };
    268     
    269     if (element.usagePage == kHIDPage_Sensor) {
    270         JOYAxes3DUsage usage;
    271         JOYElement *element1 = nil, *element2 = nil, *element3 = nil;
    272         
    273         switch (element.usage) {
    274             case kHIDUsage_Snsr_Data_Motion_AccelerationAxisX:
    275             case kHIDUsage_Snsr_Data_Motion_AccelerationAxisY:
    276             case kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ:
    277                 usage = JOYAxes3DUsageAcceleration;
    278                 break;
    279             case kHIDUsage_Snsr_Data_Motion_AngularPositionXAxis:
    280             case kHIDUsage_Snsr_Data_Motion_AngularPositionYAxis:
    281             case kHIDUsage_Snsr_Data_Motion_AngularPositionZAxis:
    282                 usage = JOYAxes3DUsageOrientation;
    283                 break;
    284             case kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis:
    285             case kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis:
    286             case kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis:
    287                 usage = JOYAxes3DUsageGyroscope;
    288                 break;
    289             default:
    290                 return;
    291         }
    292         
    293         switch (element.usage) {
    294             case kHIDUsage_Snsr_Data_Motion_AccelerationAxisX:
    295             case kHIDUsage_Snsr_Data_Motion_AngularPositionXAxis:
    296             case kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis:
    297                 element1 = element;
    298                 if (_lastAxes3D && !_lastAxes3D->_element1 && _lastAxes3D.usage == usage) {
    299                     element2 = _lastAxes3D->_element2;
    300                     element3 = _lastAxes3D->_element3;
    301                 }
    302                 break;
    303             case kHIDUsage_Snsr_Data_Motion_AccelerationAxisY:
    304             case kHIDUsage_Snsr_Data_Motion_AngularPositionYAxis:
    305             case kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis:
    306                 element2 = element;
    307                 if (_lastAxes3D && !_lastAxes3D->_element2 && _lastAxes3D.usage == usage) {
    308                     element1 = _lastAxes3D->_element1;
    309                     element3 = _lastAxes3D->_element3;
    310                 }
    311                 break;
    312             case kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ:
    313             case kHIDUsage_Snsr_Data_Motion_AngularPositionZAxis:
    314             case kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis:
    315                 element3 = element;
    316                 if (_lastAxes3D && !_lastAxes3D->_element3 && _lastAxes3D.usage == usage) {
    317                     element1 = _lastAxes3D->_element1;
    318                     element2 = _lastAxes3D->_element2;
    319                 }
    320                 break;
    321         }
    322         
    323         _lastAxes3D = [[JOYAxes3D alloc] initWithFirstElement:element1 secondElement:element2 thirdElement:element3];
    324         _lastAxes3D.usage = usage;
    325         if (element1) _axes3D[element1] = _lastAxes3D;
    326         if (element2) _axes3D[element2] = _lastAxes3D;
    327         if (element3) _axes3D[element3] = _lastAxes3D;
    328         
    329         return;
    330     }
    331     
    332     axisGroups = _hacks[JOYAxisGroups] ?: axisGroups;
    333     
    334     if (element.usagePage == kHIDPage_Button ||
    335         (element.usagePage == kHIDPage_Consumer && (element.usage == kHIDUsage_Csmr_ACHome ||
    336                                                     element.usage == kHIDUsage_Csmr_ACBack))) {
    337     button: {
    338         JOYButton *button = [[JOYButton alloc] initWithElement: element];
    339         [_buttons setObject:button forKey:element];
    340         NSNumber *replacementUsage = element.usagePage == kHIDPage_Button? _hacks[JOYButtonUsageMapping][@(button.usage)] : nil;
    341         if (replacementUsage) {
    342             button.originalUsage = button.usage = [replacementUsage unsignedIntValue];
    343         }
    344         return;
    345     }
    346     }
    347     else if (element.usagePage == kHIDPage_Simulation) {
    348         switch (element.usage) {
    349             case kHIDUsage_Sim_Accelerator:
    350             case kHIDUsage_Sim_Brake:
    351             case kHIDUsage_Sim_Rudder:
    352             case kHIDUsage_Sim_Throttle:
    353             goto single;
    354         }
    355     }
    356     else if (element.usagePage == kHIDPage_GenericDesktop) {
    357         switch (element.usage) {
    358             case kHIDUsage_GD_X:
    359             case kHIDUsage_GD_Y:
    360             case kHIDUsage_GD_Z:
    361             case kHIDUsage_GD_Rx:
    362             case kHIDUsage_GD_Ry:
    363             case kHIDUsage_GD_Rz: {
    364                 
    365                 JOYElement *other = _previousAxisElement;
    366                 _previousAxisElement = element;
    367                 if (!other) goto single;
    368                 if (other.usage >= element.usage) goto single;
    369                 if (other.reportID != element.reportID) goto single;
    370                 if (![axisGroups[@(other.usage)] isEqual: axisGroups[@(element.usage)]]) goto single;
    371                 if (other.parentID != element.parentID) goto single;
    372                 
    373                 JOYAxes2D *axes = nil;
    374                 if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [_hacks[JOYSwapZRz] boolValue]) {
    375                     axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other];
    376                 }
    377                 else {
    378                     axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element];
    379                 }
    380                 NSNumber *replacementUsage = _hacks[JOYAxes2DUsageMapping][@(axes.usage)];
    381                 if (replacementUsage) {
    382                     axes.usage = [replacementUsage unsignedIntValue];
    383                 }
    384                 
    385                 [_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)];
    386                 [_axes removeObjectForKey:other];
    387                 _previousAxisElement = nil;
    388                 _axes2D[other] = axes;
    389                 _axes2D[element] = axes;
    390                 
    391                 if (axes2DEmulateButtons) {
    392                     _axes2DEmulatedButtons[@(axes.uniqueID)] = @[
    393                         [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x100000000L],
    394                         [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x200000000L],
    395                         [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp type:JOYButtonTypeAxes2DEmulated  uniqueID:axes.uniqueID | 0x300000000L],
    396                         [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x400000000L],
    397                     ];
    398                 }
    399                 break;
    400             }
    401             case kHIDUsage_GD_Slider:
    402             case kHIDUsage_GD_Dial:
    403             case kHIDUsage_GD_Wheel:
    404             { single: {
    405                 JOYAxis *axis = [[JOYAxis alloc] initWithElement: element];
    406                 [_axes setObject:axis forKey:element];
    407                 
    408                 NSNumber *replacementUsage = element.usagePage == kHIDPage_GenericDesktop? _hacks[JOYAxisUsageMapping][@(axis.usage)] : nil;
    409                 if (replacementUsage) {
    410                     axis.usage = [replacementUsage unsignedIntValue];
    411                 }
    412                 
    413                 if ([_hacks[JOYEmulateAxisButtons] boolValue]) {
    414                     _axisEmulatedButtons[@(axis.uniqueID)] =
    415                     [[JOYEmulatedButton alloc] initWithUsage:axis.equivalentButtonUsage type:JOYButtonTypeAxisEmulated uniqueID:axis.uniqueID];
    416                 }
    417                 
    418                 
    419                 break;
    420             }}
    421             case kHIDUsage_GD_DPadUp:
    422             case kHIDUsage_GD_DPadDown:
    423             case kHIDUsage_GD_DPadRight:
    424             case kHIDUsage_GD_DPadLeft:
    425             case kHIDUsage_GD_Start:
    426             case kHIDUsage_GD_Select:
    427             case kHIDUsage_GD_SystemMainMenu:
    428                 goto button;
    429                 
    430             case kHIDUsage_GD_Hatswitch: {
    431                 JOYHat *hat = [[JOYHat alloc] initWithElement: element];
    432                 [_hats setObject:hat forKey:element];
    433                 if (hatsEmulateButtons) {
    434                     _hatEmulatedButtons[@(hat.uniqueID)] = @[
    435                         [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft type:JOYButtonTypeHatEmulated  uniqueID:hat.uniqueID | 0x100000000L],
    436                         [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x200000000L],
    437                         [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp type:JOYButtonTypeHatEmulated  uniqueID:hat.uniqueID | 0x300000000L],
    438                         [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x400000000L],
    439                     ];
    440                 }
    441                 break;
    442             }
    443         }
    444     }
    445 }
    446 
    447 - (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray <NSNumber *> *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks
    448 {
    449     self = [super init];
    450     if (!self) return self;
    451     
    452     _physicallyConnected = true;
    453     _logicallyConnected = true;
    454     _device = (IOHIDDeviceRef)CFRetain(device);
    455     _serialSuffix = suffix;
    456     _playerLEDs = -1;
    457     [self obtainInfo];
    458 
    459     IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self);
    460     IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    461 
    462     NSArray *array = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
    463     _buttons = [NSMutableDictionary dictionary];
    464     _axes = [NSMutableDictionary dictionary];
    465     _axes2D = [NSMutableDictionary dictionary];
    466     _axes3D = [NSMutableDictionary dictionary];
    467     _hats = [NSMutableDictionary dictionary];
    468     _axisEmulatedButtons = [NSMutableDictionary dictionary];
    469     _axes2DEmulatedButtons = [NSMutableDictionary dictionary];
    470     _hatEmulatedButtons = [NSMutableDictionary dictionary];
    471     _iokitToJOY = [NSMutableDictionary dictionary];
    472     
    473     
    474     _hacks = hacks;
    475     _isSwitch = [_hacks[JOYIsSwitch] boolValue];
    476     _isDualShock3 = [_hacks[JOYIsDualShock3] boolValue];
    477     _isSony = [_hacks[JOYIsSony] boolValue];
    478     _joyconType = [_hacks[JOYJoyCon] unsignedIntValue];
    479 
    480     NSDictionary *customReports = hacks[JOYCustomReports];
    481     _lastReport = [NSMutableData dataWithLength:MAX(
    482                                                     MAX(
    483                                                         [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue],
    484                                                         [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue]
    485                                                         ),
    486                                                     [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue]
    487                                                     )];
    488     IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self);
    489 
    490     if (hacks[JOYCustomReports]) {
    491         _multiElements = [NSMutableDictionary dictionary];
    492         _fullReportElements = [NSMutableDictionary dictionary];
    493 
    494         
    495         for (NSNumber *_reportID in customReports) {
    496             signed reportID = [_reportID intValue];
    497             bool isOutput = false;
    498             if (reportID < 0) {
    499                 isOutput = true;
    500                 reportID = -reportID;
    501             }
    502             
    503             JOYFullReportElement *element = [[JOYFullReportElement alloc] initWithDevice:device reportID:reportID];
    504             NSMutableArray *elements = [NSMutableArray array];
    505             for (NSDictionary <NSString *,NSNumber *> *subElementDef in customReports[_reportID]) {
    506                 if (filter && subElementDef[@"reportID"] && ![filter containsObject:subElementDef[@"reportID"]]) continue;
    507                 JOYSubElement *subElement = [[JOYSubElement alloc] initWithRealElement:element
    508                                                                                   size:subElementDef[@"size"].unsignedLongValue
    509                                                                                 offset:subElementDef[@"offset"].unsignedLongValue + 8 // Compensate for the reportID
    510                                                                              usagePage:subElementDef[@"usagePage"].unsignedLongValue
    511                                                                                  usage:subElementDef[@"usage"].unsignedLongValue
    512                                                                                    min:subElementDef[@"min"].unsignedIntValue
    513                                                                                    max:subElementDef[@"max"].unsignedIntValue];
    514                 [elements addObject:subElement];
    515                 if (isOutput) {
    516                     [self createOutputForElement:subElement];
    517                 }
    518                 else {
    519                     [self createInputForElement:subElement];
    520                 }
    521             }
    522             _multiElements[element] = elements;
    523             if (!isOutput) {
    524                 _fullReportElements[@(reportID)] = element;
    525             }
    526         }
    527     }
    528     
    529     id previous = nil;
    530     NSSet *ignoredReports = nil;
    531     if (hacks[JOYIgnoredReports]) {
    532         ignoredReports = [NSSet setWithArray:hacks[JOYIgnoredReports]];
    533     }
    534     
    535     for (id _element in array) {
    536         if (_element == previous) continue; // Some elements are reported twice for some reason
    537         previous = _element;
    538         JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element];
    539 
    540         bool isOutput = false;
    541         if (filter && ![filter containsObject:@(element.reportID)]) continue;
    542 
    543         switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) {
    544             /* Handled */
    545             case kIOHIDElementTypeInput_Misc:
    546             case kIOHIDElementTypeInput_Button:
    547             case kIOHIDElementTypeInput_Axis:
    548                 break;
    549             case kIOHIDElementTypeOutput:
    550                 isOutput = true;
    551                 break;
    552             /* Ignored */
    553             default:
    554             case kIOHIDElementTypeInput_ScanCodes:
    555             case kIOHIDElementTypeInput_NULL:
    556             case kIOHIDElementTypeFeature:
    557             case kIOHIDElementTypeCollection:
    558                 continue;
    559         }
    560         if ((!isOutput && [ignoredReports containsObject:@(element.reportID)]) ||
    561             (isOutput && [ignoredReports containsObject:@(-element.reportID)])) continue;
    562 
    563         
    564         if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue;
    565         
    566         if (isOutput) {
    567             [self createOutputForElement:element];
    568         }
    569         else {
    570             [self createInputForElement:element];
    571         }
    572         
    573         _iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element;
    574     }
    575     
    576     [exposedControllers addObject:self];
    577     if (_logicallyConnected) {
    578         for (id<JOYListener> listener in listeners) {
    579             if ([listener respondsToSelector:@selector(controllerConnected:)]) {
    580                 [listener controllerConnected:self];
    581             }
    582         }
    583     }
    584     
    585     if (_hacks[JOYActivationReport]) {
    586         [self sendReport:hacks[JOYActivationReport]];
    587     }
    588     
    589     if (_isSwitch) {
    590         [self sendReport:[NSData dataWithBytes:inline_const(uint8_t[], {0x80, 0x04}) length:2]];
    591         [self sendReport:[NSData dataWithBytes:inline_const(uint8_t[], {0x80, 0x02}) length:2]];
    592         
    593         _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs
    594         _lastVendorSpecificOutput.switchPacket.sequence++;
    595         _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
    596         _lastVendorSpecificOutput.switchPacket.command = 3; // Set input report mode
    597         _lastVendorSpecificOutput.switchPacket.commandData[0] = 0x30; // Standard full mode
    598         [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
    599         
    600         _lastVendorSpecificOutput.switchPacket.sequence++;
    601         _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
    602         _lastVendorSpecificOutput.switchPacket.command = 0x48; // Set vibration enabled
    603         _lastVendorSpecificOutput.switchPacket.commandData[0] = 1; // enabled
    604         [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
    605         
    606         // The Joy-Cons don't like having their IMU enabled too quickly
    607         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    608             _lastVendorSpecificOutput.switchPacket.sequence++;
    609             _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
    610             _lastVendorSpecificOutput.switchPacket.command = 0x40; // Enable/disableIMU
    611             _lastVendorSpecificOutput.switchPacket.commandData[0] = 1; // Enabled
    612             [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
    613         });
    614     }
    615     
    616     if (_isDualShock3) {
    617         _lastVendorSpecificOutput.ds3Output = (JOYDualShock3Output){
    618             .reportID = 1,
    619             .led = {
    620                 {.timeEnabled =  0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
    621                 {.timeEnabled =  0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
    622                 {.timeEnabled =  0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
    623                 {.timeEnabled =  0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
    624                 {.timeEnabled =  0,    .dutyLength = 0,    .enabled = 0,    .dutyOff = 0, .dutyOn = 0},
    625             }
    626         };
    627     }
    628     if (_isSony) {
    629         _isDualSense = [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue] == 0xCE6;
    630     }
    631     
    632     if (_isDualSense) {
    633         _isUSBDualSense = [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"];
    634         _lastVendorSpecificOutput.dualsenseOutput = (JOYDualSenseOutput){
    635             .reportID = 0x31,
    636             .tag = 0x10,
    637             .flags = 0x1403, // Rumble, lightbar and player LEDs
    638             .flags2 = 2,
    639             .lightbarSetup = 2,
    640             .lightbarBlue = 255,
    641         };
    642         if (_isUSBDualSense) {
    643             _lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB = 1;
    644             _lastVendorSpecificOutput.dualsenseOutput.lightbarBlue = 0;
    645             _lastVendorSpecificOutput.dualsenseOutput.lightbarGreen = 96;
    646             _lastVendorSpecificOutput.dualsenseOutput.lightbarRed = 255;
    647 
    648         }
    649         // Send a report to switch the controller to a more capable mode
    650         [self sendDualSenseOutput];
    651         _lastVendorSpecificOutput.dualsenseOutput.flags2 = 0;
    652         _lastVendorSpecificOutput.dualsenseOutput.lightbarSetup = 0;
    653     }
    654     
    655     _rumbleQueue = dispatch_queue_create([NSString stringWithFormat:@"Rumble Queue for %@", self.deviceName].UTF8String,
    656                                          NULL);
    657     
    658     return self;
    659 }
    660 
    661 
    662 - (void)obtainInfo
    663 {
    664     _deviceName = IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductKey));
    665     NSString *serial = (__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDSerialNumberKey));
    666     if (!serial || [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]) {
    667         serial = [NSString stringWithFormat:@"%04x%04x%08x",
    668                   [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDVendorIDKey)) unsignedIntValue],
    669                   [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue],
    670                   [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDLocationIDKey)) unsignedIntValue]];
    671     }
    672     if (_serialSuffix) {
    673         _uniqueID = [NSString stringWithFormat:@"%@-%@", serial, _serialSuffix];
    674         return;
    675     }
    676     _uniqueID = serial;
    677 }
    678 
    679 - (JOYControllerCombinedType)combinedControllerType
    680 {
    681     return _parent? JOYControllerCombinedTypeComponent : JOYControllerCombinedTypeSingle;
    682 }
    683 
    684 - (NSString *)description
    685 {
    686     return [NSString stringWithFormat:@"<%@: %p, %@, %@>", self.className, self, self.deviceName, self.uniqueID];
    687 }
    688 
    689 - (NSArray<JOYButton *> *)buttons
    690 {
    691     NSMutableArray *ret = [[_buttons allValues] mutableCopy];
    692     [ret addObjectsFromArray:_axisEmulatedButtons.allValues];
    693     for (NSArray *array in _axes2DEmulatedButtons.allValues) {
    694         [ret addObjectsFromArray:array];
    695     }
    696     for (NSArray *array in _hatEmulatedButtons.allValues) {
    697         [ret addObjectsFromArray:array];
    698     }
    699     return ret;
    700 }
    701 
    702 - (NSArray<JOYAxis *> *)axes
    703 {
    704     return [_axes allValues];
    705 }
    706 
    707 - (NSArray<JOYAxes2D *> *)axes2D
    708 {
    709     return [[NSSet setWithArray:[_axes2D allValues]] allObjects];
    710 }
    711 
    712 - (NSArray<JOYAxes3D *> *)axes3D
    713 {
    714     return [[NSSet setWithArray:[_axes3D allValues]] allObjects];
    715 }
    716 
    717 - (NSArray<JOYHat *> *)hats
    718 {
    719     return [_hats allValues];
    720 }
    721 
    722 - (void)gotReport:(NSData *)report
    723 {
    724     JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)];
    725     if (element) {
    726         [element updateValue:report];
    727         
    728         NSArray<JOYElement *> *subElements = _multiElements[element];
    729         if (subElements) {
    730             for (JOYElement *subElement in subElements) {
    731                 [self _elementChanged:subElement];
    732             }
    733         }
    734     }
    735     dispatch_async(_rumbleQueue, ^{
    736         [self updateRumble];
    737     });
    738 }
    739 
    740 - (void)elementChanged:(IOHIDElementRef)element
    741 {
    742     JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))];
    743     if (_element) {
    744         [self _elementChanged:_element];
    745     }
    746     else {
    747         //NSLog(@"Unhandled usage %x (Cookie: %x, Usage: %x)", IOHIDElementGetUsage(element), IOHIDElementGetCookie(element), IOHIDElementGetUsage(element));
    748     }
    749 }
    750 
    751 - (void)_elementChanged:(JOYElement *)element
    752 {
    753     if (element == _connectedElement) {
    754         bool old = self.connected;
    755         _logicallyConnected = _connectedElement.value != _connectedElement.min;
    756         if (!old && self.connected) {
    757             for (id<JOYListener> listener in listeners) {
    758                 if ([listener respondsToSelector:@selector(controllerConnected:)]) {
    759                     [listener controllerConnected:self];
    760                 }
    761             }
    762         }
    763         else if (old && !self.connected) {
    764             [_parent breakApart];
    765             for (id<JOYListener> listener in listeners) {
    766                 if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
    767                     [listener controllerDisconnected:self];
    768                 }
    769             }
    770         }
    771     }
    772     
    773     if (!self.connected) return;
    774     {
    775         JOYButton *button = _buttons[element];
    776         if (button) {
    777             if ([button updateState]) {
    778                 for (id<JOYListener> listener in listeners) {
    779                     if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
    780                         [listener controller:_parent ?: self buttonChangedState:button];
    781                     }
    782                 }
    783             }
    784             return;
    785         }
    786     }
    787     
    788 
    789     {
    790         JOYAxis *axis = _axes[element];
    791         if (axis) {
    792             if ([axis updateState])  {
    793                 for (id<JOYListener> listener in listeners) {
    794                     if ([listener respondsToSelector:@selector(controller:movedAxis:)]) {
    795                         [listener controller:_parent ?: self movedAxis:axis];
    796                     }
    797                 }
    798                 JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away
    799                 if ([button updateStateFromAxis:axis]) {
    800                     for (id<JOYListener> listener in listeners) {
    801                         if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
    802                             [listener controller:_parent ?: self buttonChangedState:button];
    803                         }
    804                     }
    805                 }
    806             }
    807             return;
    808         }
    809     }
    810     
    811     {
    812         JOYAxes2D *axes = _axes2D[element];
    813         if (axes) {
    814             if ([axes updateState]) {
    815                 for (id<JOYListener> listener in listeners) {
    816                     if ([listener respondsToSelector:@selector(controller:movedAxes2D:)]) {
    817                         [listener controller:_parent ?: self movedAxes2D:axes];
    818                     }
    819                 }
    820                 NSArray <JOYEmulatedButton *> *buttons = _axes2DEmulatedButtons[@(axes.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away
    821                 for (JOYEmulatedButton *button in buttons) {
    822                     if ([button updateStateFromAxes2D:axes]) {
    823                         for (id<JOYListener> listener in listeners) {
    824                             if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
    825                                 [listener controller:_parent ?: self buttonChangedState:button];
    826                             }
    827                         }
    828                     }
    829                 }
    830             }
    831             return;
    832         }
    833     }
    834     
    835     {
    836         JOYAxes3D *axes = _axes3D[element];
    837         if (axes) {
    838             if ([axes updateState]) {
    839                 for (id<JOYListener> listener in listeners) {
    840                     if ([listener respondsToSelector:@selector(controller:movedAxes3D:)]) {
    841                         [listener controller:_parent ?: self movedAxes3D:axes];
    842                     }
    843                 }
    844             }
    845             return;
    846         }
    847     }
    848     
    849     {
    850         JOYHat *hat = _hats[element];
    851         if (hat) {
    852             if ([hat updateState]) {
    853                 for (id<JOYListener> listener in listeners) {
    854                     if ([listener respondsToSelector:@selector(controller:movedHat:)]) {
    855                         [listener controller:_parent ?: self movedHat:hat];
    856                     }
    857                 }
    858                 
    859                 NSArray <JOYEmulatedButton *> *buttons = _hatEmulatedButtons[@(hat.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away
    860                 for (JOYEmulatedButton *button in buttons) {
    861                     if ([button updateStateFromHat:hat]) {
    862                         for (id<JOYListener> listener in listeners) {
    863                             if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
    864                                 [listener controller:_parent ?: self buttonChangedState:button];
    865                             }
    866                         }
    867                     }
    868                 }
    869             }
    870             return;
    871         }
    872     }
    873 }
    874 
    875 - (void)disconnected
    876 {
    877     _physicallyConnected = false;
    878     [_parent breakApart];
    879     if (_logicallyConnected && [exposedControllers containsObject:self]) {
    880         for (id<JOYListener> listener in listeners) {
    881             if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
    882                 [listener controllerDisconnected:self];
    883             }
    884         }
    885     }
    886     [exposedControllers removeObject:self];
    887     [self setRumbleAmplitude:0];
    888     dispatch_sync(_rumbleQueue, ^{
    889         [self updateRumble];
    890     });
    891     _device = nil;
    892 }
    893 
    894 - (void)sendReport:(NSData *)report
    895 {
    896     if (!report.length) return;
    897     if (!_device) return;
    898     if (_deviceCantSendReports) return;
    899     /* Some Macs fail to send reports to some devices, specifically the DS3, returning the bogus(?) error code 1 after
    900        freezing for 5 seconds. Stop sending reports if that's the case. */
    901     if (IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length) == 1) {
    902         _deviceCantSendReports = true;
    903         NSLog(@"This Mac appears to be incapable of sending output reports to %@", self);
    904     }
    905 }
    906 
    907 - (void) sendDualSenseOutput
    908 {
    909     if (_isUSBDualSense) {
    910         [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB length:_lastVendorSpecificOutput.dualsenseOutput.bluetoothSpecific - &_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB]];
    911         return;
    912     }
    913     _lastVendorSpecificOutput.dualsenseOutput.sequence += 0x10;
    914     static const uint32_t table[] = {
    915         0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
    916         0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
    917         0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
    918         0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
    919         0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
    920         0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
    921         0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
    922         0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
    923         0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
    924         0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
    925         0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
    926         0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
    927         0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
    928         0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
    929         0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
    930         0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
    931         0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
    932         0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
    933         0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
    934         0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
    935         0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
    936         0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
    937         0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
    938         0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
    939         0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
    940         0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
    941         0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
    942         0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
    943         0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
    944         0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
    945         0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
    946         0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
    947         0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
    948         0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
    949         0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
    950         0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
    951         0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
    952         0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
    953         0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
    954         0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
    955         0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
    956         0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
    957         0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
    958     };
    959     
    960     const uint8_t *byte = (void *)&_lastVendorSpecificOutput.dualsenseOutput;
    961     uint32_t size = sizeof(_lastVendorSpecificOutput.dualsenseOutput) - 4;
    962     uint32_t ret = 0xFFFFFFFF;
    963     ret = table[(ret ^ 0xA2) & 0xFF] ^ (ret >> 8);
    964 
    965     while (size--) {
    966         ret = table[(ret ^ *byte++) & 0xFF] ^ (ret >> 8);
    967     }
    968     
    969     _lastVendorSpecificOutput.dualsenseOutput.crc32 = ~ret;
    970     
    971     [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput length:sizeof(_lastVendorSpecificOutput.dualsenseOutput)]];
    972 }
    973 
    974 - (uint8_t)LEDMaskForPlayer:(unsigned)player
    975 {
    976     if (_isDualShock3) {
    977         return 2 << player;
    978     }
    979     if (_isDualSense) {
    980         switch (player) {
    981             case 0: return 0x04;
    982             case 1: return 0x0A;
    983             case 2: return 0x15;
    984             case 3: return 0x1B;
    985             default: return 0;
    986         }
    987     }
    988     return 1 << player;
    989 }
    990 
    991 - (void)setPlayerLEDs:(uint8_t)mask
    992 {
    993     if (mask == _playerLEDs) {
    994         return;
    995     }
    996     _playerLEDs = mask;
    997     if (_isSwitch) {
    998         _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs
    999         _lastVendorSpecificOutput.switchPacket.sequence++;
   1000         _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
   1001         _lastVendorSpecificOutput.switchPacket.command = 0x30; // LED
   1002         _lastVendorSpecificOutput.switchPacket.commandData[0] = mask & 0xF;
   1003         [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
   1004     }
   1005     else if (_isDualShock3) {
   1006         _lastVendorSpecificOutput.ds3Output.reportID = 1;
   1007         _lastVendorSpecificOutput.ds3Output.ledsEnabled = (mask  & 0x1F);
   1008         [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]];
   1009     }
   1010     else if (_isDualSense) {
   1011         _lastVendorSpecificOutput.dualsenseOutput.playerLEDs = mask & 0x1F;
   1012         [self sendDualSenseOutput];
   1013     }
   1014 }
   1015 
   1016 - (void)updateRumble
   1017 {
   1018     if (!self.connected) {
   1019         return;
   1020     }
   1021     if (!_rumbleElement && !_isSwitch && !_isDualShock3 && !_isDualSense) {
   1022         return;
   1023     }
   1024     if (_rumbleElement.max == 1 && _rumbleElement.min == 0) {
   1025         double ampToSend = _rumbleCounter < round(_rumbleAmplitude * PWM_RESOLUTION);
   1026         if (ampToSend != _sentRumbleAmp) {
   1027             [_rumbleElement setValue:ampToSend];
   1028             _sentRumbleAmp = ampToSend;
   1029         }
   1030         _rumbleCounter += round(_rumbleAmplitude * PWM_RESOLUTION);
   1031         if (_rumbleCounter >= PWM_RESOLUTION) {
   1032             _rumbleCounter -= PWM_RESOLUTION;
   1033         }
   1034     }
   1035     else {
   1036         if (_rumbleAmplitude == _sentRumbleAmp) {
   1037             return;
   1038         }
   1039         _sentRumbleAmp = _rumbleAmplitude;
   1040         if (_isSwitch) {
   1041             double frequency = 144;
   1042             double amp = _rumbleAmplitude;
   1043             
   1044             uint8_t highAmp = amp * 0x64;
   1045             uint8_t lowAmp = amp * 0x32 + 0x40;
   1046             if (frequency < 0) frequency = 0;
   1047             if (frequency > 1252) frequency = 1252;
   1048             uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0);
   1049             
   1050             uint16_t highFreq = (encodedFrequency - 0x60) * 4;
   1051             uint8_t lowFreq = encodedFrequency - 0x40;
   1052             
   1053             //if (frequency < 82 || frequency > 312) {
   1054             if (amp) {
   1055                 highAmp = 0;
   1056             }
   1057             
   1058             if (frequency < 40 || frequency > 626) {
   1059                 lowAmp = 0;
   1060             }
   1061             
   1062             _lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF;
   1063             _lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1);
   1064             _lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq;
   1065             _lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp;
   1066             
   1067             
   1068             _lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only
   1069             _lastVendorSpecificOutput.switchPacket.sequence++;
   1070             _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
   1071             _lastVendorSpecificOutput.switchPacket.command = 0; // LED
   1072             [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
   1073         }
   1074         else if (_isDualShock3) {
   1075             _lastVendorSpecificOutput.ds3Output.reportID = 1;
   1076             _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = _rumbleAmplitude? 0xFF : 0;
   1077             _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = round(_rumbleAmplitude * 0xFF);
   1078             [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]];
   1079         }
   1080         else if (_isDualSense) {
   1081             _lastVendorSpecificOutput.dualsenseOutput.rumbleLeftStrength = round(_rumbleAmplitude * _rumbleAmplitude * 0xFF);
   1082             _lastVendorSpecificOutput.dualsenseOutput.rumbleRightStrength = _rumbleAmplitude > 0.25 ? round(pow(_rumbleAmplitude - 0.25, 2) * 0xFF) : 0;
   1083             [self sendDualSenseOutput];
   1084         }
   1085         else {
   1086             [_rumbleElement setValue:_rumbleAmplitude * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min];
   1087         }
   1088     }
   1089 }
   1090 
   1091 - (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */
   1092 {
   1093     if (amp < 0) amp = 0;
   1094     if (amp > 1) amp = 1;
   1095     _rumbleAmplitude = amp;
   1096 }
   1097 
   1098 - (bool)isConnected
   1099 {
   1100     return _logicallyConnected && _physicallyConnected;
   1101 }
   1102 
   1103 - (NSArray<JOYInput *> *)allInputs
   1104 {
   1105     NSMutableArray<JOYInput *> *ret = [NSMutableArray array];
   1106     [ret addObjectsFromArray:self.buttons];
   1107     [ret addObjectsFromArray:self.axes];
   1108     [ret addObjectsFromArray:self.axes2D];
   1109     [ret addObjectsFromArray:self.axes3D];
   1110     [ret addObjectsFromArray:self.hats];
   1111     return ret;
   1112 }
   1113 
   1114 - (void)setusesHorizontalJoyConGrip:(bool)usesHorizontalJoyConGrip
   1115 {
   1116     if (usesHorizontalJoyConGrip == _usesHorizontalJoyConGrip) return; // Nothing to do
   1117     _usesHorizontalJoyConGrip = usesHorizontalJoyConGrip;
   1118     switch (self.joyconType) {
   1119         case JOYJoyConTypeLeft:
   1120         case JOYJoyConTypeRight: {
   1121             NSArray <JOYButton *> *buttons = _buttons.allValues;  // not self.buttons to skip emulated buttons
   1122             if (!usesHorizontalJoyConGrip) {
   1123                 for (JOYAxes2D *axes in self.axes2D) {
   1124                     axes.rotation = 0;
   1125                 }
   1126                 for (JOYAxes3D *axes in self.axes3D) {
   1127                     axes.rotation = 0;
   1128                 }
   1129                 for (JOYButton *button in buttons) {
   1130                     button.usage = button.originalUsage;
   1131                 }
   1132                 return;
   1133             }
   1134             for (JOYAxes2D *axes in self.axes2D) {
   1135                 axes.rotation = self.joyconType == JOYJoyConTypeLeft? -1 : 1;
   1136             }
   1137             for (JOYAxes3D *axes in self.axes3D) {
   1138                 axes.rotation = self.joyconType == JOYJoyConTypeLeft? -1 : 1;
   1139             }
   1140             if (self.joyconType == JOYJoyConTypeLeft) {
   1141                 for (JOYButton *button in buttons) {
   1142                     switch (button.originalUsage) {
   1143                         case JOYButtonUsageDPadLeft: button.usage = JOYButtonUsageB; break;
   1144                         case JOYButtonUsageDPadRight: button.usage = JOYButtonUsageX; break;
   1145                         case JOYButtonUsageDPadUp: button.usage = JOYButtonUsageY; break;
   1146                         case JOYButtonUsageDPadDown: button.usage = JOYButtonUsageA; break;
   1147                         default: button.usage = button.originalUsage; break;
   1148                     }
   1149                 }
   1150             }
   1151             else {
   1152                 for (JOYButton *button in buttons) {
   1153                     switch (button.originalUsage) {
   1154                         case JOYButtonUsageY: button.usage = JOYButtonUsageX; break;
   1155                         case JOYButtonUsageA: button.usage = JOYButtonUsageB; break;
   1156                         case JOYButtonUsageX: button.usage = JOYButtonUsageA; break;
   1157                         case JOYButtonUsageB: button.usage = JOYButtonUsageY; break;
   1158                         default: button.usage = button.originalUsage; break;
   1159                     }
   1160                 }
   1161             }
   1162         }
   1163         default:
   1164             return;
   1165     }
   1166 }
   1167 
   1168 + (void)controllerAdded:(IOHIDDeviceRef) device
   1169 {
   1170     NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
   1171     NSDictionary *hacks = hacksByName[name];
   1172     if (!hacks) {
   1173         hacks = hacksByManufacturer[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))];
   1174     }
   1175     NSArray *filters = hacks[JOYReportIDFilters];
   1176     JOYController *controller = nil;
   1177     if (filters) {
   1178         controller = [[JOYMultiplayerController alloc] initWithDevice:device
   1179                                                       reportIDFilters:filters
   1180                                                                 hacks:hacks];
   1181     }
   1182     else {
   1183         controller = [[JOYController alloc] initWithDevice:device hacks:hacks];
   1184     }
   1185         
   1186     [controllers setObject:controller forKey:[NSValue valueWithPointer:device]];
   1187 }
   1188 
   1189 + (void)controllerRemoved:(IOHIDDeviceRef) device
   1190 {
   1191     [[controllers objectForKey:[NSValue valueWithPointer:device]] disconnected];
   1192     [controllers removeObjectForKey:[NSValue valueWithPointer:device]];
   1193 }
   1194 
   1195 + (NSArray<JOYController *> *)allControllers
   1196 {
   1197     return exposedControllers;
   1198 }
   1199 
   1200 + (void)load
   1201 {
   1202 #import "ControllerConfiguration.inc"
   1203 }
   1204 
   1205 +(void)registerListener:(id<JOYListener>)listener
   1206 {
   1207     [listeners addObject:listener];
   1208 }
   1209 
   1210 +(void)unregisterListener:(id<JOYListener>)listener
   1211 {
   1212     [listeners removeObject:listener];
   1213 }
   1214 
   1215 + (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options
   1216 {
   1217     axes2DEmulateButtons = [options[JOYAxes2DEmulateButtonsKey] boolValue];
   1218     hatsEmulateButtons = [options[JOYHatsEmulateButtonsKey] boolValue];
   1219     
   1220     controllers = [NSMutableDictionary dictionary];
   1221     exposedControllers = [NSMutableArray array];
   1222     NSArray *array = @[
   1223         CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
   1224         CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
   1225         CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController),
   1226         @{@kIOHIDDeviceUsagePageKey: @(kHIDPage_Game)},
   1227     ];
   1228 
   1229     listeners = [NSMutableSet set];
   1230     static IOHIDManagerRef manager = nil;
   1231     if (manager) {
   1232         CFRelease(manager); // Stop the previous session
   1233     }
   1234     manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
   1235     
   1236     if (!manager) return;
   1237     if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone)) {
   1238         CFRelease(manager);
   1239         return;
   1240     }
   1241     
   1242     IOHIDManagerSetDeviceMatchingMultiple(manager, (__bridge CFArrayRef)array);
   1243     IOHIDManagerRegisterDeviceMatchingCallback(manager, HIDDeviceAdded, NULL);
   1244     IOHIDManagerRegisterDeviceRemovalCallback(manager, HIDDeviceRemoved, NULL);
   1245     IOHIDManagerScheduleWithRunLoop(manager, [runloop getCFRunLoop], kCFRunLoopDefaultMode);
   1246 }
   1247 
   1248 - (void)dealloc
   1249 {
   1250     if (_device) {
   1251         CFRelease(_device);
   1252         _device = NULL;
   1253     }
   1254 }
   1255 @end
   1256 
   1257 
   1258 @implementation JOYCombinedController
   1259 - (instancetype)initWithChildren:(NSArray<JOYController *> *)children
   1260 {
   1261     self = [super init];
   1262     // Sorting makes the device name and unique id consistent
   1263     _children = [children sortedArrayUsingComparator:^NSComparisonResult(JOYController *a, JOYController *b) {
   1264         return [a.uniqueID compare:b.uniqueID];
   1265     }];
   1266     
   1267     if (_children.count == 0) return nil;
   1268     
   1269     for (JOYController *child in _children) {
   1270         if (child.combinedControllerType != JOYControllerCombinedTypeSingle) {
   1271             NSLog(@"Cannot combine non-single controller %@", child);
   1272             return nil;
   1273         }
   1274         if (![exposedControllers containsObject:child]) {
   1275             NSLog(@"Cannot combine unexposed controller %@", child);
   1276             return nil;
   1277         }
   1278     }
   1279     
   1280     unsigned index = 0;
   1281     for (JOYController *child in _children) {
   1282         for (id<JOYListener> listener in listeners) {
   1283             if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
   1284                 [listener controllerDisconnected:child];
   1285             }
   1286         }
   1287         child->_parent = self;
   1288         for (JOYInput *input in child.allInputs) {
   1289             input.combinedIndex = index;
   1290         }
   1291         index++;
   1292         [exposedControllers removeObject:child];
   1293     }
   1294     
   1295     [exposedControllers addObject:self];
   1296     for (id<JOYListener> listener in listeners) {
   1297         if ([listener respondsToSelector:@selector(controllerConnected:)]) {
   1298             [listener controllerConnected:self];
   1299         }
   1300     }
   1301     
   1302     return self;
   1303 }
   1304 
   1305 - (void)breakApart
   1306 {
   1307     if (![exposedControllers containsObject:self]) {
   1308         // Already broken apart
   1309         return;
   1310     }
   1311     
   1312     [exposedControllers removeObject:self];
   1313     for (id<JOYListener> listener in listeners) {
   1314         if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
   1315             [listener controllerDisconnected:self];
   1316         }
   1317     }
   1318 
   1319     for (JOYController *child in _children) {
   1320         child->_parent = nil;
   1321         for (JOYInput *input in child.allInputs) {
   1322             input.combinedIndex = 0;
   1323         }
   1324         if (!child.connected) break;
   1325         [exposedControllers addObject:child];
   1326         for (id<JOYListener> listener in listeners) {
   1327             if ([listener respondsToSelector:@selector(controllerConnected:)]) {
   1328                 [listener controllerConnected:child];
   1329             }
   1330         }
   1331     }
   1332 }
   1333 
   1334 - (NSString *)deviceName
   1335 {
   1336     NSString *ret = nil;
   1337     for (JOYController *child in _children) {
   1338         if (ret) {
   1339             ret = [ret stringByAppendingFormat:@" + %@", child.deviceName];
   1340         }
   1341         else {
   1342             ret = child.deviceName;
   1343         }
   1344     }
   1345     return ret;
   1346 }
   1347 
   1348 - (NSString *)uniqueID
   1349 {
   1350     NSString *ret = nil;
   1351     for (JOYController *child in _children) {
   1352         if (ret) {
   1353             ret = [ret stringByAppendingFormat:@"+%@", child.uniqueID];
   1354         }
   1355         else {
   1356             ret = child.uniqueID;
   1357         }
   1358     }
   1359     return ret;
   1360 }
   1361 
   1362 - (JOYControllerCombinedType)combinedControllerType
   1363 {
   1364     return JOYControllerCombinedTypeCombined;
   1365 }
   1366 
   1367 - (NSArray<JOYButton *> *)buttons
   1368 {
   1369     NSArray<JOYButton *> *ret = nil;
   1370     for (JOYController *child in _children) {
   1371         if (ret) {
   1372             ret = [ret arrayByAddingObjectsFromArray:child.buttons];
   1373         }
   1374         else {
   1375             ret = child.buttons;
   1376         }
   1377     }
   1378     return ret;
   1379 }
   1380 
   1381 - (NSArray<JOYAxis *> *)axes
   1382 {
   1383     NSArray<JOYAxis *> *ret = nil;
   1384     for (JOYController *child in _children) {
   1385         if (ret) {
   1386             ret = [ret arrayByAddingObjectsFromArray:child.axes];
   1387         }
   1388         else {
   1389             ret = child.axes;
   1390         }
   1391     }
   1392     return ret;
   1393 }
   1394 
   1395 - (NSArray<JOYAxes2D *> *)axes2D
   1396 {
   1397     NSArray<JOYAxes2D *> *ret = nil;
   1398     for (JOYController *child in _children) {
   1399         if (ret) {
   1400             ret = [ret arrayByAddingObjectsFromArray:child.axes2D];
   1401         }
   1402         else {
   1403             ret = child.axes2D;
   1404         }
   1405     }
   1406     return ret;
   1407 }
   1408 
   1409 - (NSArray<JOYAxes3D *> *)axes3D
   1410 {
   1411     NSArray<JOYAxes3D *> *ret = nil;
   1412     for (JOYController *child in _children) {
   1413         if (ret) {
   1414             ret = [ret arrayByAddingObjectsFromArray:child.axes3D];
   1415         }
   1416         else {
   1417             ret = child.axes3D;
   1418         }
   1419     }
   1420     return ret;
   1421 }
   1422 
   1423 - (NSArray<JOYHat *> *)hats
   1424 {
   1425     NSArray<JOYHat *> *ret = nil;
   1426     for (JOYController *child in _children) {
   1427         if (ret) {
   1428             ret = [ret arrayByAddingObjectsFromArray:child.hats];
   1429         }
   1430         else {
   1431             ret = child.hats;
   1432         }
   1433     }
   1434     return ret;
   1435 }
   1436 
   1437 - (void)setRumbleAmplitude:(double)amp
   1438 {
   1439     for (JOYController *child in _children) {
   1440         [child setRumbleAmplitude:amp];
   1441     }
   1442 }
   1443 
   1444 - (void)setPlayerLEDs:(uint8_t)mask
   1445 {
   1446     // Mask is actually just the player ID in a combined controller to
   1447     // allow combining controllers with different LED layouts
   1448     for (JOYController *child in _children) {
   1449         [child setPlayerLEDs:[child LEDMaskForPlayer:mask]];
   1450     }
   1451 }
   1452 
   1453 - (uint8_t)LEDMaskForPlayer:(unsigned)player
   1454 {
   1455     return player;
   1456 }
   1457 
   1458 - (bool)isConnected
   1459 {
   1460     if (![exposedControllers containsObject:self]) {
   1461          // Controller was broken apart
   1462         return false;
   1463     }
   1464     
   1465     for (JOYController *child in _children) {
   1466         if (!child.isConnected) {
   1467             return false; // Should never happen
   1468         }
   1469     }
   1470     
   1471     return true;
   1472 }
   1473 
   1474 - (JOYJoyConType)joyconType
   1475 {
   1476     if (_children.count != 2) return JOYJoyConTypeNone;
   1477     if (_children[0].joyconType == JOYJoyConTypeLeft &&
   1478         _children[1].joyconType == JOYJoyConTypeRight) {
   1479         return JOYJoyConTypeDual;
   1480     }
   1481     
   1482     if (_children[1].joyconType == JOYJoyConTypeLeft &&
   1483         _children[0].joyconType == JOYJoyConTypeRight) {
   1484         return JOYJoyConTypeDual;
   1485     }
   1486      return JOYJoyConTypeNone;
   1487 }
   1488 
   1489 @end

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.