Hacking Question Payload loader for iOS?

Lil_SpazJoekp

Well-Known Member
Newcomer
Joined
Apr 11, 2018
Messages
89
Trophies
0
Age
27
XP
373
Country
United States
Holy crap this advanced hella fast while I was gone.

Is the GitHub updated to the latest stuff? Now would probably be the best time to look at the code lol. Now I feel left out from a question that I asked lol because I don't have an OTG adapter and they all cost a crap load of money and I don't even really know what I'm trying to get.

Does just any kind of cable to female USB adapter work? Cause I c could just get a female to female USB adapter so I could adapt anything.

Sent from my XT1565 using Tapatalk

I was think it needs power this should work. https://www.amazon.com/gp/product/B01F7KJDIM
 

JustBrandonT

Well-Known Member
Newcomer
Joined
Mar 11, 2018
Messages
75
Trophies
0
Age
34
XP
518
Country
Canada
EDIT: Well you posted while I was writing this, looks like you got even further... Maybe some of what I gathered from my exploration of the payload injector, can help you finish off the smashing the stack part! :D

What do you think? We're getting there aren't we! :)

I'm at work atm :D I will definitely check out the code you posted above and try it out. I fell asleep late last night while working on it lol. Yeah man, we made lots of progress fast. I don't know what I was doing last night.

I added the WriteUSB function to take strings because I couldn't debug remotely on iOS10 (my test device) so I used UIAlert to display the switch information.. the alert would show whatever strings were appended to the WriteUSB string array (cheap man's debugger). Reasoning was because everytime I had to unplug the device from Xcode and plug in the switch and I'd have no access to the console so I couldn't see what it was doing. UIAlert helped here lol.

I'll try out your code when I get home and update the project so the code isn't so ugly (possibly make the USB driver stuff a framework and then make the switch specific stuff part of the app itself). I had gotten as far as it kicking me out of RCM last night and couldn't figure out what I was doing wrong with the payload so I ended up using JavaScriptCore.framework to run the Fusee Web Launcher code on the device :D
 

Lil_SpazJoekp

Well-Known Member
Newcomer
Joined
Apr 11, 2018
Messages
89
Trophies
0
Age
27
XP
373
Country
United States
I'm at work atm :D I will definitely check out the code you posted above and try it out. I fell asleep late last night while working on it lol. Yeah man, we made lots of progress fast. I don't know what I was doing last night.

I added the WriteUSB function to take strings because I couldn't debug remotely on iOS10 (my test device) so I used UIAlert to display the switch information.. the alert would show whatever strings were appended to the WriteUSB string array (cheap man's debugger). Reasoning was because everytime I had to unplug the device from Xcode and plug in the switch and I'd have no access to the console so I couldn't see what it was doing. UIAlert helped here lol.

I'll try out your code when I get home and update the project so the code isn't so ugly (possibly make the USB driver stuff a framework and then make the switch specific stuff part of the app itself). I had gotten as far as it kicking me out of RCM last night and couldn't figure out what I was doing wrong with the payload so I ended up using JavaScriptCore.framework to run the Fusee Web Launcher code on the device :D
Could you connect to it over WiFi to debug?
 

Enovale

Hey. I exist. Woo
OP
Member
Joined
Jul 12, 2016
Messages
833
Trophies
0
Location
Narnia
XP
946
Country
United States
I'm at work atm :D I will definitely check out the code you posted above and try it out. I fell asleep late last night while working on it lol. Yeah man, we made lots of progress fast. I don't know what I was doing last night.

I added the WriteUSB function to take strings because I couldn't debug remotely on iOS10 (my test device) so I used UIAlert to display the switch information.. the alert would show whatever strings were appended to the WriteUSB string array (cheap man's debugger). Reasoning was because everytime I had to unplug the device from Xcode and plug in the switch and I'd have no access to the console so I couldn't see what it was doing. UIAlert helped here lol.

I'll try out your code when I get home and update the project so the code isn't so ugly (possibly make the USB driver stuff a framework and then make the switch specific stuff part of the app itself). I had gotten as far as it kicking me out of RCM last night and couldn't figure out what I was doing wrong with the payload so I ended up using JavaScriptCore.framework to run the Fusee Web Launcher code on the device :D
Yeah, you can install and debug Xcode apps over wifi, go to Window - Devices and Simulators - Your device - "Connect over network" (or something)
 

Dread_Pirate_PJ

Well-Known Member
Newcomer
Joined
Feb 24, 2018
Messages
64
Trophies
0
Age
53
XP
178
Country
United States
I really don't want to pay 40 dollars for this random idea though...

If you already have a USB A to USB C cable for loading hekate payloads from a PC, all you need for the iOS device is this:

https://www.apple.com/shop/product/MD821AM/A/lightning-to-usb-camera-adapter

There may be cheaper non-Apple brand ones from Amazon, eBay, Best Buy, Alibaba, etc. Just look for Lightning to female USB A adapter.

--------------------- MERGED ---------------------------

@softwareengineer Looks like your code gets us closer still. I'll give your code a try tomorrow after I get a Lightning to USB adapter.
 

Lil_SpazJoekp

Well-Known Member
Newcomer
Joined
Apr 11, 2018
Messages
89
Trophies
0
Age
27
XP
373
Country
United States
If you already have a USB A to USB C cable for loading hekate payloads from a PC, all you need for the iOS device is this:

https://www.apple.com/shop/product/MD821AM/A/lightning-to-usb-camera-adapter

There may be cheaper non-Apple brand ones from Amazon, eBay, Best Buy, Alibaba, etc. Just look for Lightning to female USB A adapter.

--------------------- MERGED ---------------------------

@softwareengineer Looks like your code gets us closer still. I'll give your code a try tomorrow after I get a Lightning to USB adapter.

According to what @JustBrandonT said here:

Testing it, I actually figured out why the device kept getting disconnected. It's a power issue with the Apple Lightning Camera Kit USB3 (the one that has the USB3 port and the lightning port for charging).

Previously I was testing with just the switch plugged into the OTG and then into the phone.. it'd disconnect every 3 seconds because the switch required more power to that USB3 port which the phone just couldn't provide.. So I plugged a second lightning cable into the OTG's lightning port so the device was charging and the switch into the other port (USB3 port).. Now the device NEVER disconnects or gives intermittent issues. I registered for notifications successfully so now I can tell whenever the switch is plugged in, disconnected, etc..

So from what I understand, you need to have power connected to it because the switch pulls too much.

@JustBrandonT could you confirm this?
 

Dread_Pirate_PJ

Well-Known Member
Newcomer
Joined
Feb 24, 2018
Messages
64
Trophies
0
Age
53
XP
178
Country
United States
What do you think? We're getting there aren't we! :)

I didn't have time to stop by my nearest Apple store to get a Lightning to USB adapter today, but I looked your code over, and what I see missing is putting

[[NSBundle mainBundle] resourcePath]

before INTERMEZZO_PATH or PAYLOAD_PATH in the fopen lines. Otherwise the code looks good.

My personal MacBook is running Mac OS Sierra, so I couldn't open @JustBrandonT's xcodeproj, as it seems to need a new version of Xcode. So I am going to upgrade to High Sierra before going to bed, then I'll try to build and test tomorrow once I get an Lightning to USB adapter.

I'm going to err on the side of caution and get the adapter @JustBrandonT and @Lil_SpazJoekp recommended, with the Lightning power slot.
 
Last edited by Dread_Pirate_PJ,

Dread_Pirate_PJ

Well-Known Member
Newcomer
Joined
Feb 24, 2018
Messages
64
Trophies
0
Age
53
XP
178
Country
United States
@JustBrandonT and @softwareengineer I got the cables I needed and worked on the project a little bit. I have a fork of the code here:

https://github.com/dreadpiratepj/iOUSB

I removed all the code for detecting a rooted device and enumerating the contents of /dev, as it's not needed. Now that we have IOKit, we don't have to root our devices. I may also remove the code to detect MFI device attachment, again, not needed, so it just confused me to find that there.

Everything seems to work. I get the dialog box detecting the APX device, the code detects when the device is disconnected. So far so good.

Once I got everything to build and run without errors, I started debugging. The code in my fork currently fails in function WriteToUSB, at line 303 of USB.m. What I mean is that the if statement at that line doesn't evaluate to true and the rest of the WriteToUSB code isn't run. The WriteToUSB function ends without writing anything, since the first if statement is false.
 
Last edited by Dread_Pirate_PJ,

Lil_SpazJoekp

Well-Known Member
Newcomer
Joined
Apr 11, 2018
Messages
89
Trophies
0
Age
27
XP
373
Country
United States
@JustBrandonT and @softwareengineer I got the cables I needed and worked on the project a little bit. I have a fork of the code here:

https://github.com/dreadpiratepj/iOUSB

I removed all the code for detecting a rooted device and enumerating the contents of /dev, as it's not needed. Now that we have IOKit, we don't have to root our devices. I may also remove the code to detect MFI device attachment, again, not needed, so it just confused me to find that there.

Everything seems to work. I get the dialog box detecting the APX device, the code detects when the device is disconnected. So far so good.

Once I got everything to build and run without errors, I started debugging. The code in my fork currently fails in function WriteToUSB, at line 303 of USB.m. What I mean is that the if statement at that line doesn't evaluate to true and the rest of the WriteToUSB code isn't run. The WriteToUSB function ends without writing anything, since the first if statement is false.
What iOS and Xcode version are you using?
 

Lil_SpazJoekp

Well-Known Member
Newcomer
Joined
Apr 11, 2018
Messages
89
Trophies
0
Age
27
XP
373
Country
United States
Yes, Xcode 9.4. Just upgraded it and Mac OS yesterday. Before yesterday I had Xcode 9.3 and Sierra, but I could not open JustBrandonT's xcodeproj.
So mine is (I’m using your fork) doing the same thing. Stepping through it, it looks like it is dropping the device before it gets to line 303 in of usb.m and looking at
Code:
deviceInterface
it reads null at the If statement but at the line before it reads this
Screen Shot 2018-05-31 at 9.21.05 PM.png
and then at 303 this
Screen Shot 2018-05-31 at 9.21.22 PM.png
Screen Shot 2018-05-31 at 9.21.25 PM.png
. I wonder how far @JustBrandonT has gotten. Hopefully being able to connect to the device wirelessly will help.
 
Last edited by Lil_SpazJoekp,

JustBrandonT

Well-Known Member
Newcomer
Joined
Mar 11, 2018
Messages
75
Trophies
0
Age
34
XP
518
Country
Canada
@JustBrandonT has gotten. Hopefully being able to connect to the device wirelessly will help.

I've gotten as far as making the packet and stuff.. The problem is I can't figure out how to "transfer I/O control" still.. None of the code posted on this thread works to transfer it.. I've made the code cross-platform for iOS and MacOS.. So it can be ran on MacOS and you can see.. Once it runs on MacOS, it'll run on iOS no problem and we'd be done :D

The code I have now is (entirely C++ for a reason):

USBDevice.hxx
Code:
//
//  USBDevice.h
//  iOUSB
//
//  Created by Brandon on 2018-05-28.
//  Copyright © 2018 XIO. All rights reserved.
//

extern "C" {
    #import <IOKit/IOKitLib.h>
    #import <IOKit/usb/IOUSBLib.h>
    #import <IOKit/IOCFPlugIn.h>
    #import <IOKit/IOMessage.h>
    #import <IOKit/IOBSD.h>
    #import <CoreFoundation/CoreFoundation.h>
}

#include <iostream>
#include <vector>

/// Allow formatting of C++ strings
template<typename... Args>
std::string string_format(const std::string& format, Args... args)
{
    size_t size = std::snprintf(nullptr, 0, format.c_str(), args...) + 1;
    std::unique_ptr<char[]> buf(new char[size]);
    std::snprintf(buf.get(), size, format.c_str(), args...);
    return std::string(buf.get(), buf.get() + size - 1);
}

/// Error Codes
extern std::string human_error_string(IOReturn errorCode);


/// Devices
class USBDevice
{
private:
    IOUSBDeviceInterface300** deviceInterface;
    IOUSBInterfaceInterface300** interface;
    io_object_t device;
 
    std::uint8_t controlPipeRef;
    std::uint8_t readPipeRef;
    std::uint8_t writePipeRef;
 
    /// Returns an arbitrary interface from a plugin interface
    void** GetInterface(io_object_t device, CFUUIDRef type, CFUUIDRef uuid);
 
    /// Get Device Property
    int GetProperty(std::string propertyName);
 
    /// Get String Descriptor
    std::string GetStringDescriptor(std::uint8_t index);
 
    /// Load Pipe Refs
    void GetPipeRefs();
 
public:
    USBDevice(io_object_t device);
    ~USBDevice();
 
    /// Properties
    std::string GetName();
    std::string GetClass();
    std::string GetPath();
    std::int32_t GetVendorID();
    std::int32_t GetProductID();
    std::string GetSerialNumber();
    std::string GetManufacturer();
    std::uint32_t GetLocationID();
 
    /// Debug Print an interface
    std::string PrintInterface();
 
    /// Get Max Packet Size for a pipe
    std::uint16_t GetMaxPipePacketSize(std::uint8_t pipeRef);
 
    /// Clear the pipe
    void ClearPipe(std::uint8_t pipeRef, std::uint32_t timeout);
 
    /// Open the Device Interface
    bool Open();
 
    /// Close the Device Interface
    void Close();
 
    /// Get Pipe Status
    kern_return_t GetPipeStatus(std::uint8_t pipeRef);
 
    /// Get Driver Version
    NumVersion GetDriverVersion();
 
    /// Get Control pipe reference
    std::uint8_t GetControlPipeRef();
 
    /// Get Read pipe reference
    std::uint8_t GetReadPipeRef();
 
    /// Get Write pipe reference
    std::uint8_t GetWritePipeRef();
 
    /// Read from the device
    int Read(std::uint8_t* buffer, std::size_t bufferSize);
 
    /// Read from the device asynchronously
    int ReadAsync(std::uint8_t* buffer, std::size_t bufferSize);
 
    /// Write to the device
    int Write(std::uint8_t* buffer, std::size_t bufferSize);
 
    /// Write to the device asynchronously
    int WriteAsync(std::uint8_t* buffer, std::size_t bufferSize);
 
    /// Send Device Request
    int SendDeviceRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout = 0);
 
    /// Send I/O Control Request
    int SendControlRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout = 0);
 
    /// Send Raw Request
    int SendRawRequest(IOUSBDevRequest *request);
 
    /// Send Raw I/O Control Request
    int SendRawControlRequest(IOUSBDevRequest *request, std::uint8_t pipeRef);
};

/// Device Manager
class USBDeviceManager
{
private:
    void* notificationRegistry;
 
public:
    USBDeviceManager();
    ~USBDeviceManager();
 
    std::vector<std::unique_ptr<USBDevice>> GetDevicesMatching(std::string service, int vendorId, int productId);
 
    void RegisterForDeviceNotifications(std::string service, int vendorId, int productId, void(*onDeviceAdded)(USBDevice *device), void(*onDeviceDisconnected)(USBDevice *device));
};

USBDevice.cxx:
Code:
//
//  USBDevice.m
//  iOUSB
//
//  Created by Brandon on 2018-05-28.
//  Copyright © 2018 XIO. All rights reserved.
//

#include "USBDevice.hxx"
#include <memory>

struct USBNotification
{
    USBDeviceManager* _this;
    IONotificationPortRef notificationPort;
    io_iterator_t deviceAddedIterator;
    io_iterator_t deviceRemovedIterator;
    io_object_t notification;
    CFRunLoopRef runLoop;
    void(*onDeviceAdded)(USBDevice *device);
    void(*onDeviceDisconnected)(USBDevice *device);
};

struct DeviceMessage
{
    USBDevice* _this;
    USBNotification* notification;
};

#pragma mark - Utilities

#define CFSAFERELEASE(ref) if (ref) CFRelease(ref)
void DeviceAdded(void *userInfo, io_iterator_t iterator);
void DeviceDisconnected(void *userInfo, io_iterator_t iterator);
void DeviceMessageReceived(void *userInfo, io_service_t service, natural_t messageType, void *messageArgument);

/// Converts IOKit errors to human readable strings.
std::string usb_human_error_string(IOReturn errorCode)
{
//    #define err_get_system(err) (((err)>>26)&0x3f)
//    #define err_get_sub(err) (((err)>>14)&0xfff)
//    #define err_get_code(err) ((err)&0x3fff)
 
    switch (errorCode) {
        case kIOUSBUnknownPipeErr:
            return string_format("Pipe Ref Not Recognized (%08x)", errorCode);
      
        case kIOUSBTooManyPipesErr:
            return string_format("Too Many Pipes (%08x)", errorCode);
      
        case kIOUSBNoAsyncPortErr:
            return string_format("No Async Port (%08x)", errorCode);
      
        case kIOUSBNotEnoughPipesErr:
            return string_format("Not Enough Pipes in Interface (%08x)", errorCode);
      
        case kIOUSBNotEnoughPowerErr:
            return string_format("Not Enough Power for Selected Configuration (%08x)", errorCode);
      
        case kIOUSBEndpointNotFound:
            return string_format("Endpoint Not Found (%08x)", errorCode);
      
        case kIOUSBConfigNotFound:
            return string_format("Configuration Not Found (%08x)", errorCode);
      
        case kIOUSBTransactionTimeout:
            return string_format("Transaction Timed Out (%08x)", errorCode);
      
        case kIOUSBTransactionReturned:
            return string_format("Transaction has been returned to the caller (%08x)", errorCode);
      
        case kIOUSBPipeStalled:
            return string_format("Pipe has stalled, Error needs to be cleared (%08x)", errorCode);
      
        case kIOUSBInterfaceNotFound:
            return string_format("Interface Ref Not Recognized (%08x)", errorCode);
      
        case kIOUSBLowLatencyBufferNotPreviouslyAllocated:
            return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the data buffer) first (%08x)", errorCode);
      
        case kIOUSBLowLatencyFrameListNotPreviouslyAllocated:
            return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the frame list) first (%08x)", errorCode);
      
        case kIOUSBHighSpeedSplitError:
            return string_format("Error to hub on high speed bus trying to do split transaction (%08x)", errorCode);
      
        case kIOUSBSyncRequestOnWLThread:
            return string_format("A synchronous USB request was made on the workloop thread (from a callback?).  Only async requests are permitted in that case (%08x)", errorCode);
      
        case kIOUSBDeviceTransferredToCompanion:
            return string_format("The device has been tranferred to another controller for enumeration (%08x)", errorCode);
      
        case kIOUSBClearPipeStallNotRecursive:
            return string_format("ClearPipeStall should not be called recursively (%08x)", errorCode);
      
        case kIOUSBDevicePortWasNotSuspended:
            return string_format("Port was not suspended (%08x)", errorCode);
      
        case kIOUSBEndpointCountExceeded:
            return string_format("The endpoint was not created because the controller cannot support more endpoints (%08x)", errorCode);
      
        case kIOUSBDeviceCountExceeded:
            return string_format("The device cannot be enumerated because the controller cannot support more devices (%08x)", errorCode);
      
        case kIOUSBStreamsNotSupported:
            return string_format("The request cannot be completed because the XHCI controller does not support streams (%08x)", errorCode);
      
        case kIOUSBInvalidSSEndpoint:
            return string_format("An endpoint found in a SuperSpeed device is invalid (usually because there is no Endpoint Companion Descriptor) (%08x)", errorCode);
      
        case kIOUSBTooManyTransactionsPending:
            return string_format("The transaction cannot be submitted because it would exceed the allowed number of pending transactions (%08x)", errorCode);
      
        default:
            return string_format("Error Code (%08x)\n-- System: (%02X), SubSystem: (%02X), Code: (%02X) ", errorCode, err_get_system(errorCode), err_get_sub(errorCode), err_get_code(errorCode));
    }
}

std::string human_error_string(IOReturn errorCode)
{
    switch (errorCode) {
        case kIOReturnSuccess:
            return string_format("Success (%08x)", errorCode);
      
        case kIOReturnError:
            return string_format("General Error (%08x)", errorCode);
      
        case kIOReturnNoMemory:
            return string_format("Cannot Allocate Memory (%08x)", errorCode);
      
        case kIOReturnNoResources:
            return string_format("Resource Shortage (%08x)", errorCode);
      
        case kIOReturnIPCError:
            return string_format("IPC Error (%08x)", errorCode);
      
        case kIOReturnNoDevice:
            return string_format("No Such Device (%08x)", errorCode);
      
        case kIOReturnNotPrivileged:
            return string_format("Insufficient Privileges (%08x)", errorCode);
      
        case kIOReturnBadArgument:
            return string_format("Invalid Argument (%08x)", errorCode);
      
        case kIOReturnLockedRead:
            return string_format("Device Read Locked (%08x)", errorCode);
      
        case kIOReturnLockedWrite:
            return string_format("Device Write Locked (%08x)", errorCode);
      
        case kIOReturnExclusiveAccess:
            return string_format("Exclusive Access and Device already opened (%08x)", errorCode);
      
        case kIOReturnBadMessageID:
            return string_format("Sent/Received Messages had different MSG_ID (%08x)", errorCode);
      
        case kIOReturnUnsupported:
            return string_format("Unsupported Function (%08x)", errorCode);
      
        case kIOReturnVMError:
            return string_format("Misc. VM Failure (%08x)", errorCode);
      
        case kIOReturnInternalError:
            return string_format("Internal Error (%08x)", errorCode);
      
        case kIOReturnIOError:
            return string_format("General I/O Error (%08x)", errorCode);
      
        case kIOReturnCannotLock:
            return string_format("Can't Acquire Lock (%08x)", errorCode);
      
        case kIOReturnNotOpen:
            return string_format("Device Not Open (%08x)", errorCode);
      
        case kIOReturnNotReadable:
            return string_format("Read Not Supported (%08x)", errorCode);
      
        case kIOReturnNotWritable:
            return string_format("Write Not Supported (%08x)", errorCode);
      
        case kIOReturnNotAligned:
            return string_format("Alignment Error (%08x)", errorCode);
      
        case kIOReturnBadMedia:
            return string_format("Media Error (%08x)", errorCode);
      
        case kIOReturnStillOpen:
            return string_format("Device(s) Still Open (%08x)", errorCode);
      
        case kIOReturnRLDError:
            return string_format("RLD Failure (%08x)", errorCode);
      
        case kIOReturnDMAError:
            return string_format("DMA Failure (%08x)", errorCode);
      
        case kIOReturnBusy:
            return string_format("Device Busy (%08x)", errorCode);
      
        case kIOReturnTimeout:
            return string_format("I/O Timeout (%08x)", errorCode);
      
        case kIOReturnOffline:
            return string_format("Device Offline (%08x)", errorCode);
      
        case kIOReturnNotReady:
            return string_format("Not Ready (%08x)", errorCode);
      
        case kIOReturnNotAttached:
            return string_format("Device Not Attached (%08x)", errorCode);
      
        case kIOReturnNoChannels:
            return string_format("No DMA Channels Left (%08x)", errorCode);
      
        case kIOReturnNoSpace:
            return string_format("No Space For Data (%08x)", errorCode);
      
        case kIOReturnPortExists:
            return string_format("Port Already Exists (%08x)", errorCode);
      
        case kIOReturnCannotWire:
            return string_format("Can't Write Down (%08x)", errorCode);
      
        case kIOReturnNoInterrupt:
            return string_format("No Interrupt Attached (%08x)", errorCode);
      
        case kIOReturnNoFrames:
            return string_format("No DMA Frames Enqueued (%08x)", errorCode);
      
        case kIOReturnMessageTooLarge:
            return string_format("Oversized MSG Received On Interrupt Port (%08x)", errorCode);
      
        case kIOReturnNotPermitted:
            return string_format("Not Permitted (%08x)", errorCode);
      
        case kIOReturnNoPower:
            return string_format("No Power To Device (%08x)", errorCode);
      
        case kIOReturnNoMedia:
            return string_format("Media Not Present (%08x)", errorCode);
      
        case kIOReturnUnformattedMedia:
            return string_format("Media Not Formatted (%08x)", errorCode);
      
        case kIOReturnUnsupportedMode:
            return string_format("No Such Mode (%08x)", errorCode);
      
        case kIOReturnUnderrun:
            return string_format("Buffer Underflow (%08x)", errorCode);
      
        case kIOReturnOverrun:
            return string_format("Buffer Overflow (%08x)", errorCode);
      
        case kIOReturnDeviceError:
            return string_format("The Device Is Not Working Properly! (%08x)", errorCode);
      
        case kIOReturnNoCompletion:
            return string_format("A Completion Routine Is Required (%08x)", errorCode);
      
        case kIOReturnAborted:
            return string_format("Operation Aborted (%08x)", errorCode);
      
        case kIOReturnNoBandwidth:
            return string_format("Bus Bandwidth Would Be Exceeded (%08x)", errorCode);
      
        case kIOReturnNotResponding:
            return string_format("Device Not Responding (%08x)", errorCode);
      
        case kIOReturnIsoTooOld:
            return string_format("ISOChronous I/O request for distance past! (%08x)", errorCode);
      
        case kIOReturnIsoTooNew:
            return string_format("ISOChronous I/O request for distant future! (%08x)", errorCode);
      
        case kIOReturnNotFound:
            return string_format("Data Not Found (%08x)", errorCode);
      
        case kIOReturnInvalid:
            return string_format("Should Never Be Seen(%08x)", errorCode);
      
        default:
            /// See here for more: https://developer.apple.com/library/content/qa/qa1075/_index.html
            return usb_human_error_string(errorCode);
    }
}


#pragma mark - Device Private
USBDevice::USBDevice(io_object_t device) : deviceInterface(nullptr), interface(nullptr), device(device), controlPipeRef(0x00), readPipeRef(0x01), writePipeRef(0x02)
{
    IOObjectRetain(device);
}

USBDevice::~USBDevice()
{
    Close();
    IOObjectRelease(device);
}

void** USBDevice::GetInterface(io_object_t device, CFUUIDRef type, CFUUIDRef uuid)
{
    /// Create a plugin interface for the service.
    IOCFPlugInInterface **plugInInterface = nullptr;
    SInt32 score = 0;
 
    kern_return_t kr = IOCreatePlugInInterfaceForService(device, type, kIOCFPlugInInterfaceID, &plugInInterface, &score);
 
    if ((kIOReturnSuccess != kr) || !plugInInterface)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return nullptr;
    }
 
    /// Get an interface from the plugin.. and release the plugin
    void **interface = nullptr;
    HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(uuid), (LPVOID*) &interface);
    (*plugInInterface)->Release(plugInInterface);
 
    if (res || !interface)
    {
        return nullptr;
    }
    return interface;
}

int USBDevice::GetProperty(std::string propertyName)
{
    CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, propertyName.c_str(), kCFStringEncodingUTF8);
    CFNumberRef number = (CFNumberRef)IORegistryEntryCreateCFProperty(device, name, kCFAllocatorDefault, 0);
 
    if (number)
    {
        int value = 0;
        CFNumberGetValue(number, kCFNumberSInt32Type, &value);
        CFSAFERELEASE(number);
        CFSAFERELEASE(name);
        return value;
    }
 
    CFSAFERELEASE(name);
    return -1;
}

std::string USBDevice::GetStringDescriptor(std::uint8_t index)
{
    std::uint8_t requestBuffer[256];
    IOUSBDevRequest request = {
        .bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice),
        .bRequest = kUSBRqGetDescriptor,
        .wValue = static_cast<std::uint16_t>((kUSBStringDesc << 8) | index),
        .wIndex = 0x409, //English
        .wLength = sizeof(requestBuffer),
        .pData = requestBuffer
    };
 
    kern_return_t kr = (*deviceInterface)->DeviceRequest(deviceInterface, &request);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return "";
    }
 
    CFStringRef descriptor = CFStringCreateWithBytes(kCFAllocatorDefault, &requestBuffer[2], requestBuffer[0] - 2, kCFStringEncodingUTF16LE, false);
 
    CFIndex length = CFStringGetLength(descriptor);
    CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
    std::string result(maxSize, '\0');
    CFStringGetCString(descriptor, &result[0], maxSize, kCFStringEncodingUTF8);
    CFSAFERELEASE(descriptor);
    return result;
}

void USBDevice::GetPipeRefs()
{
    controlPipeRef = 0x00;
    readPipeRef = 0x01;
    writePipeRef = 0x02;
 
    if (interface)
    {
        UInt8 interfaceNumEndpoints = 0;
        IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
        if (err)
        {
            std::cerr<<human_error_string(err)<<"\n";
            return;
        }
  
        for (UInt8 ref = 1; ref <= interfaceNumEndpoints; ++ref)
        {
            UInt8 direction;
            UInt8 number;
            UInt8 transferType;
            UInt16 maxPacketSize;
            UInt8 interval;
      
            IOReturn kr = (*interface)->GetPipeProperties(interface, ref, &direction, &number, &transferType, &maxPacketSize, &interval);
      
            if (kr == kIOReturnSuccess)
            {
                if (transferType == kUSBControl)
                {
                    controlPipeRef = 0;
                }
                else if (transferType == kUSBBulk)
                {
                    if (direction == kUSBIn)
                    {
                        readPipeRef = ref;
                    }
                    else if (direction == kUSBOut)
                    {
                        writePipeRef = ref;
                    }
                }
            }
        }
    }
}

std::string USBDevice::GetName()
{
    io_string_t name = {0};
    kern_return_t kr = IORegistryEntryGetName(device, name);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
    }
    return std::string(name);
}

std::string USBDevice::GetClass()
{
    io_string_t name = {0};
    kern_return_t kr = IOObjectGetClass(device, name);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
    }
 
    return std::string(name);
}

std::string USBDevice::GetPath()
{
    io_string_t path = {0};
    kern_return_t kr = IORegistryEntryGetPath(device, kIOServicePlane, path);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
    }
 
    return std::string(path);
}

std::int32_t USBDevice::GetVendorID()
{
    return GetProperty(kUSBVendorID);
}

std::int32_t USBDevice::GetProductID()
{
    return GetProperty(kUSBProductID);
}

std::string USBDevice::GetSerialNumber()
{
    if (deviceInterface)
    {
        std::uint8_t index = 0;
        IOReturn kr = (*deviceInterface)->USBGetSerialNumberStringIndex(deviceInterface, &index);
        if (kr != kIOReturnSuccess)
        {
            std::cerr<<human_error_string(kr)<<"\n";
            return "";
        }
  
        return GetStringDescriptor(index);
    }
    return "";
}

std::string USBDevice::GetManufacturer()
{
    if (deviceInterface)
    {
        std::uint8_t index = 0;
        IOReturn kr = (*deviceInterface)->USBGetManufacturerStringIndex(deviceInterface, &index);
        if (kr != kIOReturnSuccess)
        {
            std::cerr<<human_error_string(kr)<<"\n";
            return "";
        }
  
        return GetStringDescriptor(index);
    }
    return "";
}

std::uint32_t USBDevice::GetLocationID()
{
    UInt32 locationID = -1;
    IOUSBDeviceInterface **deviceInterface = reinterpret_cast<IOUSBDeviceInterface **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID));
    if (deviceInterface)
    {
        // Get the location ID from the USB Interface
        (*deviceInterface)->GetLocationID(deviceInterface, &locationID);
  
        // Cleanup
        (*deviceInterface)->Release(deviceInterface);
        deviceInterface = nil;
    }
 
    return locationID;
}

std::string USBDevice::PrintInterface()
{
    UInt8 interfaceNumEndpoints = 0;
    IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
    if (err)
    {
        std::cerr<<human_error_string(err)<<"\n";
        return string_format("Unable to get number of endpoints (%08x)", err);
    }
 
    std::string output = string_format("Interface has %d endpoints\n", interfaceNumEndpoints);
 
    /// USUALLY..
    /// Index 0 = DeviceControl/ControlRequest.
    /// Index 1 = DeviceRead
    /// Index 2 = DeviceWrite
    for (UInt8 pipeRef = 1; pipeRef <= interfaceNumEndpoints; ++pipeRef)
    {
        UInt8 direction;
        UInt8 number;
        UInt8 transferType;
        UInt16 maxPacketSize;
        UInt8 interval;
  
        IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval);
  
        if (kr != kIOReturnSuccess)
        {
            output += string_format("Unable to get properties of pipe %d (%08x)\n", pipeRef, kr);
        }
        else
        {
            std::string directions[] = {"Out", "In", "None", "Any"};
            std::string transferTypes[] = {"Control", "ISOC", "Bulk", "Interrupt", "Any"};
      
            output += string_format("Pipe: %d, Direction: %s, TransferType: %s, MaxPacketSize: %d\n", pipeRef, directions[direction].c_str(), transferTypes[transferType].c_str(), maxPacketSize);
        }
    }
 
    return output;
}

std::uint16_t USBDevice::GetMaxPipePacketSize(std::uint8_t pipeRef)
{
    UInt8 direction;
    UInt8 number;
    UInt8 transferType;
    UInt16 maxPacketSize;
    UInt8 interval;
 
    IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval);
    if (kr != kIOReturnSuccess)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return 0;
    }
 
    return maxPacketSize;
}

void USBDevice::ClearPipe(std::uint8_t pipeRef, std::uint32_t timeout)
{
    uint16_t maxSize = GetMaxPipePacketSize(pipeRef);
    std::unique_ptr<char[]> readBuffer(new char[maxSize]);

    while(true)
    {
        std::uint32_t readSize = maxSize;
        bzero(readBuffer.get(), maxSize * sizeof(char));
        IOReturn kr = (*interface)->ReadPipeTO(interface, pipeRef, readBuffer.get(), (UInt32 *)&readSize, timeout, timeout);
        if (kr != kIOReturnSuccess)
        {
            std::cerr<<human_error_string(kr)<<"\n";
            break;
        }

        if (readSize == 0)
        {
            break;
        }
    }
 
    IOReturn kr = (*interface)->ClearPipeStallBothEnds(interface, pipeRef);
    if (kr)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return;
    }
}

bool USBDevice::Open()
{
    // Open the USB device for communication.
    deviceInterface = reinterpret_cast<IOUSBDeviceInterface300 **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID300));
 
    if (deviceInterface)
    {
        kern_return_t kr = (*deviceInterface)->USBDeviceOpen(deviceInterface);
        if (kr != kIOReturnSuccess)
        {
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
      
            std::cerr<<human_error_string(kr)<<"\n";
            return false;
        }
  

        //Get the configuration..
        IOUSBConfigurationDescriptorPtr config;
        kr = (*deviceInterface)->GetConfigurationDescriptorPtr(deviceInterface, 0, &config);
        if (kr != kIOReturnSuccess)
        {
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
      
            std::cerr<<human_error_string(kr)<<"\n";
            return false;
        }
  
        //Set the configuration..
        (*deviceInterface)->SetConfiguration(deviceInterface, config->bConfigurationValue);
  
        //Find the USB interface..
        IOUSBFindInterfaceRequest interfaceRequest;
        interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
  
        //Get an interface iterator..
        io_iterator_t iterator;
        kr = (*deviceInterface)->CreateInterfaceIterator(deviceInterface, &interfaceRequest, &iterator);
        if (kr != kIOReturnSuccess)
        {
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
      
            std::cerr<<human_error_string(kr)<<"\n";
            return false;
        }
  
        //Get the device object
        io_object_t usbDevice = IOIteratorNext(iterator);
        if (!usbDevice)
        {
            IOObjectRelease(iterator);
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
            return false;
        }
  
        //Get a USB Interface
        interface = reinterpret_cast<IOUSBInterfaceInterface300 **>(GetInterface(usbDevice, kIOUSBInterfaceUserClientTypeID, kIOUSBInterfaceInterfaceID300));
  
        if (!interface)
        {
            IOObjectRelease(usbDevice);
            IOObjectRelease(iterator);
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
            return false;
        }


        kr = (*interface)->USBInterfaceOpen(interface);
        if (kr != kIOReturnSuccess)
        {
            (*interface)->Release(interface);
            interface = nullptr;
      
            IOObjectRelease(usbDevice);
            IOObjectRelease(iterator);
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
            return false;
        }
  
        //Load the pipe references
        GetPipeRefs();
  
        //Create Async Notifications
        //TODO: Release this event..
        CFRunLoopSourceRef asyncEventSource;
        kr = (*interface)->CreateInterfaceAsyncEventSource(interface, &asyncEventSource);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), asyncEventSource, kCFRunLoopDefaultMode);
  
        IOObjectRelease(usbDevice);
        IOObjectRelease(iterator);
        return true;
    }
 
    return false;
}

void USBDevice::Close()
{
    if (interface)
    {
        (*interface)->USBInterfaceClose(interface);
        (*interface)->Release(interface);
        interface = nullptr;
    }
 
    if (deviceInterface)
    {
        (*deviceInterface)->USBDeviceClose(deviceInterface);
        (*deviceInterface)->Release(deviceInterface);
        deviceInterface = nullptr;
    }
}

kern_return_t USBDevice::GetPipeStatus(std::uint8_t pipeRef)
{
    if (interface)
    {
        return (*interface)->GetPipeStatus(interface, pipeRef);
    }
 
    return kIOReturnNotOpen;
}

NumVersion USBDevice::GetDriverVersion()
{
    if (interface)
    {
        NumVersion ioUSBLibVersion;
        NumVersion usbFamilyVersion;
        (*interface)->GetIOUSBLibVersion(interface, &ioUSBLibVersion, &usbFamilyVersion);
        return ioUSBLibVersion;
    }
 
    return NumVersion{0};
}

std::uint8_t USBDevice::GetControlPipeRef()
{
    return controlPipeRef;
}

std::uint8_t USBDevice::GetReadPipeRef()
{
    return readPipeRef;
}

std::uint8_t USBDevice::GetWritePipeRef()
{
    return writePipeRef;
}

void onReadAsync(void *refcon, IOReturn result, void *arg0)
{
 
}

void onWriteAsync(void *refcon, IOReturn result, void *arg0)
{
 
}

int USBDevice::Read(std::uint8_t* buffer, std::size_t bufferSize)
{
    if (interface)
    {
        UInt8 pipeRef = readPipeRef;
        UInt32 size = static_cast<UInt32>(bufferSize);
        kern_return_t res = (*interface)->ReadPipe(interface, pipeRef, buffer, &size);
        return res != kIOReturnSuccess ? -1 : static_cast<int>(size);
    }
    return -1;
}

int USBDevice::ReadAsync(std::uint8_t* buffer, std::size_t bufferSize)
{
    if (interface)
    {
        UInt8 pipeRef = readPipeRef;
        UInt32 size = static_cast<UInt32>(bufferSize);
        kern_return_t res = (*interface)->ReadPipeAsync(interface, pipeRef, buffer, size, onReadAsync, this);
        return res != kIOReturnSuccess ? -1 : static_cast<int>(size);
    }
    return -1;
}

int USBDevice::Write(uint8_t* buffer, size_t bufferSize)
{
    UInt8 pipeRef = writePipeRef;
    int packetSize = GetMaxPipePacketSize(pipeRef);
 
    size_t bytesWritten = 0;
    int bytesRemaining = static_cast<int>(bufferSize);
 
    auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) {
        kern_return_t res = (*interface)->WritePipe(interface, pipeRef, buffer, static_cast<int>(bufferSize));
        return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize);
    };
 
    while (bytesRemaining > 0)
    {
        size_t bytesToWrite = std::min(bytesRemaining, packetSize);
        auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite);
        if (retVal < 0)
        {
            return retVal;
        }
        else if (retVal < static_cast<int>(bytesToWrite))
        {
            return static_cast<int>(bytesWritten) + retVal;
        }
  
        bytesWritten += retVal;
        bytesRemaining -= retVal;
    }
 
    return static_cast<int>(bytesWritten);
}

int USBDevice::WriteAsync(std::uint8_t* buffer, std::size_t bufferSize)
{
    UInt8 pipeRef = writePipeRef;
    int packetSize = GetMaxPipePacketSize(pipeRef);
 
    size_t bytesWritten = 0;
    int bytesRemaining = static_cast<int>(bufferSize);
 
    auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) {
        kern_return_t res = (*interface)->WritePipeAsync(interface, pipeRef, buffer, static_cast<int>(bufferSize), onWriteAsync, this);
        return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize);
    };
 
    while (bytesRemaining > 0)
    {
        size_t bytesToWrite = std::min(bytesRemaining, packetSize);
        auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite);
        if (retVal < 0)
        {
            return retVal;
        }
        else if (retVal < static_cast<int>(bytesToWrite))
        {
            return static_cast<int>(bytesWritten) + retVal;
        }
  
        bytesWritten += retVal;
        bytesRemaining -= retVal;
    }
 
    return static_cast<int>(bytesWritten);
}

int USBDevice::SendDeviceRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout)
{
    if (!deviceInterface)
    {
        return -1;
    }
 
    //If there's no timeout.. just send a regular request..
    if (timeout <= 0)
    {
        IOUSBDevRequest req;
        req.bmRequestType = requesttype;
        req.bRequest = request;
        req.wValue = value;
        req.wIndex = index;
        req.wLength = size;
        req.pData = bytes;
  
        kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, &req);
        return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
    }
 
    //Send Timeout request..
    IOUSBDevRequestTO req;
    req.bmRequestType = requesttype;
    req.bRequest = request;
    req.wValue = value;
    req.wIndex = index;
    req.wLength = size;
    req.pData = bytes;
    req.completionTimeout = timeout;
 
    kern_return_t result = (*deviceInterface)->DeviceRequestTO(deviceInterface, &req);
    return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}

int USBDevice::SendControlRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout)
{
    if (!interface)
    {
        return -1;
    }
 
    //If there's no timeout.. just send a regular request..
    if (timeout <= 0)
    {
        IOUSBDevRequest req;
        req.bmRequestType = requesttype;
        req.bRequest = request;
        req.wValue = value;
        req.wIndex = index;
        req.wLength = size;
        req.pData = bytes;
  
        kern_return_t result = (*interface)->ControlRequest(interface, controlPipeRef, &req);
        return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
    }
 
    //Send Timeout request..
    IOUSBDevRequestTO req;
    req.bmRequestType = requesttype;
    req.bRequest = request;
    req.wValue = value;
    req.wIndex = index;
    req.wLength = size;
    req.pData = bytes;
    req.completionTimeout = timeout;
 
    kern_return_t result = (*interface)->ControlRequestTO(interface, controlPipeRef, &req);
    return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}

int USBDevice::SendRawRequest(IOUSBDevRequest *request)
{
    if (!deviceInterface)
    {
        return -1;
    }
    kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, request);
    return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred..
}

int USBDevice::SendRawControlRequest(IOUSBDevRequest *request, std::uint8_t pipeRef)
{
    if (!interface)
    {
        return -1;
    }
    kern_return_t result = (*interface)->ControlRequest(interface, pipeRef, request);
    return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred..
}

#pragma mark - USB Public

USBDeviceManager::USBDeviceManager() : notificationRegistry(nullptr)
{
}

USBDeviceManager::~USBDeviceManager()
{
    if (this->notificationRegistry)
    {
        USBNotification *notification = static_cast<USBNotification *>(this->notificationRegistry);
        delete notification;
    }
}

std::vector<std::unique_ptr<USBDevice>> USBDeviceManager::GetDevicesMatching(std::string service, int vendorId, int productId)
{
    io_iterator_t iter = 0;
 
    /// Find Matching Services
    CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str());
    if (matchingDict == nil)
    {
        return std::vector<std::unique_ptr<USBDevice>> {};
    }
 
    /// Added VendorID and ProductID to query
    CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef);
    CFRelease(numberRef);
 
    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef);
    CFRelease(numberRef);
    numberRef = nullptr;
 
    /// Find devices matching the above criteria
    kern_return_t kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return std::vector<std::unique_ptr<USBDevice>> {};
    }
 
    /// Iterate over all devices
    io_service_t device = 0;
    std::vector<std::unique_ptr<USBDevice>> devices;
    while ((device = IOIteratorNext(iter)))
    {
        devices.push_back(std::unique_ptr<USBDevice>(new USBDevice(device)));
    }
 
    IOObjectRelease(iter);
    return devices;
}

void USBDeviceManager::RegisterForDeviceNotifications(std::string service, int vendorId, int productId, void(*onDeviceAdded)(USBDevice *device), void(*onDeviceDisconnected)(USBDevice *device))
{
    /// Find Matching Services
    CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str());
    if (matchingDict == nil)
    {
        return;
    }
 
    /// Added VendorID and ProductID to query
    CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef);
    CFRelease(numberRef);
 
    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef);
    CFRelease(numberRef);
    numberRef = nullptr;
 
    // Setup notifications for when the device is available
    USBNotification *notificationInfo = new USBNotification();
    notificationInfo->_this = this;
    notificationInfo->onDeviceAdded = onDeviceAdded;
    notificationInfo->onDeviceDisconnected = onDeviceDisconnected;
    notificationInfo->notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
    notificationInfo->runLoop = CFRunLoopGetCurrent();
    notificationInfo->deviceAddedIterator = 0;
    notificationInfo->deviceRemovedIterator = 0;
    this->notificationRegistry = notificationInfo;
 
    CFRunLoopSourceRef runLoopSource = IONotificationPortGetRunLoopSource(notificationInfo->notificationPort);
    CFRunLoopAddSource(notificationInfo->runLoop, runLoopSource, kCFRunLoopDefaultMode);
 
    // Now set up a notification to be called when a device is matched and detached by I/O Kit.
    kern_return_t rc;
//    rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort,
//                                          kIOTerminatedNotification,
//                                          matchingDict,
//                                          DeviceDisconnected,
//                                          notificationInfo, //userInfo
//                                          &notificationInfo->deviceRemovedIterator);
//
//    if (rc != kIOReturnSuccess)
//    {
//        IONotificationPortDestroy(notificationInfo->notificationPort);
//        delete notificationInfo;
//        std::cerr<<human_error_string(rc)<<"\n";
//        return;
//    }
 
    // Now set up a notification to be called when a device is matched and attached by I/O Kit.
    rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort,
                                          kIOMatchedNotification,
                                          matchingDict,
                                          DeviceAdded,
                                          notificationInfo, //userInfo
                                          &notificationInfo->deviceAddedIterator);
 
    if (rc != kIOReturnSuccess)
    {
        IONotificationPortDestroy(notificationInfo->notificationPort);
        delete notificationInfo;
        std::cerr<<human_error_string(rc)<<"\n";
        return;
    }
 
//    DeviceDisconnected(notificationInfo, notificationInfo->deviceRemovedIterator);
    DeviceAdded(notificationInfo, notificationInfo->deviceAddedIterator);
}

// When a device has been plugged into the phone's serial port, this function will get called
void DeviceAdded(void *userInfo, io_iterator_t iterator)
{
    kern_return_t kr;
    io_service_t usbDevice;
 
    USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo);
 
    // Iterate over all devices..
    while ((usbDevice = IOIteratorNext(iterator))) {
  
        DeviceMessage* notificationMessage = new DeviceMessage();
        notificationMessage->_this = new USBDevice(usbDevice);
        notificationMessage->notification = notificationInfo;
  
        if (notificationInfo->onDeviceAdded)
        {
            notificationInfo->onDeviceAdded(notificationMessage->_this);
        }
  
        // Register for Device Notifications.. IE: Disconnected Notification..
        kr = IOServiceAddInterestNotification(notificationInfo->notificationPort,
                                              usbDevice,
                                              kIOGeneralInterest,
                                              DeviceMessageReceived,
                                              notificationMessage, //userInfo
                                              &notificationInfo->notification);
        // Cleanup
        kr = IOObjectRelease(usbDevice);
    }
}

// When a device has been unplugged from the phone's serial port, this function will get called.
void DeviceDisconnected(void *userInfo, io_iterator_t iterator)
{
    USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo);
    if (notificationInfo->onDeviceDisconnected)
    {
        notificationInfo->onDeviceDisconnected(nullptr);
    }
 
    // Cleanup
    IOObjectRelease(notificationInfo->notification);
    delete notificationInfo;
}

// When a device message has been received..
void DeviceMessageReceived(void* userInfo, io_service_t service, uint32_t messageType, void* messageArgument)
{
    DeviceMessage *message = static_cast<DeviceMessage *>(userInfo);
 
    // If it's the disconnect message, we need to cleanup..
    if (messageType == kIOMessageServiceIsTerminated)
    {
        if (message->notification && message->notification->onDeviceDisconnected)
        {
            message->notification->onDeviceDisconnected(message->_this);
        }
  
        // Cleanup
//        IOObjectRelease(message->notification->notificationPort);
        delete message;
    }
}


Then you use it for Nintendo Switch like:

main.mm:
Code:
//
//  main.m
//  USBLoader
//
//  Created by Brandon on 2018-05-30.
//  Copyright © 2018 XIO. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/usb/IOUSBLib.h>
#import <IOKit/IOCFPlugIn.h>
#import <IOKit/IOMessage.h>
#import <IOKit/IOBSD.h>
#include <vector>
#include <iostream>
#include "USBDevice.hxx"

//Include some headers that read the fusee.bin and intermezzo and create the Fusee Gelee Payload..
#include "Fusee.h"

// Helper file to try to smash the stack..
#include "Smash.hpp"

//Nintendo Constants
#define kNintendoSwitchVendorID 0x0955
#define kNintendoSwitchProductID 0x7321

#pragma mark - Utilities

void displayStrings(std::vector<std::string> strings)
{
    for (std::string& str : strings)
    {
        std::cout<<str<<"\n";
    }
}

void displayStrings(NSArray<NSString *> *strings)
{
    for (NSString *str in strings)
    {
        NSLog(@"%@", str);
    }
}

void onDeviceAdded(USBDevice *device)
{
    if (!device->Open())
    {
        std::cerr<<"Cannot Open Device\n";
        return;
    }
 
    std::string manufacturer = device->GetManufacturer();
    std::string name = device->GetName();
    std::string clazz = device->GetClass();
    std::string path = device->GetPath();
    std::int32_t vendorId = device->GetVendorID();
    std::int32_t productId = device->GetProductID();
    std::uint32_t locationId = device->GetLocationID();
 
    displayStrings({
        "       Device Information",
        "-----------------------------------",
        string_format("Manufacturer: %s", manufacturer.c_str()),
        string_format("Name: %s", name.c_str()),
        string_format("Class: %s", clazz.c_str()),
        string_format("Path: %s", path.c_str()),
        string_format("VendorId: 0x%04X", vendorId),
        string_format("ProductId: 0x%04X", productId),
        string_format("LocationId: 0x%04X", locationId)
    });
 
    //device->ClearPipe(device->GetReadPipeRef(), 10);
    //device->ClearPipe(device->GetWritePipeRef(), 10);  //I don't have async event in the above class yet (it'll take a few lines of code to fix.. this function uses async read.. if changed to just read, it'll work).. in fact I think I can remove the read all together and just call clear stall.. - TODO: Brandon T investigate.
 
    uint8_t deviceId[0x10] = {0};
    if (device->Read(deviceId, sizeof(deviceId)) != sizeof(deviceId))
    {
        displayStrings({"Cannot Read Device-ID"});
        return;
    }
 
    std::cout<<"\n\n";
    std::string deviceSerialNumber = string_format("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
                                                   (uint32_t)deviceId[0],
                                                   (uint32_t)deviceId[1],
                                                   (uint32_t)deviceId[2],
                                                   (uint32_t)deviceId[3],
                                                   (uint32_t)deviceId[4],
                                                   (uint32_t)deviceId[5],
                                                   (uint32_t)deviceId[6],
                                                   (uint32_t)deviceId[7],
                                                   (uint32_t)deviceId[8],
                                                   (uint32_t)deviceId[9],
                                                   (uint32_t)deviceId[10],
                                                   (uint32_t)deviceId[11],
                                                   (uint32_t)deviceId[12],
                                                   (uint32_t)deviceId[13],
                                                   (uint32_t)deviceId[14],
                                                   (uint32_t)deviceId[15]);
 
    displayStrings({
        string_format("Device Id: %s\n\n", deviceSerialNumber.c_str()),
        "Loading Payload"
    });
 
    auto payload = createRCMPayload(intermezzo, fusee);
 
    displayStrings({"Uploading Payload"});
    smash(device, payload);
 
    device->Close();
    return;
}

void onDeviceDisconnected(USBDevice *device)
{
    std::cout<<"Device Disconnected\n";
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        USBDeviceManager device;
        device.RegisterForDeviceNotifications("IOUSBHostDevice",
                                              kNintendoSwitchVendorID,
                                              kNintendoSwitchProductID,
                                              onDeviceAdded,
                                              onDeviceDisconnected);
        CFRunLoopRun();
    }
    return 0;
}


It uploads the payload successfully.. I just can't get it to freaking smash the damn stack.. grr.. Also, once you "read" the deviceId, the control of the device is switched so you CANNOT run the code again without restarting the device.. I tried libusb and it behaves the same way so it means the code above is working as it should.. I just have to figure out how to smash the stack and that's it.


Functions I added: "human_error_string" will print out any errors from IOKit as human readable strings =]
The USBDeviceManager will load all the devices that match the vendor and product. You will see when you plug in the switch, it will display:

y6fd2ID.png




Once I figure out how to "Transfer Control" after uploading the payload, I will post here or upload the code or something to the github.

I think we got fairly far.. just need the last step.. Btw, I tested the above on my iPhone 6S.. Same output. Still no control transfer..
 
Last edited by JustBrandonT,

Lil_SpazJoekp

Well-Known Member
Newcomer
Joined
Apr 11, 2018
Messages
89
Trophies
0
Age
27
XP
373
Country
United States
I've gotten as far as making the packet and stuff.. The problem is I can't figure out how to "transfer I/O control" still.. None of the code posted on this thread works to transfer it.. I've made the code cross-platform for iOS and MacOS.. So it can be ran on MacOS and you can see.. Once it runs on MacOS, it'll run on iOS no problem and we'd be done :D

The code I have now is:

USBDevice.hxx
Code:
//
//  USBDevice.h
//  iOUSB
//
//  Created by Brandon on 2018-05-28.
//  Copyright © 2018 XIO. All rights reserved.
//

extern "C" {
    #import <IOKit/IOKitLib.h>
    #import <IOKit/usb/IOUSBLib.h>
    #import <IOKit/IOCFPlugIn.h>
    #import <IOKit/IOMessage.h>
    #import <IOKit/IOBSD.h>
    #import <CoreFoundation/CoreFoundation.h>
}

#include <iostream>
#include <vector>

/// Allow formatting of C++ strings
template<typename... Args>
std::string string_format(const std::string& format, Args... args)
{
    size_t size = std::snprintf(nullptr, 0, format.c_str(), args...) + 1;
    std::unique_ptr<char[]> buf(new char[size]);
    std::snprintf(buf.get(), size, format.c_str(), args...);
    return std::string(buf.get(), buf.get() + size - 1);
}

/// Error Codes
extern std::string human_error_string(IOReturn errorCode);


/// Devices
class USBDevice
{
private:
    IOUSBDeviceInterface300** deviceInterface;
    IOUSBInterfaceInterface300** interface;
    io_object_t device;
   
    std::uint8_t controlPipeRef;
    std::uint8_t readPipeRef;
    std::uint8_t writePipeRef;
   
    /// Returns an arbitrary interface from a plugin interface
    void** GetInterface(io_object_t device, CFUUIDRef type, CFUUIDRef uuid);
   
    /// Get Device Property
    int GetProperty(std::string propertyName);
   
    /// Get String Descriptor
    std::string GetStringDescriptor(std::uint8_t index);
   
    /// Load Pipe Refs
    void GetPipeRefs();
   
public:
    USBDevice(io_object_t device);
    ~USBDevice();
   
    /// Properties
    std::string GetName();
    std::string GetClass();
    std::string GetPath();
    std::int32_t GetVendorID();
    std::int32_t GetProductID();
    std::string GetSerialNumber();
    std::string GetManufacturer();
    std::uint32_t GetLocationID();
   
    /// Debug Print an interface
    std::string PrintInterface();
   
    /// Get Max Packet Size for a pipe
    std::uint16_t GetMaxPipePacketSize(std::uint8_t pipeRef);
   
    /// Clear the pipe
    void ClearPipe(std::uint8_t pipeRef, std::uint32_t timeout);
   
    /// Open the Device Interface
    bool Open();
   
    /// Close the Device Interface
    void Close();
   
    /// Get Pipe Status
    kern_return_t GetPipeStatus(std::uint8_t pipeRef);
   
    /// Get Driver Version
    NumVersion GetDriverVersion();
   
    /// Get Control pipe reference
    std::uint8_t GetControlPipeRef();
   
    /// Get Read pipe reference
    std::uint8_t GetReadPipeRef();
   
    /// Get Write pipe reference
    std::uint8_t GetWritePipeRef();
   
    /// Read from the device
    int Read(std::uint8_t* buffer, std::size_t bufferSize);
   
    /// Read from the device asynchronously
    int ReadAsync(std::uint8_t* buffer, std::size_t bufferSize);
   
    /// Write to the device
    int Write(std::uint8_t* buffer, std::size_t bufferSize);
   
    /// Write to the device asynchronously
    int WriteAsync(std::uint8_t* buffer, std::size_t bufferSize);
   
    /// Send Device Request
    int SendDeviceRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout = 0);
   
    /// Send I/O Control Request
    int SendControlRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout = 0);
   
    /// Send Raw Request
    int SendRawRequest(IOUSBDevRequest *request);
   
    /// Send Raw I/O Control Request
    int SendRawControlRequest(IOUSBDevRequest *request, std::uint8_t pipeRef);
};

/// Device Manager
class USBDeviceManager
{
private:
    void* notificationRegistry;
   
public:
    USBDeviceManager();
    ~USBDeviceManager();
   
    std::vector<std::unique_ptr<USBDevice>> GetDevicesMatching(std::string service, int vendorId, int productId);
   
    void RegisterForDeviceNotifications(std::string service, int vendorId, int productId, void(*onDeviceAdded)(USBDevice *device), void(*onDeviceDisconnected)(USBDevice *device));
};

USBDevice.cxx:
Code:
//
//  USBDevice.m
//  iOUSB
//
//  Created by Brandon on 2018-05-28.
//  Copyright © 2018 XIO. All rights reserved.
//

#include "USBDevice.hxx"
#include <memory>

struct USBNotification
{
    USBDeviceManager* _this;
    IONotificationPortRef notificationPort;
    io_iterator_t deviceAddedIterator;
    io_iterator_t deviceRemovedIterator;
    io_object_t notification;
    CFRunLoopRef runLoop;
    void(*onDeviceAdded)(USBDevice *device);
    void(*onDeviceDisconnected)(USBDevice *device);
};

struct DeviceMessage
{
    USBDevice* _this;
    USBNotification* notification;
};

#pragma mark - Utilities

#define CFSAFERELEASE(ref) if (ref) CFRelease(ref)
void DeviceAdded(void *userInfo, io_iterator_t iterator);
void DeviceDisconnected(void *userInfo, io_iterator_t iterator);
void DeviceMessageReceived(void *userInfo, io_service_t service, natural_t messageType, void *messageArgument);

/// Converts IOKit errors to human readable strings.
std::string usb_human_error_string(IOReturn errorCode)
{
//    #define err_get_system(err) (((err)>>26)&0x3f)
//    #define err_get_sub(err) (((err)>>14)&0xfff)
//    #define err_get_code(err) ((err)&0x3fff)
   
    switch (errorCode) {
        case kIOUSBUnknownPipeErr:
            return string_format("Pipe Ref Not Recognized (%08x)", errorCode);
           
        case kIOUSBTooManyPipesErr:
            return string_format("Too Many Pipes (%08x)", errorCode);
           
        case kIOUSBNoAsyncPortErr:
            return string_format("No Async Port (%08x)", errorCode);
           
        case kIOUSBNotEnoughPipesErr:
            return string_format("Not Enough Pipes in Interface (%08x)", errorCode);
           
        case kIOUSBNotEnoughPowerErr:
            return string_format("Not Enough Power for Selected Configuration (%08x)", errorCode);
           
        case kIOUSBEndpointNotFound:
            return string_format("Endpoint Not Found (%08x)", errorCode);
           
        case kIOUSBConfigNotFound:
            return string_format("Configuration Not Found (%08x)", errorCode);
           
        case kIOUSBTransactionTimeout:
            return string_format("Transaction Timed Out (%08x)", errorCode);
           
        case kIOUSBTransactionReturned:
            return string_format("Transaction has been returned to the caller (%08x)", errorCode);
           
        case kIOUSBPipeStalled:
            return string_format("Pipe has stalled, Error needs to be cleared (%08x)", errorCode);
           
        case kIOUSBInterfaceNotFound:
            return string_format("Interface Ref Not Recognized (%08x)", errorCode);
           
        case kIOUSBLowLatencyBufferNotPreviouslyAllocated:
            return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the data buffer) first (%08x)", errorCode);
           
        case kIOUSBLowLatencyFrameListNotPreviouslyAllocated:
            return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the frame list) first (%08x)", errorCode);
           
        case kIOUSBHighSpeedSplitError:
            return string_format("Error to hub on high speed bus trying to do split transaction (%08x)", errorCode);
           
        case kIOUSBSyncRequestOnWLThread:
            return string_format("A synchronous USB request was made on the workloop thread (from a callback?).  Only async requests are permitted in that case (%08x)", errorCode);
           
        case kIOUSBDeviceTransferredToCompanion:
            return string_format("The device has been tranferred to another controller for enumeration (%08x)", errorCode);
           
        case kIOUSBClearPipeStallNotRecursive:
            return string_format("ClearPipeStall should not be called recursively (%08x)", errorCode);
           
        case kIOUSBDevicePortWasNotSuspended:
            return string_format("Port was not suspended (%08x)", errorCode);
           
        case kIOUSBEndpointCountExceeded:
            return string_format("The endpoint was not created because the controller cannot support more endpoints (%08x)", errorCode);
           
        case kIOUSBDeviceCountExceeded:
            return string_format("The device cannot be enumerated because the controller cannot support more devices (%08x)", errorCode);
           
        case kIOUSBStreamsNotSupported:
            return string_format("The request cannot be completed because the XHCI controller does not support streams (%08x)", errorCode);
           
        case kIOUSBInvalidSSEndpoint:
            return string_format("An endpoint found in a SuperSpeed device is invalid (usually because there is no Endpoint Companion Descriptor) (%08x)", errorCode);
           
        case kIOUSBTooManyTransactionsPending:
            return string_format("The transaction cannot be submitted because it would exceed the allowed number of pending transactions (%08x)", errorCode);
           
        default:
            return string_format("Error Code (%08x)\n-- System: (%02X), SubSystem: (%02X), Code: (%02X) ", errorCode, err_get_system(errorCode), err_get_sub(errorCode), err_get_code(errorCode));
    }
}

std::string human_error_string(IOReturn errorCode)
{
    switch (errorCode) {
        case kIOReturnSuccess:
            return string_format("Success (%08x)", errorCode);
           
        case kIOReturnError:
            return string_format("General Error (%08x)", errorCode);
           
        case kIOReturnNoMemory:
            return string_format("Cannot Allocate Memory (%08x)", errorCode);
           
        case kIOReturnNoResources:
            return string_format("Resource Shortage (%08x)", errorCode);
           
        case kIOReturnIPCError:
            return string_format("IPC Error (%08x)", errorCode);
           
        case kIOReturnNoDevice:
            return string_format("No Such Device (%08x)", errorCode);
           
        case kIOReturnNotPrivileged:
            return string_format("Insufficient Privileges (%08x)", errorCode);
           
        case kIOReturnBadArgument:
            return string_format("Invalid Argument (%08x)", errorCode);
           
        case kIOReturnLockedRead:
            return string_format("Device Read Locked (%08x)", errorCode);
           
        case kIOReturnLockedWrite:
            return string_format("Device Write Locked (%08x)", errorCode);
           
        case kIOReturnExclusiveAccess:
            return string_format("Exclusive Access and Device already opened (%08x)", errorCode);
           
        case kIOReturnBadMessageID:
            return string_format("Sent/Received Messages had different MSG_ID (%08x)", errorCode);
           
        case kIOReturnUnsupported:
            return string_format("Unsupported Function (%08x)", errorCode);
           
        case kIOReturnVMError:
            return string_format("Misc. VM Failure (%08x)", errorCode);
           
        case kIOReturnInternalError:
            return string_format("Internal Error (%08x)", errorCode);
           
        case kIOReturnIOError:
            return string_format("General I/O Error (%08x)", errorCode);
           
        case kIOReturnCannotLock:
            return string_format("Can't Acquire Lock (%08x)", errorCode);
           
        case kIOReturnNotOpen:
            return string_format("Device Not Open (%08x)", errorCode);
           
        case kIOReturnNotReadable:
            return string_format("Read Not Supported (%08x)", errorCode);
           
        case kIOReturnNotWritable:
            return string_format("Write Not Supported (%08x)", errorCode);
           
        case kIOReturnNotAligned:
            return string_format("Alignment Error (%08x)", errorCode);
           
        case kIOReturnBadMedia:
            return string_format("Media Error (%08x)", errorCode);
           
        case kIOReturnStillOpen:
            return string_format("Device(s) Still Open (%08x)", errorCode);
           
        case kIOReturnRLDError:
            return string_format("RLD Failure (%08x)", errorCode);
           
        case kIOReturnDMAError:
            return string_format("DMA Failure (%08x)", errorCode);
           
        case kIOReturnBusy:
            return string_format("Device Busy (%08x)", errorCode);
           
        case kIOReturnTimeout:
            return string_format("I/O Timeout (%08x)", errorCode);
           
        case kIOReturnOffline:
            return string_format("Device Offline (%08x)", errorCode);
           
        case kIOReturnNotReady:
            return string_format("Not Ready (%08x)", errorCode);
           
        case kIOReturnNotAttached:
            return string_format("Device Not Attached (%08x)", errorCode);
           
        case kIOReturnNoChannels:
            return string_format("No DMA Channels Left (%08x)", errorCode);
           
        case kIOReturnNoSpace:
            return string_format("No Space For Data (%08x)", errorCode);
           
        case kIOReturnPortExists:
            return string_format("Port Already Exists (%08x)", errorCode);
           
        case kIOReturnCannotWire:
            return string_format("Can't Write Down (%08x)", errorCode);
           
        case kIOReturnNoInterrupt:
            return string_format("No Interrupt Attached (%08x)", errorCode);
           
        case kIOReturnNoFrames:
            return string_format("No DMA Frames Enqueued (%08x)", errorCode);
           
        case kIOReturnMessageTooLarge:
            return string_format("Oversized MSG Received On Interrupt Port (%08x)", errorCode);
           
        case kIOReturnNotPermitted:
            return string_format("Not Permitted (%08x)", errorCode);
           
        case kIOReturnNoPower:
            return string_format("No Power To Device (%08x)", errorCode);
           
        case kIOReturnNoMedia:
            return string_format("Media Not Present (%08x)", errorCode);
           
        case kIOReturnUnformattedMedia:
            return string_format("Media Not Formatted (%08x)", errorCode);
           
        case kIOReturnUnsupportedMode:
            return string_format("No Such Mode (%08x)", errorCode);
           
        case kIOReturnUnderrun:
            return string_format("Buffer Underflow (%08x)", errorCode);
           
        case kIOReturnOverrun:
            return string_format("Buffer Overflow (%08x)", errorCode);
           
        case kIOReturnDeviceError:
            return string_format("The Device Is Not Working Properly! (%08x)", errorCode);
           
        case kIOReturnNoCompletion:
            return string_format("A Completion Routine Is Required (%08x)", errorCode);
           
        case kIOReturnAborted:
            return string_format("Operation Aborted (%08x)", errorCode);
           
        case kIOReturnNoBandwidth:
            return string_format("Bus Bandwidth Would Be Exceeded (%08x)", errorCode);
           
        case kIOReturnNotResponding:
            return string_format("Device Not Responding (%08x)", errorCode);
           
        case kIOReturnIsoTooOld:
            return string_format("ISOChronous I/O request for distance past! (%08x)", errorCode);
           
        case kIOReturnIsoTooNew:
            return string_format("ISOChronous I/O request for distant future! (%08x)", errorCode);
           
        case kIOReturnNotFound:
            return string_format("Data Not Found (%08x)", errorCode);
           
        case kIOReturnInvalid:
            return string_format("Should Never Be Seen(%08x)", errorCode);
           
        default:
            /// See here for more: https://developer.apple.com/library/content/qa/qa1075/_index.html
            return usb_human_error_string(errorCode);
    }
}


#pragma mark - Device Private
USBDevice::USBDevice(io_object_t device) : deviceInterface(nullptr), interface(nullptr), device(device), controlPipeRef(0x00), readPipeRef(0x01), writePipeRef(0x02)
{
    IOObjectRetain(device);
}

USBDevice::~USBDevice()
{
    Close();
    IOObjectRelease(device);
}

void** USBDevice::GetInterface(io_object_t device, CFUUIDRef type, CFUUIDRef uuid)
{
    /// Create a plugin interface for the service.
    IOCFPlugInInterface **plugInInterface = nullptr;
    SInt32 score = 0;
   
    kern_return_t kr = IOCreatePlugInInterfaceForService(device, type, kIOCFPlugInInterfaceID, &plugInInterface, &score);
   
    if ((kIOReturnSuccess != kr) || !plugInInterface)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return nullptr;
    }
   
    /// Get an interface from the plugin.. and release the plugin
    void **interface = nullptr;
    HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(uuid), (LPVOID*) &interface);
    (*plugInInterface)->Release(plugInInterface);
   
    if (res || !interface)
    {
        return nullptr;
    }
    return interface;
}

int USBDevice::GetProperty(std::string propertyName)
{
    CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, propertyName.c_str(), kCFStringEncodingUTF8);
    CFNumberRef number = (CFNumberRef)IORegistryEntryCreateCFProperty(device, name, kCFAllocatorDefault, 0);
   
    if (number)
    {
        int value = 0;
        CFNumberGetValue(number, kCFNumberSInt32Type, &value);
        CFSAFERELEASE(number);
        CFSAFERELEASE(name);
        return value;
    }
   
    CFSAFERELEASE(name);
    return -1;
}

std::string USBDevice::GetStringDescriptor(std::uint8_t index)
{
    std::uint8_t requestBuffer[256];
    IOUSBDevRequest request = {
        .bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice),
        .bRequest = kUSBRqGetDescriptor,
        .wValue = static_cast<std::uint16_t>((kUSBStringDesc << 8) | index),
        .wIndex = 0x409, //English
        .wLength = sizeof(requestBuffer),
        .pData = requestBuffer
    };
   
    kern_return_t kr = (*deviceInterface)->DeviceRequest(deviceInterface, &request);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return "";
    }
   
    CFStringRef descriptor = CFStringCreateWithBytes(kCFAllocatorDefault, &requestBuffer[2], requestBuffer[0] - 2, kCFStringEncodingUTF16LE, false);
   
    CFIndex length = CFStringGetLength(descriptor);
    CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
    std::string result(maxSize, '\0');
    CFStringGetCString(descriptor, &result[0], maxSize, kCFStringEncodingUTF8);
    CFSAFERELEASE(descriptor);
    return result;
}

void USBDevice::GetPipeRefs()
{
    controlPipeRef = 0x00;
    readPipeRef = 0x01;
    writePipeRef = 0x02;
   
    if (interface)
    {
        UInt8 interfaceNumEndpoints = 0;
        IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
        if (err)
        {
            std::cerr<<human_error_string(err)<<"\n";
            return;
        }
       
        for (UInt8 ref = 1; ref <= interfaceNumEndpoints; ++ref)
        {
            UInt8 direction;
            UInt8 number;
            UInt8 transferType;
            UInt16 maxPacketSize;
            UInt8 interval;
           
            IOReturn kr = (*interface)->GetPipeProperties(interface, ref, &direction, &number, &transferType, &maxPacketSize, &interval);
           
            if (kr == kIOReturnSuccess)
            {
                if (transferType == kUSBControl)
                {
                    controlPipeRef = 0;
                }
                else if (transferType == kUSBBulk)
                {
                    if (direction == kUSBIn)
                    {
                        readPipeRef = ref;
                    }
                    else if (direction == kUSBOut)
                    {
                        writePipeRef = ref;
                    }
                }
            }
        }
    }
}

std::string USBDevice::GetName()
{
    io_string_t name = {0};
    kern_return_t kr = IORegistryEntryGetName(device, name);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
    }
    return std::string(name);
}

std::string USBDevice::GetClass()
{
    io_string_t name = {0};
    kern_return_t kr = IOObjectGetClass(device, name);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
    }
   
    return std::string(name);
}

std::string USBDevice::GetPath()
{
    io_string_t path = {0};
    kern_return_t kr = IORegistryEntryGetPath(device, kIOServicePlane, path);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
    }
   
    return std::string(path);
}

std::int32_t USBDevice::GetVendorID()
{
    return GetProperty(kUSBVendorID);
}

std::int32_t USBDevice::GetProductID()
{
    return GetProperty(kUSBProductID);
}

std::string USBDevice::GetSerialNumber()
{
    if (deviceInterface)
    {
        std::uint8_t index = 0;
        IOReturn kr = (*deviceInterface)->USBGetSerialNumberStringIndex(deviceInterface, &index);
        if (kr != kIOReturnSuccess)
        {
            std::cerr<<human_error_string(kr)<<"\n";
            return "";
        }
       
        return GetStringDescriptor(index);
    }
    return "";
}

std::string USBDevice::GetManufacturer()
{
    if (deviceInterface)
    {
        std::uint8_t index = 0;
        IOReturn kr = (*deviceInterface)->USBGetManufacturerStringIndex(deviceInterface, &index);
        if (kr != kIOReturnSuccess)
        {
            std::cerr<<human_error_string(kr)<<"\n";
            return "";
        }
       
        return GetStringDescriptor(index);
    }
    return "";
}

std::uint32_t USBDevice::GetLocationID()
{
    UInt32 locationID = -1;
    IOUSBDeviceInterface **deviceInterface = reinterpret_cast<IOUSBDeviceInterface **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID));
    if (deviceInterface)
    {
        // Get the location ID from the USB Interface
        (*deviceInterface)->GetLocationID(deviceInterface, &locationID);
       
        // Cleanup
        (*deviceInterface)->Release(deviceInterface);
        deviceInterface = nil;
    }
   
    return locationID;
}

std::string USBDevice::PrintInterface()
{
    UInt8 interfaceNumEndpoints = 0;
    IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
    if (err)
    {
        std::cerr<<human_error_string(err)<<"\n";
        return string_format("Unable to get number of endpoints (%08x)", err);
    }
   
    std::string output = string_format("Interface has %d endpoints\n", interfaceNumEndpoints);
   
    /// USUALLY..
    /// Index 0 = DeviceControl/ControlRequest.
    /// Index 1 = DeviceRead
    /// Index 2 = DeviceWrite
    for (UInt8 pipeRef = 1; pipeRef <= interfaceNumEndpoints; ++pipeRef)
    {
        UInt8 direction;
        UInt8 number;
        UInt8 transferType;
        UInt16 maxPacketSize;
        UInt8 interval;
       
        IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval);
       
        if (kr != kIOReturnSuccess)
        {
            output += string_format("Unable to get properties of pipe %d (%08x)\n", pipeRef, kr);
        }
        else
        {
            std::string directions[] = {"Out", "In", "None", "Any"};
            std::string transferTypes[] = {"Control", "ISOC", "Bulk", "Interrupt", "Any"};
           
            output += string_format("Pipe: %d, Direction: %s, TransferType: %s, MaxPacketSize: %d\n", pipeRef, directions[direction].c_str(), transferTypes[transferType].c_str(), maxPacketSize);
        }
    }
   
    return output;
}

std::uint16_t USBDevice::GetMaxPipePacketSize(std::uint8_t pipeRef)
{
    UInt8 direction;
    UInt8 number;
    UInt8 transferType;
    UInt16 maxPacketSize;
    UInt8 interval;
   
    IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval);
    if (kr != kIOReturnSuccess)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return 0;
    }
   
    return maxPacketSize;
}

void USBDevice::ClearPipe(std::uint8_t pipeRef, std::uint32_t timeout)
{
    uint16_t maxSize = GetMaxPipePacketSize(pipeRef);
    std::unique_ptr<char[]> readBuffer(new char[maxSize]);

    while(true)
    {
        std::uint32_t readSize = maxSize;
        bzero(readBuffer.get(), maxSize * sizeof(char));
        IOReturn kr = (*interface)->ReadPipeTO(interface, pipeRef, readBuffer.get(), (UInt32 *)&readSize, timeout, timeout);
        if (kr != kIOReturnSuccess)
        {
            std::cerr<<human_error_string(kr)<<"\n";
            break;
        }

        if (readSize == 0)
        {
            break;
        }
    }
   
    IOReturn kr = (*interface)->ClearPipeStallBothEnds(interface, pipeRef);
    if (kr)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return;
    }
}

bool USBDevice::Open()
{
    // Open the USB device for communication.
    deviceInterface = reinterpret_cast<IOUSBDeviceInterface300 **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID300));
   
    if (deviceInterface)
    {
        kern_return_t kr = (*deviceInterface)->USBDeviceOpen(deviceInterface);
        if (kr != kIOReturnSuccess)
        {
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
           
            std::cerr<<human_error_string(kr)<<"\n";
            return false;
        }
       

        //Get the configuration..
        IOUSBConfigurationDescriptorPtr config;
        kr = (*deviceInterface)->GetConfigurationDescriptorPtr(deviceInterface, 0, &config);
        if (kr != kIOReturnSuccess)
        {
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
           
            std::cerr<<human_error_string(kr)<<"\n";
            return false;
        }
       
        //Set the configuration..
        (*deviceInterface)->SetConfiguration(deviceInterface, config->bConfigurationValue);
       
        //Find the USB interface..
        IOUSBFindInterfaceRequest interfaceRequest;
        interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
       
        //Get an interface iterator..
        io_iterator_t iterator;
        kr = (*deviceInterface)->CreateInterfaceIterator(deviceInterface, &interfaceRequest, &iterator);
        if (kr != kIOReturnSuccess)
        {
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
           
            std::cerr<<human_error_string(kr)<<"\n";
            return false;
        }
       
        //Get the device object
        io_object_t usbDevice = IOIteratorNext(iterator);
        if (!usbDevice)
        {
            IOObjectRelease(iterator);
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
            return false;
        }
       
        //Get a USB Interface
        interface = reinterpret_cast<IOUSBInterfaceInterface300 **>(GetInterface(usbDevice, kIOUSBInterfaceUserClientTypeID, kIOUSBInterfaceInterfaceID300));
       
        if (!interface)
        {
            IOObjectRelease(usbDevice);
            IOObjectRelease(iterator);
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
            return false;
        }


        kr = (*interface)->USBInterfaceOpen(interface);
        if (kr != kIOReturnSuccess)
        {
            (*interface)->Release(interface);
            interface = nullptr;
           
            IOObjectRelease(usbDevice);
            IOObjectRelease(iterator);
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
            return false;
        }
       
        //Load the pipe references
        GetPipeRefs();
       
        //Create Async Notifications
        //TODO: Release this event..
        CFRunLoopSourceRef asyncEventSource;
        kr = (*interface)->CreateInterfaceAsyncEventSource(interface, &asyncEventSource);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), asyncEventSource, kCFRunLoopDefaultMode);
       
        IOObjectRelease(usbDevice);
        IOObjectRelease(iterator);
        return true;
    }
   
    return false;
}

void USBDevice::Close()
{
    if (interface)
    {
        (*interface)->USBInterfaceClose(interface);
        (*interface)->Release(interface);
        interface = nullptr;
    }
   
    if (deviceInterface)
    {
        (*deviceInterface)->USBDeviceClose(deviceInterface);
        (*deviceInterface)->Release(deviceInterface);
        deviceInterface = nullptr;
    }
}

kern_return_t USBDevice::GetPipeStatus(std::uint8_t pipeRef)
{
    if (interface)
    {
        return (*interface)->GetPipeStatus(interface, pipeRef);
    }
   
    return kIOReturnNotOpen;
}

NumVersion USBDevice::GetDriverVersion()
{
    if (interface)
    {
        NumVersion ioUSBLibVersion;
        NumVersion usbFamilyVersion;
        (*interface)->GetIOUSBLibVersion(interface, &ioUSBLibVersion, &usbFamilyVersion);
        return ioUSBLibVersion;
    }
   
    return NumVersion{0};
}

std::uint8_t USBDevice::GetControlPipeRef()
{
    return controlPipeRef;
}

std::uint8_t USBDevice::GetReadPipeRef()
{
    return readPipeRef;
}

std::uint8_t USBDevice::GetWritePipeRef()
{
    return writePipeRef;
}

void onReadAsync(void *refcon, IOReturn result, void *arg0)
{
   
}

void onWriteAsync(void *refcon, IOReturn result, void *arg0)
{
   
}

int USBDevice::Read(std::uint8_t* buffer, std::size_t bufferSize)
{
    if (interface)
    {
        UInt8 pipeRef = readPipeRef;
        UInt32 size = static_cast<UInt32>(bufferSize);
        kern_return_t res = (*interface)->ReadPipe(interface, pipeRef, buffer, &size);
        return res != kIOReturnSuccess ? -1 : static_cast<int>(size);
    }
    return -1;
}

int USBDevice::ReadAsync(std::uint8_t* buffer, std::size_t bufferSize)
{
    if (interface)
    {
        UInt8 pipeRef = readPipeRef;
        UInt32 size = static_cast<UInt32>(bufferSize);
        kern_return_t res = (*interface)->ReadPipeAsync(interface, pipeRef, buffer, size, onReadAsync, this);
        return res != kIOReturnSuccess ? -1 : static_cast<int>(size);
    }
    return -1;
}

int USBDevice::Write(uint8_t* buffer, size_t bufferSize)
{
    UInt8 pipeRef = writePipeRef;
    int packetSize = GetMaxPipePacketSize(pipeRef);
   
    size_t bytesWritten = 0;
    int bytesRemaining = static_cast<int>(bufferSize);
   
    auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) {
        kern_return_t res = (*interface)->WritePipe(interface, pipeRef, buffer, static_cast<int>(bufferSize));
        return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize);
    };
   
    while (bytesRemaining > 0)
    {
        size_t bytesToWrite = std::min(bytesRemaining, packetSize);
        auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite);
        if (retVal < 0)
        {
            return retVal;
        }
        else if (retVal < static_cast<int>(bytesToWrite))
        {
            return static_cast<int>(bytesWritten) + retVal;
        }
       
        bytesWritten += retVal;
        bytesRemaining -= retVal;
    }
   
    return static_cast<int>(bytesWritten);
}

int USBDevice::WriteAsync(std::uint8_t* buffer, std::size_t bufferSize)
{
    UInt8 pipeRef = writePipeRef;
    int packetSize = GetMaxPipePacketSize(pipeRef);
   
    size_t bytesWritten = 0;
    int bytesRemaining = static_cast<int>(bufferSize);
   
    auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) {
        kern_return_t res = (*interface)->WritePipeAsync(interface, pipeRef, buffer, static_cast<int>(bufferSize), onWriteAsync, this);
        return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize);
    };
   
    while (bytesRemaining > 0)
    {
        size_t bytesToWrite = std::min(bytesRemaining, packetSize);
        auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite);
        if (retVal < 0)
        {
            return retVal;
        }
        else if (retVal < static_cast<int>(bytesToWrite))
        {
            return static_cast<int>(bytesWritten) + retVal;
        }
       
        bytesWritten += retVal;
        bytesRemaining -= retVal;
    }
   
    return static_cast<int>(bytesWritten);
}

int USBDevice::SendDeviceRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout)
{
    if (!deviceInterface)
    {
        return -1;
    }
   
    //If there's no timeout.. just send a regular request..
    if (timeout <= 0)
    {
        IOUSBDevRequest req;
        req.bmRequestType = requesttype;
        req.bRequest = request;
        req.wValue = value;
        req.wIndex = index;
        req.wLength = size;
        req.pData = bytes;
       
        kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, &req);
        return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
    }
   
    //Send Timeout request..
    IOUSBDevRequestTO req;
    req.bmRequestType = requesttype;
    req.bRequest = request;
    req.wValue = value;
    req.wIndex = index;
    req.wLength = size;
    req.pData = bytes;
    req.completionTimeout = timeout;
   
    kern_return_t result = (*deviceInterface)->DeviceRequestTO(deviceInterface, &req);
    return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}

int USBDevice::SendControlRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout)
{
    if (!interface)
    {
        return -1;
    }
   
    //If there's no timeout.. just send a regular request..
    if (timeout <= 0)
    {
        IOUSBDevRequest req;
        req.bmRequestType = requesttype;
        req.bRequest = request;
        req.wValue = value;
        req.wIndex = index;
        req.wLength = size;
        req.pData = bytes;
       
        kern_return_t result = (*interface)->ControlRequest(interface, controlPipeRef, &req);
        return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
    }
   
    //Send Timeout request..
    IOUSBDevRequestTO req;
    req.bmRequestType = requesttype;
    req.bRequest = request;
    req.wValue = value;
    req.wIndex = index;
    req.wLength = size;
    req.pData = bytes;
    req.completionTimeout = timeout;
   
    kern_return_t result = (*interface)->ControlRequestTO(interface, controlPipeRef, &req);
    return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}

int USBDevice::SendRawRequest(IOUSBDevRequest *request)
{
    if (!deviceInterface)
    {
        return -1;
    }
    kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, request);
    return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred..
}

int USBDevice::SendRawControlRequest(IOUSBDevRequest *request, std::uint8_t pipeRef)
{
    if (!interface)
    {
        return -1;
    }
    kern_return_t result = (*interface)->ControlRequest(interface, pipeRef, request);
    return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred..
}

#pragma mark - USB Public

USBDeviceManager::USBDeviceManager() : notificationRegistry(nullptr)
{
}

USBDeviceManager::~USBDeviceManager()
{
    if (this->notificationRegistry)
    {
        USBNotification *notification = static_cast<USBNotification *>(this->notificationRegistry);
        delete notification;
    }
}

std::vector<std::unique_ptr<USBDevice>> USBDeviceManager::GetDevicesMatching(std::string service, int vendorId, int productId)
{
    io_iterator_t iter = 0;
   
    /// Find Matching Services
    CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str());
    if (matchingDict == nil)
    {
        return std::vector<std::unique_ptr<USBDevice>> {};
    }
   
    /// Added VendorID and ProductID to query
    CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef);
    CFRelease(numberRef);
   
    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef);
    CFRelease(numberRef);
    numberRef = nullptr;
   
    /// Find devices matching the above criteria
    kern_return_t kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return std::vector<std::unique_ptr<USBDevice>> {};
    }
   
    /// Iterate over all devices
    io_service_t device = 0;
    std::vector<std::unique_ptr<USBDevice>> devices;
    while ((device = IOIteratorNext(iter)))
    {
        devices.push_back(std::unique_ptr<USBDevice>(new USBDevice(device)));
    }
   
    IOObjectRelease(iter);
    return devices;
}

void USBDeviceManager::RegisterForDeviceNotifications(std::string service, int vendorId, int productId, void(*onDeviceAdded)(USBDevice *device), void(*onDeviceDisconnected)(USBDevice *device))
{
    /// Find Matching Services
    CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str());
    if (matchingDict == nil)
    {
        return;
    }
   
    /// Added VendorID and ProductID to query
    CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef);
    CFRelease(numberRef);
   
    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef);
    CFRelease(numberRef);
    numberRef = nullptr;
   
    // Setup notifications for when the device is available
    USBNotification *notificationInfo = new USBNotification();
    notificationInfo->_this = this;
    notificationInfo->onDeviceAdded = onDeviceAdded;
    notificationInfo->onDeviceDisconnected = onDeviceDisconnected;
    notificationInfo->notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
    notificationInfo->runLoop = CFRunLoopGetCurrent();
    notificationInfo->deviceAddedIterator = 0;
    notificationInfo->deviceRemovedIterator = 0;
    this->notificationRegistry = notificationInfo;
   
    CFRunLoopSourceRef runLoopSource = IONotificationPortGetRunLoopSource(notificationInfo->notificationPort);
    CFRunLoopAddSource(notificationInfo->runLoop, runLoopSource, kCFRunLoopDefaultMode);
   
    // Now set up a notification to be called when a device is matched and detached by I/O Kit.
    kern_return_t rc;
//    rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort,
//                                          kIOTerminatedNotification,
//                                          matchingDict,
//                                          DeviceDisconnected,
//                                          notificationInfo, //userInfo
//                                          &notificationInfo->deviceRemovedIterator);
//  
//    if (rc != kIOReturnSuccess)
//    {
//        IONotificationPortDestroy(notificationInfo->notificationPort);
//        delete notificationInfo;
//        std::cerr<<human_error_string(rc)<<"\n";
//        return;
//    }
   
    // Now set up a notification to be called when a device is matched and attached by I/O Kit.
    rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort,
                                          kIOMatchedNotification,
                                          matchingDict,
                                          DeviceAdded,
                                          notificationInfo, //userInfo
                                          &notificationInfo->deviceAddedIterator);
   
    if (rc != kIOReturnSuccess)
    {
        IONotificationPortDestroy(notificationInfo->notificationPort);
        delete notificationInfo;
        std::cerr<<human_error_string(rc)<<"\n";
        return;
    }
   
//    DeviceDisconnected(notificationInfo, notificationInfo->deviceRemovedIterator);
    DeviceAdded(notificationInfo, notificationInfo->deviceAddedIterator);
}

// When a device has been plugged into the phone's serial port, this function will get called
void DeviceAdded(void *userInfo, io_iterator_t iterator)
{
    kern_return_t kr;
    io_service_t usbDevice;
   
    USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo);
   
    // Iterate over all devices..
    while ((usbDevice = IOIteratorNext(iterator))) {
       
        DeviceMessage* notificationMessage = new DeviceMessage();
        notificationMessage->_this = new USBDevice(usbDevice);
        notificationMessage->notification = notificationInfo;
       
        if (notificationInfo->onDeviceAdded)
        {
            notificationInfo->onDeviceAdded(notificationMessage->_this);
        }
       
        // Register for Device Notifications.. IE: Disconnected Notification..
        kr = IOServiceAddInterestNotification(notificationInfo->notificationPort,
                                              usbDevice,
                                              kIOGeneralInterest,
                                              DeviceMessageReceived,
                                              notificationMessage, //userInfo
                                              &notificationInfo->notification);
        // Cleanup
        kr = IOObjectRelease(usbDevice);
    }
}

// When a device has been unplugged from the phone's serial port, this function will get called.
void DeviceDisconnected(void *userInfo, io_iterator_t iterator)
{
    USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo);
    if (notificationInfo->onDeviceDisconnected)
    {
        notificationInfo->onDeviceDisconnected(nullptr);
    }
   
    // Cleanup
    IOObjectRelease(notificationInfo->notification);
    delete notificationInfo;
}

// When a device message has been received..
void DeviceMessageReceived(void* userInfo, io_service_t service, uint32_t messageType, void* messageArgument)
{
    DeviceMessage *message = static_cast<DeviceMessage *>(userInfo);
   
    // If it's the disconnect message, we need to cleanup..
    if (messageType == kIOMessageServiceIsTerminated)
    {
        if (message->notification && message->notification->onDeviceDisconnected)
        {
            message->notification->onDeviceDisconnected(message->_this);
        }
       
        // Cleanup
//        IOObjectRelease(message->notification->notificationPort);
        delete message;
    }
}


Then you use it for Nintendo Switch like:

main.mm:
Code:
//
//  main.m
//  USBLoader
//
//  Created by Brandon on 2018-05-30.
//  Copyright © 2018 XIO. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/usb/IOUSBLib.h>
#import <IOKit/IOCFPlugIn.h>
#import <IOKit/IOMessage.h>
#import <IOKit/IOBSD.h>
#include <vector>
#include <iostream>
#include "USBDevice.hxx"

//Include some headers that read the fusee.bin and intermezzo and create the Fusee Gelee Payload..
#include "Fusee.h"

// Helper file to try to smash the stack..
#include "Smash.hpp"

//Nintendo Constants
#define kNintendoSwitchVendorID 0x0955
#define kNintendoSwitchProductID 0x7321

#pragma mark - Utilities

void displayStrings(std::vector<std::string> strings)
{
    for (std::string& str : strings)
    {
        std::cout<<str<<"\n";
    }
}

void displayStrings(NSArray<NSString *> *strings)
{
    for (NSString *str in strings)
    {
        NSLog(@"%@", str);
    }
}

void onDeviceAdded(USBDevice *device)
{
    if (!device->Open())
    {
        std::cerr<<"Cannot Open Device\n";
        return;
    }
   
    std::string manufacturer = device->GetManufacturer();
    std::string name = device->GetName();
    std::string clazz = device->GetClass();
    std::string path = device->GetPath();
    std::int32_t vendorId = device->GetVendorID();
    std::int32_t productId = device->GetProductID();
    std::uint32_t locationId = device->GetLocationID();
   
    displayStrings({
        "       Device Information",
        "-----------------------------------",
        string_format("Manufacturer: %s", manufacturer.c_str()),
        string_format("Name: %s", name.c_str()),
        string_format("Class: %s", clazz.c_str()),
        string_format("Path: %s", path.c_str()),
        string_format("VendorId: 0x%04X", vendorId),
        string_format("ProductId: 0x%04X", productId),
        string_format("LocationId: 0x%04X", locationId)
    });
   
    device->ClearPipe(device->GetReadPipeRef(), 10);
    device->ClearPipe(device->GetWritePipeRef(), 10);
   
    uint8_t deviceId[0x10] = {0};
    if (device->Read(deviceId, sizeof(deviceId)) != sizeof(deviceId))
    {
        displayStrings({"Cannot Read Device-ID"});
        return;
    }
   
    std::cout<<"\n\n";
    std::string deviceSerialNumber = string_format("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
                                                   (uint32_t)deviceId[0],
                                                   (uint32_t)deviceId[1],
                                                   (uint32_t)deviceId[2],
                                                   (uint32_t)deviceId[3],
                                                   (uint32_t)deviceId[4],
                                                   (uint32_t)deviceId[5],
                                                   (uint32_t)deviceId[6],
                                                   (uint32_t)deviceId[7],
                                                   (uint32_t)deviceId[8],
                                                   (uint32_t)deviceId[9],
                                                   (uint32_t)deviceId[10],
                                                   (uint32_t)deviceId[11],
                                                   (uint32_t)deviceId[12],
                                                   (uint32_t)deviceId[13],
                                                   (uint32_t)deviceId[14],
                                                   (uint32_t)deviceId[15]);
   
    displayStrings({
        string_format("Device Id: %s\n\n", deviceSerialNumber.c_str()),
        "Loading Payload"
    });
   
    auto payload = createRCMPayload(intermezzo, fusee);
   
    displayStrings({"Uploading Payload"});
    smash(device, payload);
   
    device->Close();
    return;
}

void onDeviceDisconnected(USBDevice *device)
{
    std::cout<<"Device Disconnected\n";
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        USBDeviceManager device;
        device.RegisterForDeviceNotifications("IOUSBHostDevice",
                                              kNintendoSwitchVendorID,
                                              kNintendoSwitchProductID,
                                              onDeviceAdded,
                                              onDeviceDisconnected);
        CFRunLoopRun();
    }
    return 0;
}


It uploads the payload successfully.. I just can't get it to freaking smash the damn stack.. grr.. Also, once you "read" the deviceId, the control of the device is switched so you CANNOT run the code again without restarting the device.. I tried libusb and it behaves the same way so it means the code above is working as it should.. I just have to figure out how to smash the stack and that's it.
Would this help?
urrea-sledge-hammers-1440gfv-64_1000.jpg
 
  • Like
Reactions: JustBrandonT

Lil_SpazJoekp

Well-Known Member
Newcomer
Joined
Apr 11, 2018
Messages
89
Trophies
0
Age
27
XP
373
Country
United States
I've gotten as far as making the packet and stuff.. The problem is I can't figure out how to "transfer I/O control" still.. None of the code posted on this thread works to transfer it.. I've made the code cross-platform for iOS and MacOS.. So it can be ran on MacOS and you can see.. Once it runs on MacOS, it'll run on iOS no problem and we'd be done :D

The code I have now is (entirely C++ for a reason):

USBDevice.hxx
Code:
//
//  USBDevice.h
//  iOUSB
//
//  Created by Brandon on 2018-05-28.
//  Copyright © 2018 XIO. All rights reserved.
//

extern "C" {
    #import <IOKit/IOKitLib.h>
    #import <IOKit/usb/IOUSBLib.h>
    #import <IOKit/IOCFPlugIn.h>
    #import <IOKit/IOMessage.h>
    #import <IOKit/IOBSD.h>
    #import <CoreFoundation/CoreFoundation.h>
}

#include <iostream>
#include <vector>

/// Allow formatting of C++ strings
template<typename... Args>
std::string string_format(const std::string& format, Args... args)
{
    size_t size = std::snprintf(nullptr, 0, format.c_str(), args...) + 1;
    std::unique_ptr<char[]> buf(new char[size]);
    std::snprintf(buf.get(), size, format.c_str(), args...);
    return std::string(buf.get(), buf.get() + size - 1);
}

/// Error Codes
extern std::string human_error_string(IOReturn errorCode);


/// Devices
class USBDevice
{
private:
    IOUSBDeviceInterface300** deviceInterface;
    IOUSBInterfaceInterface300** interface;
    io_object_t device;
 
    std::uint8_t controlPipeRef;
    std::uint8_t readPipeRef;
    std::uint8_t writePipeRef;
 
    /// Returns an arbitrary interface from a plugin interface
    void** GetInterface(io_object_t device, CFUUIDRef type, CFUUIDRef uuid);
 
    /// Get Device Property
    int GetProperty(std::string propertyName);
 
    /// Get String Descriptor
    std::string GetStringDescriptor(std::uint8_t index);
 
    /// Load Pipe Refs
    void GetPipeRefs();
 
public:
    USBDevice(io_object_t device);
    ~USBDevice();
 
    /// Properties
    std::string GetName();
    std::string GetClass();
    std::string GetPath();
    std::int32_t GetVendorID();
    std::int32_t GetProductID();
    std::string GetSerialNumber();
    std::string GetManufacturer();
    std::uint32_t GetLocationID();
 
    /// Debug Print an interface
    std::string PrintInterface();
 
    /// Get Max Packet Size for a pipe
    std::uint16_t GetMaxPipePacketSize(std::uint8_t pipeRef);
 
    /// Clear the pipe
    void ClearPipe(std::uint8_t pipeRef, std::uint32_t timeout);
 
    /// Open the Device Interface
    bool Open();
 
    /// Close the Device Interface
    void Close();
 
    /// Get Pipe Status
    kern_return_t GetPipeStatus(std::uint8_t pipeRef);
 
    /// Get Driver Version
    NumVersion GetDriverVersion();
 
    /// Get Control pipe reference
    std::uint8_t GetControlPipeRef();
 
    /// Get Read pipe reference
    std::uint8_t GetReadPipeRef();
 
    /// Get Write pipe reference
    std::uint8_t GetWritePipeRef();
 
    /// Read from the device
    int Read(std::uint8_t* buffer, std::size_t bufferSize);
 
    /// Read from the device asynchronously
    int ReadAsync(std::uint8_t* buffer, std::size_t bufferSize);
 
    /// Write to the device
    int Write(std::uint8_t* buffer, std::size_t bufferSize);
 
    /// Write to the device asynchronously
    int WriteAsync(std::uint8_t* buffer, std::size_t bufferSize);
 
    /// Send Device Request
    int SendDeviceRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout = 0);
 
    /// Send I/O Control Request
    int SendControlRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout = 0);
 
    /// Send Raw Request
    int SendRawRequest(IOUSBDevRequest *request);
 
    /// Send Raw I/O Control Request
    int SendRawControlRequest(IOUSBDevRequest *request, std::uint8_t pipeRef);
};

/// Device Manager
class USBDeviceManager
{
private:
    void* notificationRegistry;
 
public:
    USBDeviceManager();
    ~USBDeviceManager();
 
    std::vector<std::unique_ptr<USBDevice>> GetDevicesMatching(std::string service, int vendorId, int productId);
 
    void RegisterForDeviceNotifications(std::string service, int vendorId, int productId, void(*onDeviceAdded)(USBDevice *device), void(*onDeviceDisconnected)(USBDevice *device));
};

USBDevice.cxx:
Code:
//
//  USBDevice.m
//  iOUSB
//
//  Created by Brandon on 2018-05-28.
//  Copyright © 2018 XIO. All rights reserved.
//

#include "USBDevice.hxx"
#include <memory>

struct USBNotification
{
    USBDeviceManager* _this;
    IONotificationPortRef notificationPort;
    io_iterator_t deviceAddedIterator;
    io_iterator_t deviceRemovedIterator;
    io_object_t notification;
    CFRunLoopRef runLoop;
    void(*onDeviceAdded)(USBDevice *device);
    void(*onDeviceDisconnected)(USBDevice *device);
};

struct DeviceMessage
{
    USBDevice* _this;
    USBNotification* notification;
};

#pragma mark - Utilities

#define CFSAFERELEASE(ref) if (ref) CFRelease(ref)
void DeviceAdded(void *userInfo, io_iterator_t iterator);
void DeviceDisconnected(void *userInfo, io_iterator_t iterator);
void DeviceMessageReceived(void *userInfo, io_service_t service, natural_t messageType, void *messageArgument);

/// Converts IOKit errors to human readable strings.
std::string usb_human_error_string(IOReturn errorCode)
{
//    #define err_get_system(err) (((err)>>26)&0x3f)
//    #define err_get_sub(err) (((err)>>14)&0xfff)
//    #define err_get_code(err) ((err)&0x3fff)
 
    switch (errorCode) {
        case kIOUSBUnknownPipeErr:
            return string_format("Pipe Ref Not Recognized (%08x)", errorCode);
     
        case kIOUSBTooManyPipesErr:
            return string_format("Too Many Pipes (%08x)", errorCode);
     
        case kIOUSBNoAsyncPortErr:
            return string_format("No Async Port (%08x)", errorCode);
     
        case kIOUSBNotEnoughPipesErr:
            return string_format("Not Enough Pipes in Interface (%08x)", errorCode);
     
        case kIOUSBNotEnoughPowerErr:
            return string_format("Not Enough Power for Selected Configuration (%08x)", errorCode);
     
        case kIOUSBEndpointNotFound:
            return string_format("Endpoint Not Found (%08x)", errorCode);
     
        case kIOUSBConfigNotFound:
            return string_format("Configuration Not Found (%08x)", errorCode);
     
        case kIOUSBTransactionTimeout:
            return string_format("Transaction Timed Out (%08x)", errorCode);
     
        case kIOUSBTransactionReturned:
            return string_format("Transaction has been returned to the caller (%08x)", errorCode);
     
        case kIOUSBPipeStalled:
            return string_format("Pipe has stalled, Error needs to be cleared (%08x)", errorCode);
     
        case kIOUSBInterfaceNotFound:
            return string_format("Interface Ref Not Recognized (%08x)", errorCode);
     
        case kIOUSBLowLatencyBufferNotPreviouslyAllocated:
            return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the data buffer) first (%08x)", errorCode);
     
        case kIOUSBLowLatencyFrameListNotPreviouslyAllocated:
            return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the frame list) first (%08x)", errorCode);
     
        case kIOUSBHighSpeedSplitError:
            return string_format("Error to hub on high speed bus trying to do split transaction (%08x)", errorCode);
     
        case kIOUSBSyncRequestOnWLThread:
            return string_format("A synchronous USB request was made on the workloop thread (from a callback?).  Only async requests are permitted in that case (%08x)", errorCode);
     
        case kIOUSBDeviceTransferredToCompanion:
            return string_format("The device has been tranferred to another controller for enumeration (%08x)", errorCode);
     
        case kIOUSBClearPipeStallNotRecursive:
            return string_format("ClearPipeStall should not be called recursively (%08x)", errorCode);
     
        case kIOUSBDevicePortWasNotSuspended:
            return string_format("Port was not suspended (%08x)", errorCode);
     
        case kIOUSBEndpointCountExceeded:
            return string_format("The endpoint was not created because the controller cannot support more endpoints (%08x)", errorCode);
     
        case kIOUSBDeviceCountExceeded:
            return string_format("The device cannot be enumerated because the controller cannot support more devices (%08x)", errorCode);
     
        case kIOUSBStreamsNotSupported:
            return string_format("The request cannot be completed because the XHCI controller does not support streams (%08x)", errorCode);
     
        case kIOUSBInvalidSSEndpoint:
            return string_format("An endpoint found in a SuperSpeed device is invalid (usually because there is no Endpoint Companion Descriptor) (%08x)", errorCode);
     
        case kIOUSBTooManyTransactionsPending:
            return string_format("The transaction cannot be submitted because it would exceed the allowed number of pending transactions (%08x)", errorCode);
     
        default:
            return string_format("Error Code (%08x)\n-- System: (%02X), SubSystem: (%02X), Code: (%02X) ", errorCode, err_get_system(errorCode), err_get_sub(errorCode), err_get_code(errorCode));
    }
}

std::string human_error_string(IOReturn errorCode)
{
    switch (errorCode) {
        case kIOReturnSuccess:
            return string_format("Success (%08x)", errorCode);
     
        case kIOReturnError:
            return string_format("General Error (%08x)", errorCode);
     
        case kIOReturnNoMemory:
            return string_format("Cannot Allocate Memory (%08x)", errorCode);
     
        case kIOReturnNoResources:
            return string_format("Resource Shortage (%08x)", errorCode);
     
        case kIOReturnIPCError:
            return string_format("IPC Error (%08x)", errorCode);
     
        case kIOReturnNoDevice:
            return string_format("No Such Device (%08x)", errorCode);
     
        case kIOReturnNotPrivileged:
            return string_format("Insufficient Privileges (%08x)", errorCode);
     
        case kIOReturnBadArgument:
            return string_format("Invalid Argument (%08x)", errorCode);
     
        case kIOReturnLockedRead:
            return string_format("Device Read Locked (%08x)", errorCode);
     
        case kIOReturnLockedWrite:
            return string_format("Device Write Locked (%08x)", errorCode);
     
        case kIOReturnExclusiveAccess:
            return string_format("Exclusive Access and Device already opened (%08x)", errorCode);
     
        case kIOReturnBadMessageID:
            return string_format("Sent/Received Messages had different MSG_ID (%08x)", errorCode);
     
        case kIOReturnUnsupported:
            return string_format("Unsupported Function (%08x)", errorCode);
     
        case kIOReturnVMError:
            return string_format("Misc. VM Failure (%08x)", errorCode);
     
        case kIOReturnInternalError:
            return string_format("Internal Error (%08x)", errorCode);
     
        case kIOReturnIOError:
            return string_format("General I/O Error (%08x)", errorCode);
     
        case kIOReturnCannotLock:
            return string_format("Can't Acquire Lock (%08x)", errorCode);
     
        case kIOReturnNotOpen:
            return string_format("Device Not Open (%08x)", errorCode);
     
        case kIOReturnNotReadable:
            return string_format("Read Not Supported (%08x)", errorCode);
     
        case kIOReturnNotWritable:
            return string_format("Write Not Supported (%08x)", errorCode);
     
        case kIOReturnNotAligned:
            return string_format("Alignment Error (%08x)", errorCode);
     
        case kIOReturnBadMedia:
            return string_format("Media Error (%08x)", errorCode);
     
        case kIOReturnStillOpen:
            return string_format("Device(s) Still Open (%08x)", errorCode);
     
        case kIOReturnRLDError:
            return string_format("RLD Failure (%08x)", errorCode);
     
        case kIOReturnDMAError:
            return string_format("DMA Failure (%08x)", errorCode);
     
        case kIOReturnBusy:
            return string_format("Device Busy (%08x)", errorCode);
     
        case kIOReturnTimeout:
            return string_format("I/O Timeout (%08x)", errorCode);
     
        case kIOReturnOffline:
            return string_format("Device Offline (%08x)", errorCode);
     
        case kIOReturnNotReady:
            return string_format("Not Ready (%08x)", errorCode);
     
        case kIOReturnNotAttached:
            return string_format("Device Not Attached (%08x)", errorCode);
     
        case kIOReturnNoChannels:
            return string_format("No DMA Channels Left (%08x)", errorCode);
     
        case kIOReturnNoSpace:
            return string_format("No Space For Data (%08x)", errorCode);
     
        case kIOReturnPortExists:
            return string_format("Port Already Exists (%08x)", errorCode);
     
        case kIOReturnCannotWire:
            return string_format("Can't Write Down (%08x)", errorCode);
     
        case kIOReturnNoInterrupt:
            return string_format("No Interrupt Attached (%08x)", errorCode);
     
        case kIOReturnNoFrames:
            return string_format("No DMA Frames Enqueued (%08x)", errorCode);
     
        case kIOReturnMessageTooLarge:
            return string_format("Oversized MSG Received On Interrupt Port (%08x)", errorCode);
     
        case kIOReturnNotPermitted:
            return string_format("Not Permitted (%08x)", errorCode);
     
        case kIOReturnNoPower:
            return string_format("No Power To Device (%08x)", errorCode);
     
        case kIOReturnNoMedia:
            return string_format("Media Not Present (%08x)", errorCode);
     
        case kIOReturnUnformattedMedia:
            return string_format("Media Not Formatted (%08x)", errorCode);
     
        case kIOReturnUnsupportedMode:
            return string_format("No Such Mode (%08x)", errorCode);
     
        case kIOReturnUnderrun:
            return string_format("Buffer Underflow (%08x)", errorCode);
     
        case kIOReturnOverrun:
            return string_format("Buffer Overflow (%08x)", errorCode);
     
        case kIOReturnDeviceError:
            return string_format("The Device Is Not Working Properly! (%08x)", errorCode);
     
        case kIOReturnNoCompletion:
            return string_format("A Completion Routine Is Required (%08x)", errorCode);
     
        case kIOReturnAborted:
            return string_format("Operation Aborted (%08x)", errorCode);
     
        case kIOReturnNoBandwidth:
            return string_format("Bus Bandwidth Would Be Exceeded (%08x)", errorCode);
     
        case kIOReturnNotResponding:
            return string_format("Device Not Responding (%08x)", errorCode);
     
        case kIOReturnIsoTooOld:
            return string_format("ISOChronous I/O request for distance past! (%08x)", errorCode);
     
        case kIOReturnIsoTooNew:
            return string_format("ISOChronous I/O request for distant future! (%08x)", errorCode);
     
        case kIOReturnNotFound:
            return string_format("Data Not Found (%08x)", errorCode);
     
        case kIOReturnInvalid:
            return string_format("Should Never Be Seen(%08x)", errorCode);
     
        default:
            /// See here for more: https://developer.apple.com/library/content/qa/qa1075/_index.html
            return usb_human_error_string(errorCode);
    }
}


#pragma mark - Device Private
USBDevice::USBDevice(io_object_t device) : deviceInterface(nullptr), interface(nullptr), device(device), controlPipeRef(0x00), readPipeRef(0x01), writePipeRef(0x02)
{
    IOObjectRetain(device);
}

USBDevice::~USBDevice()
{
    Close();
    IOObjectRelease(device);
}

void** USBDevice::GetInterface(io_object_t device, CFUUIDRef type, CFUUIDRef uuid)
{
    /// Create a plugin interface for the service.
    IOCFPlugInInterface **plugInInterface = nullptr;
    SInt32 score = 0;
 
    kern_return_t kr = IOCreatePlugInInterfaceForService(device, type, kIOCFPlugInInterfaceID, &plugInInterface, &score);
 
    if ((kIOReturnSuccess != kr) || !plugInInterface)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return nullptr;
    }
 
    /// Get an interface from the plugin.. and release the plugin
    void **interface = nullptr;
    HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(uuid), (LPVOID*) &interface);
    (*plugInInterface)->Release(plugInInterface);
 
    if (res || !interface)
    {
        return nullptr;
    }
    return interface;
}

int USBDevice::GetProperty(std::string propertyName)
{
    CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, propertyName.c_str(), kCFStringEncodingUTF8);
    CFNumberRef number = (CFNumberRef)IORegistryEntryCreateCFProperty(device, name, kCFAllocatorDefault, 0);
 
    if (number)
    {
        int value = 0;
        CFNumberGetValue(number, kCFNumberSInt32Type, &value);
        CFSAFERELEASE(number);
        CFSAFERELEASE(name);
        return value;
    }
 
    CFSAFERELEASE(name);
    return -1;
}

std::string USBDevice::GetStringDescriptor(std::uint8_t index)
{
    std::uint8_t requestBuffer[256];
    IOUSBDevRequest request = {
        .bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice),
        .bRequest = kUSBRqGetDescriptor,
        .wValue = static_cast<std::uint16_t>((kUSBStringDesc << 8) | index),
        .wIndex = 0x409, //English
        .wLength = sizeof(requestBuffer),
        .pData = requestBuffer
    };
 
    kern_return_t kr = (*deviceInterface)->DeviceRequest(deviceInterface, &request);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return "";
    }
 
    CFStringRef descriptor = CFStringCreateWithBytes(kCFAllocatorDefault, &requestBuffer[2], requestBuffer[0] - 2, kCFStringEncodingUTF16LE, false);
 
    CFIndex length = CFStringGetLength(descriptor);
    CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
    std::string result(maxSize, '\0');
    CFStringGetCString(descriptor, &result[0], maxSize, kCFStringEncodingUTF8);
    CFSAFERELEASE(descriptor);
    return result;
}

void USBDevice::GetPipeRefs()
{
    controlPipeRef = 0x00;
    readPipeRef = 0x01;
    writePipeRef = 0x02;
 
    if (interface)
    {
        UInt8 interfaceNumEndpoints = 0;
        IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
        if (err)
        {
            std::cerr<<human_error_string(err)<<"\n";
            return;
        }
 
        for (UInt8 ref = 1; ref <= interfaceNumEndpoints; ++ref)
        {
            UInt8 direction;
            UInt8 number;
            UInt8 transferType;
            UInt16 maxPacketSize;
            UInt8 interval;
     
            IOReturn kr = (*interface)->GetPipeProperties(interface, ref, &direction, &number, &transferType, &maxPacketSize, &interval);
     
            if (kr == kIOReturnSuccess)
            {
                if (transferType == kUSBControl)
                {
                    controlPipeRef = 0;
                }
                else if (transferType == kUSBBulk)
                {
                    if (direction == kUSBIn)
                    {
                        readPipeRef = ref;
                    }
                    else if (direction == kUSBOut)
                    {
                        writePipeRef = ref;
                    }
                }
            }
        }
    }
}

std::string USBDevice::GetName()
{
    io_string_t name = {0};
    kern_return_t kr = IORegistryEntryGetName(device, name);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
    }
    return std::string(name);
}

std::string USBDevice::GetClass()
{
    io_string_t name = {0};
    kern_return_t kr = IOObjectGetClass(device, name);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
    }
 
    return std::string(name);
}

std::string USBDevice::GetPath()
{
    io_string_t path = {0};
    kern_return_t kr = IORegistryEntryGetPath(device, kIOServicePlane, path);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
    }
 
    return std::string(path);
}

std::int32_t USBDevice::GetVendorID()
{
    return GetProperty(kUSBVendorID);
}

std::int32_t USBDevice::GetProductID()
{
    return GetProperty(kUSBProductID);
}

std::string USBDevice::GetSerialNumber()
{
    if (deviceInterface)
    {
        std::uint8_t index = 0;
        IOReturn kr = (*deviceInterface)->USBGetSerialNumberStringIndex(deviceInterface, &index);
        if (kr != kIOReturnSuccess)
        {
            std::cerr<<human_error_string(kr)<<"\n";
            return "";
        }
 
        return GetStringDescriptor(index);
    }
    return "";
}

std::string USBDevice::GetManufacturer()
{
    if (deviceInterface)
    {
        std::uint8_t index = 0;
        IOReturn kr = (*deviceInterface)->USBGetManufacturerStringIndex(deviceInterface, &index);
        if (kr != kIOReturnSuccess)
        {
            std::cerr<<human_error_string(kr)<<"\n";
            return "";
        }
 
        return GetStringDescriptor(index);
    }
    return "";
}

std::uint32_t USBDevice::GetLocationID()
{
    UInt32 locationID = -1;
    IOUSBDeviceInterface **deviceInterface = reinterpret_cast<IOUSBDeviceInterface **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID));
    if (deviceInterface)
    {
        // Get the location ID from the USB Interface
        (*deviceInterface)->GetLocationID(deviceInterface, &locationID);
 
        // Cleanup
        (*deviceInterface)->Release(deviceInterface);
        deviceInterface = nil;
    }
 
    return locationID;
}

std::string USBDevice::PrintInterface()
{
    UInt8 interfaceNumEndpoints = 0;
    IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
    if (err)
    {
        std::cerr<<human_error_string(err)<<"\n";
        return string_format("Unable to get number of endpoints (%08x)", err);
    }
 
    std::string output = string_format("Interface has %d endpoints\n", interfaceNumEndpoints);
 
    /// USUALLY..
    /// Index 0 = DeviceControl/ControlRequest.
    /// Index 1 = DeviceRead
    /// Index 2 = DeviceWrite
    for (UInt8 pipeRef = 1; pipeRef <= interfaceNumEndpoints; ++pipeRef)
    {
        UInt8 direction;
        UInt8 number;
        UInt8 transferType;
        UInt16 maxPacketSize;
        UInt8 interval;
 
        IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval);
 
        if (kr != kIOReturnSuccess)
        {
            output += string_format("Unable to get properties of pipe %d (%08x)\n", pipeRef, kr);
        }
        else
        {
            std::string directions[] = {"Out", "In", "None", "Any"};
            std::string transferTypes[] = {"Control", "ISOC", "Bulk", "Interrupt", "Any"};
     
            output += string_format("Pipe: %d, Direction: %s, TransferType: %s, MaxPacketSize: %d\n", pipeRef, directions[direction].c_str(), transferTypes[transferType].c_str(), maxPacketSize);
        }
    }
 
    return output;
}

std::uint16_t USBDevice::GetMaxPipePacketSize(std::uint8_t pipeRef)
{
    UInt8 direction;
    UInt8 number;
    UInt8 transferType;
    UInt16 maxPacketSize;
    UInt8 interval;
 
    IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval);
    if (kr != kIOReturnSuccess)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return 0;
    }
 
    return maxPacketSize;
}

void USBDevice::ClearPipe(std::uint8_t pipeRef, std::uint32_t timeout)
{
    uint16_t maxSize = GetMaxPipePacketSize(pipeRef);
    std::unique_ptr<char[]> readBuffer(new char[maxSize]);

    while(true)
    {
        std::uint32_t readSize = maxSize;
        bzero(readBuffer.get(), maxSize * sizeof(char));
        IOReturn kr = (*interface)->ReadPipeTO(interface, pipeRef, readBuffer.get(), (UInt32 *)&readSize, timeout, timeout);
        if (kr != kIOReturnSuccess)
        {
            std::cerr<<human_error_string(kr)<<"\n";
            break;
        }

        if (readSize == 0)
        {
            break;
        }
    }
 
    IOReturn kr = (*interface)->ClearPipeStallBothEnds(interface, pipeRef);
    if (kr)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return;
    }
}

bool USBDevice::Open()
{
    // Open the USB device for communication.
    deviceInterface = reinterpret_cast<IOUSBDeviceInterface300 **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID300));
 
    if (deviceInterface)
    {
        kern_return_t kr = (*deviceInterface)->USBDeviceOpen(deviceInterface);
        if (kr != kIOReturnSuccess)
        {
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
     
            std::cerr<<human_error_string(kr)<<"\n";
            return false;
        }
 

        //Get the configuration..
        IOUSBConfigurationDescriptorPtr config;
        kr = (*deviceInterface)->GetConfigurationDescriptorPtr(deviceInterface, 0, &config);
        if (kr != kIOReturnSuccess)
        {
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
     
            std::cerr<<human_error_string(kr)<<"\n";
            return false;
        }
 
        //Set the configuration..
        (*deviceInterface)->SetConfiguration(deviceInterface, config->bConfigurationValue);
 
        //Find the USB interface..
        IOUSBFindInterfaceRequest interfaceRequest;
        interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
 
        //Get an interface iterator..
        io_iterator_t iterator;
        kr = (*deviceInterface)->CreateInterfaceIterator(deviceInterface, &interfaceRequest, &iterator);
        if (kr != kIOReturnSuccess)
        {
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
     
            std::cerr<<human_error_string(kr)<<"\n";
            return false;
        }
 
        //Get the device object
        io_object_t usbDevice = IOIteratorNext(iterator);
        if (!usbDevice)
        {
            IOObjectRelease(iterator);
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
            return false;
        }
 
        //Get a USB Interface
        interface = reinterpret_cast<IOUSBInterfaceInterface300 **>(GetInterface(usbDevice, kIOUSBInterfaceUserClientTypeID, kIOUSBInterfaceInterfaceID300));
 
        if (!interface)
        {
            IOObjectRelease(usbDevice);
            IOObjectRelease(iterator);
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
            return false;
        }


        kr = (*interface)->USBInterfaceOpen(interface);
        if (kr != kIOReturnSuccess)
        {
            (*interface)->Release(interface);
            interface = nullptr;
     
            IOObjectRelease(usbDevice);
            IOObjectRelease(iterator);
            (*deviceInterface)->USBDeviceClose(deviceInterface);
            (*deviceInterface)->Release(deviceInterface);
            deviceInterface = nullptr;
            return false;
        }
 
        //Load the pipe references
        GetPipeRefs();
 
        //Create Async Notifications
        //TODO: Release this event..
        CFRunLoopSourceRef asyncEventSource;
        kr = (*interface)->CreateInterfaceAsyncEventSource(interface, &asyncEventSource);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), asyncEventSource, kCFRunLoopDefaultMode);
 
        IOObjectRelease(usbDevice);
        IOObjectRelease(iterator);
        return true;
    }
 
    return false;
}

void USBDevice::Close()
{
    if (interface)
    {
        (*interface)->USBInterfaceClose(interface);
        (*interface)->Release(interface);
        interface = nullptr;
    }
 
    if (deviceInterface)
    {
        (*deviceInterface)->USBDeviceClose(deviceInterface);
        (*deviceInterface)->Release(deviceInterface);
        deviceInterface = nullptr;
    }
}

kern_return_t USBDevice::GetPipeStatus(std::uint8_t pipeRef)
{
    if (interface)
    {
        return (*interface)->GetPipeStatus(interface, pipeRef);
    }
 
    return kIOReturnNotOpen;
}

NumVersion USBDevice::GetDriverVersion()
{
    if (interface)
    {
        NumVersion ioUSBLibVersion;
        NumVersion usbFamilyVersion;
        (*interface)->GetIOUSBLibVersion(interface, &ioUSBLibVersion, &usbFamilyVersion);
        return ioUSBLibVersion;
    }
 
    return NumVersion{0};
}

std::uint8_t USBDevice::GetControlPipeRef()
{
    return controlPipeRef;
}

std::uint8_t USBDevice::GetReadPipeRef()
{
    return readPipeRef;
}

std::uint8_t USBDevice::GetWritePipeRef()
{
    return writePipeRef;
}

void onReadAsync(void *refcon, IOReturn result, void *arg0)
{
 
}

void onWriteAsync(void *refcon, IOReturn result, void *arg0)
{
 
}

int USBDevice::Read(std::uint8_t* buffer, std::size_t bufferSize)
{
    if (interface)
    {
        UInt8 pipeRef = readPipeRef;
        UInt32 size = static_cast<UInt32>(bufferSize);
        kern_return_t res = (*interface)->ReadPipe(interface, pipeRef, buffer, &size);
        return res != kIOReturnSuccess ? -1 : static_cast<int>(size);
    }
    return -1;
}

int USBDevice::ReadAsync(std::uint8_t* buffer, std::size_t bufferSize)
{
    if (interface)
    {
        UInt8 pipeRef = readPipeRef;
        UInt32 size = static_cast<UInt32>(bufferSize);
        kern_return_t res = (*interface)->ReadPipeAsync(interface, pipeRef, buffer, size, onReadAsync, this);
        return res != kIOReturnSuccess ? -1 : static_cast<int>(size);
    }
    return -1;
}

int USBDevice::Write(uint8_t* buffer, size_t bufferSize)
{
    UInt8 pipeRef = writePipeRef;
    int packetSize = GetMaxPipePacketSize(pipeRef);
 
    size_t bytesWritten = 0;
    int bytesRemaining = static_cast<int>(bufferSize);
 
    auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) {
        kern_return_t res = (*interface)->WritePipe(interface, pipeRef, buffer, static_cast<int>(bufferSize));
        return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize);
    };
 
    while (bytesRemaining > 0)
    {
        size_t bytesToWrite = std::min(bytesRemaining, packetSize);
        auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite);
        if (retVal < 0)
        {
            return retVal;
        }
        else if (retVal < static_cast<int>(bytesToWrite))
        {
            return static_cast<int>(bytesWritten) + retVal;
        }
 
        bytesWritten += retVal;
        bytesRemaining -= retVal;
    }
 
    return static_cast<int>(bytesWritten);
}

int USBDevice::WriteAsync(std::uint8_t* buffer, std::size_t bufferSize)
{
    UInt8 pipeRef = writePipeRef;
    int packetSize = GetMaxPipePacketSize(pipeRef);
 
    size_t bytesWritten = 0;
    int bytesRemaining = static_cast<int>(bufferSize);
 
    auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) {
        kern_return_t res = (*interface)->WritePipeAsync(interface, pipeRef, buffer, static_cast<int>(bufferSize), onWriteAsync, this);
        return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize);
    };
 
    while (bytesRemaining > 0)
    {
        size_t bytesToWrite = std::min(bytesRemaining, packetSize);
        auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite);
        if (retVal < 0)
        {
            return retVal;
        }
        else if (retVal < static_cast<int>(bytesToWrite))
        {
            return static_cast<int>(bytesWritten) + retVal;
        }
 
        bytesWritten += retVal;
        bytesRemaining -= retVal;
    }
 
    return static_cast<int>(bytesWritten);
}

int USBDevice::SendDeviceRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout)
{
    if (!deviceInterface)
    {
        return -1;
    }
 
    //If there's no timeout.. just send a regular request..
    if (timeout <= 0)
    {
        IOUSBDevRequest req;
        req.bmRequestType = requesttype;
        req.bRequest = request;
        req.wValue = value;
        req.wIndex = index;
        req.wLength = size;
        req.pData = bytes;
 
        kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, &req);
        return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
    }
 
    //Send Timeout request..
    IOUSBDevRequestTO req;
    req.bmRequestType = requesttype;
    req.bRequest = request;
    req.wValue = value;
    req.wIndex = index;
    req.wLength = size;
    req.pData = bytes;
    req.completionTimeout = timeout;
 
    kern_return_t result = (*deviceInterface)->DeviceRequestTO(deviceInterface, &req);
    return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}

int USBDevice::SendControlRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout)
{
    if (!interface)
    {
        return -1;
    }
 
    //If there's no timeout.. just send a regular request..
    if (timeout <= 0)
    {
        IOUSBDevRequest req;
        req.bmRequestType = requesttype;
        req.bRequest = request;
        req.wValue = value;
        req.wIndex = index;
        req.wLength = size;
        req.pData = bytes;
 
        kern_return_t result = (*interface)->ControlRequest(interface, controlPipeRef, &req);
        return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
    }
 
    //Send Timeout request..
    IOUSBDevRequestTO req;
    req.bmRequestType = requesttype;
    req.bRequest = request;
    req.wValue = value;
    req.wIndex = index;
    req.wLength = size;
    req.pData = bytes;
    req.completionTimeout = timeout;
 
    kern_return_t result = (*interface)->ControlRequestTO(interface, controlPipeRef, &req);
    return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}

int USBDevice::SendRawRequest(IOUSBDevRequest *request)
{
    if (!deviceInterface)
    {
        return -1;
    }
    kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, request);
    return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred..
}

int USBDevice::SendRawControlRequest(IOUSBDevRequest *request, std::uint8_t pipeRef)
{
    if (!interface)
    {
        return -1;
    }
    kern_return_t result = (*interface)->ControlRequest(interface, pipeRef, request);
    return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred..
}

#pragma mark - USB Public

USBDeviceManager::USBDeviceManager() : notificationRegistry(nullptr)
{
}

USBDeviceManager::~USBDeviceManager()
{
    if (this->notificationRegistry)
    {
        USBNotification *notification = static_cast<USBNotification *>(this->notificationRegistry);
        delete notification;
    }
}

std::vector<std::unique_ptr<USBDevice>> USBDeviceManager::GetDevicesMatching(std::string service, int vendorId, int productId)
{
    io_iterator_t iter = 0;
 
    /// Find Matching Services
    CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str());
    if (matchingDict == nil)
    {
        return std::vector<std::unique_ptr<USBDevice>> {};
    }
 
    /// Added VendorID and ProductID to query
    CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef);
    CFRelease(numberRef);
 
    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef);
    CFRelease(numberRef);
    numberRef = nullptr;
 
    /// Find devices matching the above criteria
    kern_return_t kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter);
    if (kr != KERN_SUCCESS)
    {
        std::cerr<<human_error_string(kr)<<"\n";
        return std::vector<std::unique_ptr<USBDevice>> {};
    }
 
    /// Iterate over all devices
    io_service_t device = 0;
    std::vector<std::unique_ptr<USBDevice>> devices;
    while ((device = IOIteratorNext(iter)))
    {
        devices.push_back(std::unique_ptr<USBDevice>(new USBDevice(device)));
    }
 
    IOObjectRelease(iter);
    return devices;
}

void USBDeviceManager::RegisterForDeviceNotifications(std::string service, int vendorId, int productId, void(*onDeviceAdded)(USBDevice *device), void(*onDeviceDisconnected)(USBDevice *device))
{
    /// Find Matching Services
    CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str());
    if (matchingDict == nil)
    {
        return;
    }
 
    /// Added VendorID and ProductID to query
    CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef);
    CFRelease(numberRef);
 
    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId);
    CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef);
    CFRelease(numberRef);
    numberRef = nullptr;
 
    // Setup notifications for when the device is available
    USBNotification *notificationInfo = new USBNotification();
    notificationInfo->_this = this;
    notificationInfo->onDeviceAdded = onDeviceAdded;
    notificationInfo->onDeviceDisconnected = onDeviceDisconnected;
    notificationInfo->notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
    notificationInfo->runLoop = CFRunLoopGetCurrent();
    notificationInfo->deviceAddedIterator = 0;
    notificationInfo->deviceRemovedIterator = 0;
    this->notificationRegistry = notificationInfo;
 
    CFRunLoopSourceRef runLoopSource = IONotificationPortGetRunLoopSource(notificationInfo->notificationPort);
    CFRunLoopAddSource(notificationInfo->runLoop, runLoopSource, kCFRunLoopDefaultMode);
 
    // Now set up a notification to be called when a device is matched and detached by I/O Kit.
    kern_return_t rc;
//    rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort,
//                                          kIOTerminatedNotification,
//                                          matchingDict,
//                                          DeviceDisconnected,
//                                          notificationInfo, //userInfo
//                                          &notificationInfo->deviceRemovedIterator);
//
//    if (rc != kIOReturnSuccess)
//    {
//        IONotificationPortDestroy(notificationInfo->notificationPort);
//        delete notificationInfo;
//        std::cerr<<human_error_string(rc)<<"\n";
//        return;
//    }
 
    // Now set up a notification to be called when a device is matched and attached by I/O Kit.
    rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort,
                                          kIOMatchedNotification,
                                          matchingDict,
                                          DeviceAdded,
                                          notificationInfo, //userInfo
                                          &notificationInfo->deviceAddedIterator);
 
    if (rc != kIOReturnSuccess)
    {
        IONotificationPortDestroy(notificationInfo->notificationPort);
        delete notificationInfo;
        std::cerr<<human_error_string(rc)<<"\n";
        return;
    }
 
//    DeviceDisconnected(notificationInfo, notificationInfo->deviceRemovedIterator);
    DeviceAdded(notificationInfo, notificationInfo->deviceAddedIterator);
}

// When a device has been plugged into the phone's serial port, this function will get called
void DeviceAdded(void *userInfo, io_iterator_t iterator)
{
    kern_return_t kr;
    io_service_t usbDevice;
 
    USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo);
 
    // Iterate over all devices..
    while ((usbDevice = IOIteratorNext(iterator))) {
 
        DeviceMessage* notificationMessage = new DeviceMessage();
        notificationMessage->_this = new USBDevice(usbDevice);
        notificationMessage->notification = notificationInfo;
 
        if (notificationInfo->onDeviceAdded)
        {
            notificationInfo->onDeviceAdded(notificationMessage->_this);
        }
 
        // Register for Device Notifications.. IE: Disconnected Notification..
        kr = IOServiceAddInterestNotification(notificationInfo->notificationPort,
                                              usbDevice,
                                              kIOGeneralInterest,
                                              DeviceMessageReceived,
                                              notificationMessage, //userInfo
                                              &notificationInfo->notification);
        // Cleanup
        kr = IOObjectRelease(usbDevice);
    }
}

// When a device has been unplugged from the phone's serial port, this function will get called.
void DeviceDisconnected(void *userInfo, io_iterator_t iterator)
{
    USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo);
    if (notificationInfo->onDeviceDisconnected)
    {
        notificationInfo->onDeviceDisconnected(nullptr);
    }
 
    // Cleanup
    IOObjectRelease(notificationInfo->notification);
    delete notificationInfo;
}

// When a device message has been received..
void DeviceMessageReceived(void* userInfo, io_service_t service, uint32_t messageType, void* messageArgument)
{
    DeviceMessage *message = static_cast<DeviceMessage *>(userInfo);
 
    // If it's the disconnect message, we need to cleanup..
    if (messageType == kIOMessageServiceIsTerminated)
    {
        if (message->notification && message->notification->onDeviceDisconnected)
        {
            message->notification->onDeviceDisconnected(message->_this);
        }
 
        // Cleanup
//        IOObjectRelease(message->notification->notificationPort);
        delete message;
    }
}


Then you use it for Nintendo Switch like:

main.mm:
Code:
//
//  main.m
//  USBLoader
//
//  Created by Brandon on 2018-05-30.
//  Copyright © 2018 XIO. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/usb/IOUSBLib.h>
#import <IOKit/IOCFPlugIn.h>
#import <IOKit/IOMessage.h>
#import <IOKit/IOBSD.h>
#include <vector>
#include <iostream>
#include "USBDevice.hxx"

//Include some headers that read the fusee.bin and intermezzo and create the Fusee Gelee Payload..
#include "Fusee.h"

// Helper file to try to smash the stack..
#include "Smash.hpp"

//Nintendo Constants
#define kNintendoSwitchVendorID 0x0955
#define kNintendoSwitchProductID 0x7321

#pragma mark - Utilities

void displayStrings(std::vector<std::string> strings)
{
    for (std::string& str : strings)
    {
        std::cout<<str<<"\n";
    }
}

void displayStrings(NSArray<NSString *> *strings)
{
    for (NSString *str in strings)
    {
        NSLog(@"%@", str);
    }
}

void onDeviceAdded(USBDevice *device)
{
    if (!device->Open())
    {
        std::cerr<<"Cannot Open Device\n";
        return;
    }
 
    std::string manufacturer = device->GetManufacturer();
    std::string name = device->GetName();
    std::string clazz = device->GetClass();
    std::string path = device->GetPath();
    std::int32_t vendorId = device->GetVendorID();
    std::int32_t productId = device->GetProductID();
    std::uint32_t locationId = device->GetLocationID();
 
    displayStrings({
        "       Device Information",
        "-----------------------------------",
        string_format("Manufacturer: %s", manufacturer.c_str()),
        string_format("Name: %s", name.c_str()),
        string_format("Class: %s", clazz.c_str()),
        string_format("Path: %s", path.c_str()),
        string_format("VendorId: 0x%04X", vendorId),
        string_format("ProductId: 0x%04X", productId),
        string_format("LocationId: 0x%04X", locationId)
    });
 
    //device->ClearPipe(device->GetReadPipeRef(), 10);
    //device->ClearPipe(device->GetWritePipeRef(), 10);  //I don't have async event in the above class yet (it'll take a few lines of code to fix.. this function uses async read.. if changed to just read, it'll work).. in fact I think I can remove the read all together and just call clear stall.. - TODO: Brandon T investigate.
 
    uint8_t deviceId[0x10] = {0};
    if (device->Read(deviceId, sizeof(deviceId)) != sizeof(deviceId))
    {
        displayStrings({"Cannot Read Device-ID"});
        return;
    }
 
    std::cout<<"\n\n";
    std::string deviceSerialNumber = string_format("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
                                                   (uint32_t)deviceId[0],
                                                   (uint32_t)deviceId[1],
                                                   (uint32_t)deviceId[2],
                                                   (uint32_t)deviceId[3],
                                                   (uint32_t)deviceId[4],
                                                   (uint32_t)deviceId[5],
                                                   (uint32_t)deviceId[6],
                                                   (uint32_t)deviceId[7],
                                                   (uint32_t)deviceId[8],
                                                   (uint32_t)deviceId[9],
                                                   (uint32_t)deviceId[10],
                                                   (uint32_t)deviceId[11],
                                                   (uint32_t)deviceId[12],
                                                   (uint32_t)deviceId[13],
                                                   (uint32_t)deviceId[14],
                                                   (uint32_t)deviceId[15]);
 
    displayStrings({
        string_format("Device Id: %s\n\n", deviceSerialNumber.c_str()),
        "Loading Payload"
    });
 
    auto payload = createRCMPayload(intermezzo, fusee);
 
    displayStrings({"Uploading Payload"});
    smash(device, payload);
 
    device->Close();
    return;
}

void onDeviceDisconnected(USBDevice *device)
{
    std::cout<<"Device Disconnected\n";
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        USBDeviceManager device;
        device.RegisterForDeviceNotifications("IOUSBHostDevice",
                                              kNintendoSwitchVendorID,
                                              kNintendoSwitchProductID,
                                              onDeviceAdded,
                                              onDeviceDisconnected);
        CFRunLoopRun();
    }
    return 0;
}


It uploads the payload successfully.. I just can't get it to freaking smash the damn stack.. grr.. Also, once you "read" the deviceId, the control of the device is switched so you CANNOT run the code again without restarting the device.. I tried libusb and it behaves the same way so it means the code above is working as it should.. I just have to figure out how to smash the stack and that's it.


Functions I added: "human_error_string" will print out any errors from IOKit as human readable strings =]
The USBDeviceManager will load all the devices that match the vendor and product. You will see when you plug in the switch, it will display:

y6fd2ID.png




Once I figure out how to "Transfer Control" after uploading the payload, I will post here or upload the code or something to the github.

I think we got fairly far.. just need the last step.. Btw, I tested the above on my iPhone 6S.. Same output. Still no control transfer..
Could you commit what you have so far?
 

Site & Scene News

Popular threads in this forum

General chit-chat
Help Users
    AncientBoi @ AncientBoi: lol