Hacking Question Payload loader for iOS?

  • Thread starter Thread starter Enovale
  • Start date Start date
  • Views Views 82,525
  • Replies Replies 222
  • Likes Likes 1
iOS developer here.. You do not need to jailbreak to communicate with anything over USB. You also do not need the MFI license either (this license is for hardware certification and development)..

If all you want to do is communicate over USB, then libusb builds for iOS just fine and works (iFunbox used to use it iirc). Also you can use EASession (iOS 3+) from the "ExternalAccessory" framework which you can link to in XCode's framework and library user General tab for your project (https://i.imgur.com/lffgsDS.png). Then you'd have to setup the streams and notifications for devices connecting and disconnecting and sending data.

Again, if you're just doing software stuff like talking to a device, then you don't have to jailbreak. It's also free to get a developer license for personal use to run apps on your phone. You obviously cannot distribute the app but you can distribute the code. You can distribute the app + a script to resign it with another developer account so users can do that.


Edit: It seems computer to USB is much easier than iPhone to some other device (I just tried.. I can communicate from computer to phone.. but can't get the phone to communicate with the switch using the USBC-To-Lightning cable).. weird.
 
Last edited by JustBrandonT,
I sense a need for convincing some people to do a bootcamp lite for iphone.

It would definitely open more doors than a skeleton key.

All that has to be done is setup a ____(crowd fund) or make the app a paid app and submit it to Apple, to make someone jump. Even if it was for benign purposes, we know what could be done....the convincing of Apple for distribution would be the hardest part.


-----
@JustBrandonT

So, would you be willing to work with others for the benefit of the community? No pressure, but you do have knowledge on this and that could really, really help a lot of people.
 
Last edited by H1B1Esquire,
  • Like
Reactions: Subtle Demise
iOS developer here.. You do not need to jailbreak to communicate with anything over USB. You also do not need the MFI license either (this license is for hardware certification and development)..

If all you want to do is communicate over USB, then libusb builds for iOS just fine and works (iFunbox used to use it iirc). Also you can use EASession from the "ExternalAccessory" framework which you can link to in XCode's framework and library user General tab for your project. Then you'd have to setup the streams and notifications for devices connecting and disconnecting and sending data.

Again, if you're just doing software stuff like talking to a device, then you don't have to jailbreak. It's also free to get a developer license for personal use to run apps on your phone. You obviously cannot distribute the app but you can distribute the code. You can distribute the app + a script to resign it with another developer account so users can do that.
Finally someone who can help :P

I haven't done much iOS developing so ill have to tinker with libusb for a while, thanks for the info!
 
  • Like
Reactions: H1B1Esquire
Well the peertalk thing does have a send payload method of the PTProtocol.h
Code:
// Generate a new tag that is unique within this protocol object.
- (uint32_t)newTag;

// Send a frame over *channel* with an optional payload and optional callback.
// If *callback* is not NULL, the block is invoked when either an error occured
// or when the frame (and payload, if any) has been completely sent.
- (void)sendFrameOfType:(uint32_t)frameType
tag:(uint32_t)tag
withPayload:(dispatch_data_t)payload
overChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error))callback;
#pragma mark Sending frames

The way that's built though it seems more for communicating between an iDevice and a computer or computer to iDevice, rather than iDevice to something else. I mean it seems like the two things talking need to speak this customized peertalk protocol. (both have to be built with peertalk support in mind It looks like to me, I could be wrong I just glanced quickly).

I think libusb or something similar that's more just low level usb communication support is our best bet for iOS. And yea the android version doesn't require rooting, so in my mind neither should the ios version either. Just the thing about installing it with a self signed certificate is the only thing trying to block the path, and if you know how to do that with the latest versions of mac and ios then it's no issue at all. Who said anything about an appstore, not necessary we sign it ourselves and install it ourselves. Or be jailbroken and run it unsigned :) lol either way.

Actually though for my application in my forum signature, that's the problem I ran into lately on the newer version of mac and xcode and ios. I used to be able to self sign or completely unsigned and build an iOS version of my apps. For that one I got it to compile and actually run and work in the simulator, but it won't let me build the one for actual ios devices so people can self sign it and install it if they want.
In case anyone didn't believe me here's a pic of an iOS simulator making an encrypted dns query to this domain using my app! xD So it should work on a real device too! (GUI could use a little fixing for the ios release though)
I9ekhgi.png


ElijahZAwesome or JustBrandonT or any ios developer that think they can help me with self signing or unsigned releases in the newer version of mac and ios then open the spoiler please. (to not clutter the thread just for my problem)
Ok here's the trouble I ran into with trying to build my app for other people despite not having an ios device myself lately...
First it says: (upon trying to build it for real device)
Check dependencies
Code Signing Error: Signing for "YourFriendlyDNS" requires a development team. Select a development team in the project editor.
Code Signing Error: Code signing is required for product type 'Application' in SDK 'iOS 11.2'

Then when I go to select a development team in qt it says:
AeCOIsz.png


Okay so I go and load up xcode and try to configure that, create a blank test project and the general tab under signing I add my apple id, my development team shows up, okay good. But then it says it can't create a provision profile for me:
wLFDFV9.png

Damnit lol! The website isn't helpful for creating a provisioning profile either (seems it's for the developer program only not personal)... Still fails to build with the error of no provisioning profile, despite now a team being selected.

I realized that Qt generates a nice xcode project file in the iosdevice release folder (where the binary would go if successfully built), so I open that, and yea in xcode it also builds for the simulator but same problem when selecting 'Generic iOS Device' to build for an actual device.

Okay so I try what I think was my old ways and go to build setting and under signing put don't code sign for everything, then I get the furthest but still no dice:
ssii6f7.png

What are these entitlements and where can I disable them, or bypass this to build it because people have to self sign it themselves anyway with either a personal or developer certificate!? (or be jailbroken).

I'm stuck here until I can either have to pick up an ios device just to build for the platform, or there's got to be a way to get around this to build it without one! Thanks for reading this and maybe helping me solve it! :)

Yea I'd like to help get an iOS version of RCM injector going, there's one for android so there should be one for iOS too! And for those saying why bother there's easier ways, well maybe that's why because iOS is trickier it's going to be a bit tougher to do, but still possible! And the possible part is what gets us going! :) Like even JustBrandonT with his initial attempts it doesn't appear to be working, there's probably something getting in the way or that we have to work around.

JustBrandonT do you have some other usb stuff to try and communicate with, to see if it's just the switch being pickier or not being talked to properly? We need to debug it, is anything actually going through the wire yet? What are you using how are you trying to send data using libusb? Chances are something has to be changed or a parameter is not correct or id or channel or port or something like that. Try figuring out what could be the issue and I'll take a look and libusb as well and see if I can figure out what you were trying so far!
 
  • Like
Reactions: H1B1Esquire
Well the peertalk thing does have a send payload method of the PTProtocol.h
Code:
// Generate a new tag that is unique within this protocol object.
- (uint32_t)newTag;

// Send a frame over *channel* with an optional payload and optional callback.
// If *callback* is not NULL, the block is invoked when either an error occured
// or when the frame (and payload, if any) has been completely sent.
- (void)sendFrameOfType:(uint32_t)frameType
tag:(uint32_t)tag
withPayload:(dispatch_data_t)payload
overChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error))callback;
#pragma mark Sending frames

The way that's built though it seems more for communicating between an iDevice and a computer or computer to iDevice, rather than iDevice to something else. I mean it seems like the two things talking need to speak this customized peertalk protocol. (both have to be built with peertalk support in mind It looks like to me, I could be wrong I just glanced quickly).

I think libusb or something similar that's more just low level usb communication support is our best bet for iOS. And yea the android version doesn't require rooting, so in my mind neither should the ios version either. Just the thing about installing it with a self signed certificate is the only thing trying to block the path, and if you know how to do that with the latest versions of mac and ios then it's no issue at all. Who said anything about an appstore, not necessary we sign it ourselves and install it ourselves. Or be jailbroken and run it unsigned :) lol either way.

Actually though for my application in my forum signature, that's the problem I ran into lately on the newer version of mac and xcode and ios. I used to be able to self sign or completely unsigned and build an iOS version of my apps. For that one I got it to compile and actually run and work in the simulator, but it won't let me build the one for actual ios devices so people can self sign it and install it if they want.
In case anyone didn't believe me here's a pic of an iOS simulator making an encrypted dns query to this domain using my app! xD So it should work on a real device too! (GUI could use a little fixing for the ios release though)
I9ekhgi.png


ElijahZAwesome or JustBrandonT or any ios developer that think they can help me with self signing or unsigned releases in the newer version of mac and ios then open the spoiler please. (to not clutter the thread just for my problem)
Ok here's the trouble I ran into with trying to build my app for other people despite not having an ios device myself lately...
First it says: (upon trying to build it for real device)
Check dependencies
Code Signing Error: Signing for "YourFriendlyDNS" requires a development team. Select a development team in the project editor.
Code Signing Error: Code signing is required for product type 'Application' in SDK 'iOS 11.2'

Then when I go to select a development team in qt it says:
AeCOIsz.png


Okay so I go and load up xcode and try to configure that, create a blank test project and the general tab under signing I add my apple id, my development team shows up, okay good. But then it says it can't create a provision profile for me:
wLFDFV9.png

Damnit lol! The website isn't helpful for creating a provisioning profile either (seems it's for the developer program only not personal)... Still fails to build with the error of no provisioning profile, despite now a team being selected.

I realized that Qt generates a nice xcode project file in the iosdevice release folder (where the binary would go if successfully built), so I open that, and yea in xcode it also builds for the simulator but same problem when selecting 'Generic iOS Device' to build for an actual device.

Okay so I try what I think was my old ways and go to build setting and under signing put don't code sign for everything, then I get the furthest but still no dice:
ssii6f7.png

What are these entitlements and where can I disable them, or bypass this to build it because people have to self sign it themselves anyway with either a personal or developer certificate!? (or be jailbroken).

I'm stuck here until I can either have to pick up an ios device just to build for the platform, or there's got to be a way to get around this to build it without one! Thanks for reading this and maybe helping me solve it! :)

Yea I'd like to help get an iOS version of RCM injector going, there's one for android so there should be one for iOS too! And for those saying why bother there's easier ways, well maybe that's why because iOS is trickier it's going to be a bit tougher to do, but still possible! And the possible part is what gets us going! :) Like even JustBrandonT with his initial attempts it doesn't appear to be working, there's probably something getting in the way or that we have to work around.

JustBrandonT do you have some other usb stuff to try and communicate with, to see if it's just the switch being pickier or not being talked to properly? We need to debug it, is anything actually going through the wire yet? What are you using how are you trying to send data using libusb? Chances are something has to be changed or a parameter is not correct or id or channel or port or something like that. Try figuring out what could be the issue and I'll take a look and libusb as well and see if I can figure out what you were trying so far!

getting and installing ipas from xcode is super easy, ive been doing it for ages without a devloper ID.

From a stackoverflow answer:
"From some time (for example Swift & Xcode7) when you are to make a build formula is more complicated - xcodebuild requires exportOptionsPlist parameter:

Code:
 xcodebuild -exportArchive -exportOptionsPlist app.plist  -archivePath app.xcarchive -exportPath app.ipa
and app.plist contains:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
    <key>method</key>
    <string>app-store</string>
    <key>uploadSymbols</key>
    <true/>
    </dict>
</plist>
"

or even simpler,

"There are 3 WAYS to create .ipa WITHOUT Command & Apple Developer Account.

1. Fast & Best

(Works on all Xcode, All Mac OS, Bundled package can be used for OTA links like Diawi)

Just build (Command+B) your app from XCode by setting proper code signing identities
From XCode's file search at left bottom, search for .app (This will be under product directory)
Right Click on this .app file and select Show in Finder
Now, create directory and name it as Payload, copy .app into Payload directory.
Archive/Compress(.zip) this Payload directory, rename file extension from .zip to .ipa"


both of these methods work fine, just make the ipa, then install it with cydia impactor.
 
  • Like
Reactions: H1B1Esquire
just posting this here will not post links or say how i do it or anything but you don't need to jailbreak your iphone's to get apps not on the app store or get payed apps for free i do it with an app i have installed on my iphone as long. as long as your are able to trust the developer it will work fine but there is a drawback on it though the apps will work for a week then always have to reinstall them.
 
Yes, Cydia Impactor, this has already been mentioned. If you have a developer account it lasts a year, but thats the end user's problem.
 
Sorry for late reply.. was coding and reading: https://www.theiphonewiki.com/wiki//dev.. I jailbroke an old device I had lying around to see if it'd make a difference.. I can connect to other devices but again, I can't seem to find the "Nintendo Switch" (connected via USB-C-to-Lightning)..

The code is below (Languages are C++ & Objective-C++ for Serial.mm, Objective-C for Serial.h, Swift for ViewController.swift):

Serial.h:
Code:
//
//  Serial.h
//  iOUSB
//
//  Created by Brandon on 2018-05-21.
//  Copyright © 2018 XIO. All rights reserved.
//

#if !defined(__cplusplus)  //-fmodules -fcxx-modules
@import Foundation;
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wauto-import"
#import <Foundation/Foundation.h>
#pragma clang diagnostic pop
#endif

#define BAUD_RATE 9600

@interface Serial : NSObject
+ (instancetype)shared;
- (bool)setup:(NSString *)filePath;
- (bool)disconnect;
- (size_t)available;
- (size_t)read:(uint8_t *)bytes length:(int32_t)length;
- (size_t)write:(uint8_t *)bytes length:(int32_t)length;
- (void)flushInputStream;
- (void)flushOutputStream;
- (void)flush;
- (NSString *)lastError;
- (void)eraseLastError;
@end

Serial.mm
Code:
//
//  Serial.mm
//  iOUSB
//
//  Created by Brandon on 2018-05-21.
//  Copyright © 2018 XIO. All rights reserved.
//

#import "Serial.h"
#include <fstream>
#include <iostream>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

#define DEFAULT_BAUD_RATE 9600

@interface Serial()
@property (nonatomic, assign) int file_descriptor;
@property (nonatomic, strong) NSString *lastError;
@end

@implementation Serial
+ (instancetype)shared {
    static Serial *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[Serial alloc] init];
    });
    return instance;
}

- (instancetype)init {
    if ((self = [super init])) {
        self.file_descriptor = -1;
        self.lastError = nil;
    }
    return self;
}

- (void)dealloc {
    [self disconnect];
}

- (bool)setup:(NSString *)filePath {
    if (self.file_descriptor != -1)
    {
        return true;
    }
 
    self.file_descriptor = open(filePath.UTF8String, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
    if (self.file_descriptor == -1)
    {
        const char* error = strerror(errno);
        self.lastError = [[NSString alloc] initWithUTF8String:error];
        perror(error);
        return false;
    }
 
    struct termios options;
    struct termios oldoptions;
    tcgetattr(self.file_descriptor, &oldoptions);
    options = oldoptions;
 
    #if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
    int baud_rates[] = {B0, B50, B75, B110, B134, B150, B200, B300, B1200, B1800, B2400, B4800, B9600, B19200, B38400, B7200, B14400, B28800, B57600, B76800, B115200, B230400
    };
    #else
    int baud_rates[] = {B0, B50, B75, B110, B134, B150, B200, B300, B1200, B1800, B2400, B4800, B9600, B19200, B38400};
    #endif
 
    auto it = std::find(std::begin(baud_rates), std::end(baud_rates), BAUD_RATE);
    if (it != std::end(baud_rates))
    {
        cfsetispeed(&options, *it);
        cfsetospeed(&options, *it);
        std::cout<<"BAUD_RATE Set Successfully!\n";
    }
    else
    {
        cfsetispeed(&options, DEFAULT_BAUD_RATE);
        cfsetospeed(&options, DEFAULT_BAUD_RATE);
        std::cerr<<"Invalid BAUD_RATE.. Setting to default: "<<DEFAULT_BAUD_RATE<<"\n";
    }
 
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag |= CS8;
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    tcsetattr(self.file_descriptor, TCSANOW, &options);
    return true;
}

- (bool)disconnect {
    if (self.file_descriptor != -1)
    {
        close(self.file_descriptor);
        self.file_descriptor = -1;
        self.lastError = nil;
    }
 
    return self.file_descriptor == -1;
}

- (size_t)available {
    if (self.file_descriptor == -1)
    {
        return -1;
    }
 
    int available = -1;
    ioctl(self.file_descriptor, FIONREAD, &available);
    return available;
}

- (size_t)read:(uint8_t *)bytes length:(int32_t)length {
    if (self.file_descriptor == -1)
    {
        return -1;
    }
 
    ssize_t bytesRead = read(self.file_descriptor, bytes, length);
    if (bytesRead < 0)
    {
        const char* error = strerror(errno);
        self.lastError = [[NSString alloc] initWithUTF8String:error];
        perror(error);
    }
    return bytesRead;
}

- (size_t)write:(uint8_t *)bytes length:(int32_t)length {
    if (self.file_descriptor == -1)
    {
        return -1;
    }
 
    ssize_t bytesWritten = write(self.file_descriptor, bytes, length);
    if (bytesWritten <= 0)
    {
        const char* error = strerror(errno);
        self.lastError = [[NSString alloc] initWithUTF8String:error];
        perror(error);
    }
    return bytesWritten;
}

- (void)flushInputStream {
    if (self.file_descriptor == -1)
    {
        return;
    }
 
    tcflush(self.file_descriptor, TCIFLUSH);
}

- (void)flushOutputStream {
    if (self.file_descriptor == -1)
    {
        return;
    }
 
    tcflush(self.file_descriptor, TCOFLUSH);
}

- (void)flush {
    if (self.file_descriptor == -1)
    {
        return;
    }
 
    tcflush(self.file_descriptor, TCIOFLUSH);
}

- (NSString *)lastError {
    return _lastError ?: @"";
}

- (void)eraseLastError {
    _lastError = nil;
}
@end

ViewController.swift:
Code:
//
//  ViewController.swift
//  iOUSB
//
//  Created by Brandon on 2018-05-20.
//  Copyright © 2018 XIO. All rights reserved.
//

import UIKit
import Foundation
import ExternalAccessory

class ViewController: UIViewController {
  
    private let tableView = UITableView(frame: .zero, style: .grouped)
    private let refreshButton = UIButton(type: .custom)
    private var deviceList = [String]()
    private var connectedDevice: String = ""

    override func viewDidLoad() {
        super.viewDidLoad()
      
        title = "Serial Devices"
      
        //Setup UI
        setupUI()
      
        //Register for Accessory Notifications
        NotificationCenter.default.addObserver(self, selector: #selector(onAccessoryConnected(_:)), name: NSNotification.Name.EAAccessoryDidConnect, object: nil)
      
        NotificationCenter.default.addObserver(self, selector: #selector(onAccessoryDisconnected(_:)), name: NSNotification.Name.EAAccessoryDidDisconnect, object: nil)
      
        EAAccessoryManager.shared().registerForLocalNotifications()
      
        //Setup Serial Communications
        listDevices()
    }
  
    deinit {
        //Unregister for Accessory Notifications
        EAAccessoryManager.shared().unregisterForLocalNotifications()
        NotificationCenter.default.removeObserver(self)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    //Accessory Notification Callbacks
    @objc
    private func onAccessoryConnected(_ notification: NSNotification) {
        print("Device Connected")
      
        EAAccessoryManager.shared().connectedAccessories.forEach({
            print("Manufacturer: \($0.manufacturer)")
            print("Name: \($0.name)")
            print("Model Number: \($0.modelNumber)")
            print("Serial Number: \($0.serialNumber)")
            print("Hardware Revision: \($0.hardwareRevision)")
            print("Firmware Revision: \($0.firmwareRevision)")
            print("Protocol Strings: \($0.protocolStrings)")
        })
    }
  
    @objc
    private func onAccessoryDisconnected(_ notification: NSNotification) {
        print("Device Disconnected")
    }
}

extension ViewController {
    func setupUI() {
        view.backgroundColor = .white
      
        let footerView = { () -> UIView in
            let view = UIView()
            view.backgroundColor = UIColor(white: 0.0, alpha: 0.15)
            view.addSubview(refreshButton)
            refreshButton.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                refreshButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15.0),
                refreshButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15.0),
                refreshButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 15.0),
                refreshButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15.0),
                refreshButton.heightAnchor.constraint(equalToConstant: 45.0)
            ])
          
            return view
        }()
      
        refreshButton.layer.cornerRadius = 5.0
        refreshButton.backgroundColor = .blue
        refreshButton.setTitleColor(.white, for: .normal)
        refreshButton.setTitle("Reload", for: .normal)
        refreshButton.addTarget(self, action: #selector(onReload(_:)), for: .touchUpInside)
      
        view.addSubview(tableView)
        view.addSubview(footerView)
        tableView.delegate = self
        tableView.dataSource = self
      
        if #available(iOS 11.0, *) {
            NSLayoutConstraint.activate([
                tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
                tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
                tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
              
                footerView.leftAnchor.constraint(equalTo: view.leftAnchor),
                footerView.rightAnchor.constraint(equalTo: view.rightAnchor),
                footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor),
                footerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
            ])
        }
        else {
            NSLayoutConstraint.activate([
                tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
                tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
                tableView.topAnchor.constraint(equalTo: view.topAnchor),
              
                footerView.leftAnchor.constraint(equalTo: view.leftAnchor),
                footerView.rightAnchor.constraint(equalTo: view.rightAnchor),
                footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor),
                footerView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            ])
        }
      
        view.subviews.forEach({ $0.translatesAutoresizingMaskIntoConstraints = false })
        tableView.register(DeviceCell.self, forCellReuseIdentifier: "DeviceCell")
    }
  
    func listDevices() {
        let fileManager = FileManager.default
        if let pathURL = URL(string: "/dev") {
            do {
                let fileURLs = try fileManager.contentsOfDirectory(at: pathURL, includingPropertiesForKeys: nil)
                deviceList = fileURLs.map({ $0.path }).sorted()
                tableView.reloadData()
            } catch {
                print("Error while enumerating files \(pathURL): \(error.localizedDescription)")
            }
        }
        else {
            print("Error opening path: /dev")
        }
    }
  
    @objc
    private func onReload(_ button: UIButton) {
        listDevices()
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
  
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return deviceList.count
    }
  
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "DeviceCell", for: indexPath) as! DeviceCell
      
        cell.configure(title: deviceList[indexPath.row]) { [weak self] in
          
            if let `self` = self {
                if self.deviceList.contains(self.connectedDevice) {
                    if Serial.shared().disconnect() {
                        self.connectedDevice = ""
                        cell.actionButton.setTitle("Connect", for: .normal)
                        cell.actionButton.backgroundColor = .blue
                        print("Disconnected!")
                    }
                }
                else {
                    if (Serial.shared().setup(self.deviceList[indexPath.row])) {
                        self.connectedDevice = self.deviceList[indexPath.row]
                        cell.actionButton.setTitle("Disconnect", for: .normal)
                        cell.actionButton.backgroundColor = .green
                        print("Connected!")
                    }
                    else {
                        let alert = UIAlertController(title: "Error", message: "Error Connecting: \(Serial.shared().lastError() ?? "UnknownError").", preferredStyle: .alert)
                        alert.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                        Serial.shared().eraseLastError()
                    }
                }
            }
        }
      
        if self.deviceList.contains(self.connectedDevice) {
            cell.actionButton.setTitle("Disconnect", for: .normal)
            cell.actionButton.backgroundColor = .green
        }
        else {
            cell.actionButton.setTitle("Connect", for: .normal)
            cell.actionButton.backgroundColor = .blue
        }
        return cell
    }
  
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }
  
    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100.0
    }
  
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return .leastNormalMagnitude
    }
  
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return .leastNormalMagnitude
    }
}

class DeviceCell: UITableViewCell {
    private let titleView = UILabel()
    let actionButton = UIButton(type: .custom)
    private var eventHandler: (() -> Void)?
  
    func configure(title: String, eventHandler: @escaping () -> Void) {
        titleView.text = title
        self.eventHandler = eventHandler
    }
  
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
      
        selectionStyle = .none
      
        actionButton.setTitle("Connect", for: .normal)
        actionButton.setTitleColor(.white, for: .normal)
        actionButton.contentEdgeInsets = UIEdgeInsets(top: 5.0, left: 15.0, bottom: 5.0, right: 15.0)
        actionButton.layer.cornerRadius = 5.0
        actionButton.backgroundColor = .blue
      
        contentView.addSubview(titleView)
        contentView.addSubview(actionButton)
      
        NSLayoutConstraint.activate([
            titleView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 15.0),
            titleView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 15.0),
            titleView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -15.0),
          
            actionButton.leftAnchor.constraint(greaterThanOrEqualTo: titleView.rightAnchor, constant: 15.0),
            actionButton.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -15.0),
            actionButton.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 15.0),
            actionButton.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -15.0),
            actionButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ])
      
        contentView.subviews.forEach({ $0.translatesAutoresizingMaskIntoConstraints = false })
      
        actionButton.addTarget(self, action: #selector(onActionPerformed(_:)), for: .touchUpInside)
    }
  
    @objc
    private func onActionPerformed(_ button: UIButton) {
        self.eventHandler?()
    }
  
    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


Screenshots:
https://i.imgur.com/GgTFV3f.png
https://i.imgur.com/PzM5v9U.png
https://i.imgur.com/Wy32uW5.png
https://i.imgur.com/4coSFep.png


If someone knows which of the above ports the switch would be on, I'd gladly connect and write something to upload the payload as I can communicate with any device.. I just don't know enough to continue atm (yet)..

Technically it should show up as TTYUSB-Serial-XXXXX right? Similar to how Linux and OSX sees usb devices.. but I never really saw that so.. not sure..

Notes: I didn't put the switch in RCM.

EDIT: Looks like I need an adapter.. My old one doesn't work. Going to pick one up now. Will report back.
 
Last edited by JustBrandonT,
Not to rub salt in the wound, and iPhones and iPads are great devices, but as locked down as their operating systems are, they're the kinds of things I would only recommend to my parents or not-so-technologically-savvy friends. Nobody with a desire for any customization or control whatsoever uses an iPhone.

Edit: I understand not everyone has a choice regarding his or her cell phone, particularly younger members, but you shouldn't expect to do a lot of this kind of thing with an iPhone, at least not in a timely fashion.
Finally, a good comment on apple devices.
 
Sorry for late reply.. was coding.. I jailbroke an old device I had lying around to see if it'd make a difference.. I can connect to other devices but again, I can't seem to find the "Nintendo Switch" (connected via USB-C-to-Lightning)..

The code is below (Languages are C++ & Objective-C++ for Serial.mm, Objective-C for Serial.h, Swift for ViewController.swift):

Serial.h:
Code:
//
//  Serial.h
//  iOUSB
//
//  Created by Brandon on 2018-05-21.
//  Copyright © 2018 XIO. All rights reserved.
//

#if !defined(__cplusplus)  //-fmodules -fcxx-modules
@import Foundation;
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wauto-import"
#import <Foundation/Foundation.h>
#pragma clang diagnostic pop
#endif

#define BAUD_RATE 9600

@interface Serial : NSObject
+ (instancetype)shared;
- (bool)setup:(NSString *)filePath;
- (bool)disconnect;
- (size_t)available;
- (size_t)read:(uint8_t *)bytes length:(int32_t)length;
- (size_t)write:(uint8_t *)bytes length:(int32_t)length;
- (void)flushInputStream;
- (void)flushOutputStream;
- (void)flush;
- (NSString *)lastError;
- (void)eraseLastError;
@end

Serial.mm
Code:
//
//  Serial.mm
//  iOUSB
//
//  Created by Brandon on 2018-05-21.
//  Copyright © 2018 XIO. All rights reserved.
//

#import "Serial.h"
#include <fstream>
#include <iostream>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

#define DEFAULT_BAUD_RATE 9600

@interface Serial()
@property (nonatomic, assign) int file_descriptor;
@property (nonatomic, strong) NSString *lastError;
@end

@implementation Serial
+ (instancetype)shared {
    static Serial *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[Serial alloc] init];
    });
    return instance;
}

- (instancetype)init {
    if ((self = [super init])) {
        self.file_descriptor = -1;
        self.lastError = nil;
    }
    return self;
}

- (void)dealloc {
    [self disconnect];
}

- (bool)setup:(NSString *)filePath {
    if (self.file_descriptor != -1)
    {
        return true;
    }
 
    self.file_descriptor = open(filePath.UTF8String, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
    if (self.file_descriptor == -1)
    {
        const char* error = strerror(errno);
        self.lastError = [[NSString alloc] initWithUTF8String:error];
        perror(error);
        return false;
    }
 
    struct termios options;
    struct termios oldoptions;
    tcgetattr(self.file_descriptor, &oldoptions);
    options = oldoptions;
 
    #if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
    int baud_rates[] = {B0, B50, B75, B110, B134, B150, B200, B300, B1200, B1800, B2400, B4800, B9600, B19200, B38400, B7200, B14400, B28800, B57600, B76800, B115200, B230400
    };
    #else
    int baud_rates[] = {B0, B50, B75, B110, B134, B150, B200, B300, B1200, B1800, B2400, B4800, B9600, B19200, B38400};
    #endif
 
    auto it = std::find(std::begin(baud_rates), std::end(baud_rates), BAUD_RATE);
    if (it != std::end(baud_rates))
    {
        cfsetispeed(&options, *it);
        cfsetospeed(&options, *it);
        std::cout<<"BAUD_RATE Set Successfully!\n";
    }
    else
    {
        cfsetispeed(&options, DEFAULT_BAUD_RATE);
        cfsetospeed(&options, DEFAULT_BAUD_RATE);
        std::cerr<<"Invalid BAUD_RATE.. Setting to default: "<<DEFAULT_BAUD_RATE<<"\n";
    }
 
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag |= CS8;
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    tcsetattr(self.file_descriptor, TCSANOW, &options);
    return true;
}

- (bool)disconnect {
    if (self.file_descriptor != -1)
    {
        close(self.file_descriptor);
        self.file_descriptor = -1;
        self.lastError = nil;
    }
 
    return self.file_descriptor == -1;
}

- (size_t)available {
    if (self.file_descriptor == -1)
    {
        return -1;
    }
 
    int available = -1;
    ioctl(self.file_descriptor, FIONREAD, &available);
    return available;
}

- (size_t)read:(uint8_t *)bytes length:(int32_t)length {
    if (self.file_descriptor == -1)
    {
        return -1;
    }
 
    ssize_t bytesRead = read(self.file_descriptor, bytes, length);
    if (bytesRead < 0)
    {
        const char* error = strerror(errno);
        self.lastError = [[NSString alloc] initWithUTF8String:error];
        perror(error);
    }
    return bytesRead;
}

- (size_t)write:(uint8_t *)bytes length:(int32_t)length {
    if (self.file_descriptor == -1)
    {
        return -1;
    }
 
    ssize_t bytesWritten = write(self.file_descriptor, bytes, length);
    if (bytesWritten <= 0)
    {
        const char* error = strerror(errno);
        self.lastError = [[NSString alloc] initWithUTF8String:error];
        perror(error);
    }
    return bytesWritten;
}

- (void)flushInputStream {
    if (self.file_descriptor == -1)
    {
        return;
    }
 
    tcflush(self.file_descriptor, TCIFLUSH);
}

- (void)flushOutputStream {
    if (self.file_descriptor == -1)
    {
        return;
    }
 
    tcflush(self.file_descriptor, TCOFLUSH);
}

- (void)flush {
    if (self.file_descriptor == -1)
    {
        return;
    }
 
    tcflush(self.file_descriptor, TCIOFLUSH);
}

- (NSString *)lastError {
    return _lastError ?: @"";
}

- (void)eraseLastError {
    _lastError = nil;
}
@end

ViewController.swift:
Code:
//
//  ViewController.swift
//  iOUSB
//
//  Created by Brandon on 2018-05-20.
//  Copyright © 2018 XIO. All rights reserved.
//

import UIKit
import Foundation
import ExternalAccessory

class ViewController: UIViewController {
   
    private let tableView = UITableView(frame: .zero, style: .grouped)
    private let refreshButton = UIButton(type: .custom)
    private var deviceList = [String]()
    private var connectedDevice: String = ""

    override func viewDidLoad() {
        super.viewDidLoad()
       
        title = "Serial Devices"
       
        //Setup UI
        setupUI()
       
        //Register for Accessory Notifications
        NotificationCenter.default.addObserver(self, selector: #selector(onAccessoryConnected(_:)), name: NSNotification.Name.EAAccessoryDidConnect, object: nil)
       
        NotificationCenter.default.addObserver(self, selector: #selector(onAccessoryDisconnected(_:)), name: NSNotification.Name.EAAccessoryDidDisconnect, object: nil)
       
        EAAccessoryManager.shared().registerForLocalNotifications()
       
        //Setup Serial Communications
        listDevices()
    }
   
    deinit {
        //Unregister for Accessory Notifications
        EAAccessoryManager.shared().unregisterForLocalNotifications()
        NotificationCenter.default.removeObserver(self)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    //Accessory Notification Callbacks
    @objc
    private func onAccessoryConnected(_ notification: NSNotification) {
        print("Device Connected")
       
        EAAccessoryManager.shared().connectedAccessories.forEach({
            print("Manufacturer: \($0.manufacturer)")
            print("Name: \($0.name)")
            print("Model Number: \($0.modelNumber)")
            print("Serial Number: \($0.serialNumber)")
            print("Hardware Revision: \($0.hardwareRevision)")
            print("Firmware Revision: \($0.firmwareRevision)")
            print("Protocol Strings: \($0.protocolStrings)")
        })
    }
   
    @objc
    private func onAccessoryDisconnected(_ notification: NSNotification) {
        print("Device Disconnected")
    }
}

extension ViewController {
    func setupUI() {
        view.backgroundColor = .white
       
        let footerView = { () -> UIView in
            let view = UIView()
            view.backgroundColor = UIColor(white: 0.0, alpha: 0.15)
            view.addSubview(refreshButton)
            refreshButton.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                refreshButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15.0),
                refreshButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15.0),
                refreshButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 15.0),
                refreshButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15.0),
                refreshButton.heightAnchor.constraint(equalToConstant: 45.0)
            ])
           
            return view
        }()
       
        refreshButton.layer.cornerRadius = 5.0
        refreshButton.backgroundColor = .blue
        refreshButton.setTitleColor(.white, for: .normal)
        refreshButton.setTitle("Reload", for: .normal)
        refreshButton.addTarget(self, action: #selector(onReload(_:)), for: .touchUpInside)
       
        view.addSubview(tableView)
        view.addSubview(footerView)
        tableView.delegate = self
        tableView.dataSource = self
       
        if #available(iOS 11.0, *) {
            NSLayoutConstraint.activate([
                tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
                tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
                tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
               
                footerView.leftAnchor.constraint(equalTo: view.leftAnchor),
                footerView.rightAnchor.constraint(equalTo: view.rightAnchor),
                footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor),
                footerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
            ])
        }
        else {
            NSLayoutConstraint.activate([
                tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
                tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
                tableView.topAnchor.constraint(equalTo: view.topAnchor),
               
                footerView.leftAnchor.constraint(equalTo: view.leftAnchor),
                footerView.rightAnchor.constraint(equalTo: view.rightAnchor),
                footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor),
                footerView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            ])
        }
       
        view.subviews.forEach({ $0.translatesAutoresizingMaskIntoConstraints = false })
        tableView.register(DeviceCell.self, forCellReuseIdentifier: "DeviceCell")
    }
   
    func listDevices() {
        let fileManager = FileManager.default
        if let pathURL = URL(string: "/dev") {
            do {
                let fileURLs = try fileManager.contentsOfDirectory(at: pathURL, includingPropertiesForKeys: nil)
                deviceList = fileURLs.map({ $0.path }).sorted()
                tableView.reloadData()
            } catch {
                print("Error while enumerating files \(pathURL): \(error.localizedDescription)")
            }
        }
        else {
            print("Error opening path: /dev")
        }
    }
   
    @objc
    private func onReload(_ button: UIButton) {
        listDevices()
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
   
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return deviceList.count
    }
   
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "DeviceCell", for: indexPath) as! DeviceCell
       
        cell.configure(title: deviceList[indexPath.row]) { [weak self] in
           
            if let `self` = self {
                if self.deviceList.contains(self.connectedDevice) {
                    if Serial.shared().disconnect() {
                        self.connectedDevice = ""
                        cell.actionButton.setTitle("Connect", for: .normal)
                        cell.actionButton.backgroundColor = .blue
                        print("Disconnected!")
                    }
                }
                else {
                    if (Serial.shared().setup(self.deviceList[indexPath.row])) {
                        self.connectedDevice = self.deviceList[indexPath.row]
                        cell.actionButton.setTitle("Disconnect", for: .normal)
                        cell.actionButton.backgroundColor = .green
                        print("Connected!")
                    }
                    else {
                        let alert = UIAlertController(title: "Error", message: "Error Connecting: \(Serial.shared().lastError() ?? "UnknownError").", preferredStyle: .alert)
                        alert.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                        Serial.shared().eraseLastError()
                    }
                }
            }
        }
       
        if self.deviceList.contains(self.connectedDevice) {
            cell.actionButton.setTitle("Disconnect", for: .normal)
            cell.actionButton.backgroundColor = .green
        }
        else {
            cell.actionButton.setTitle("Connect", for: .normal)
            cell.actionButton.backgroundColor = .blue
        }
        return cell
    }
   
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }
   
    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100.0
    }
   
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return .leastNormalMagnitude
    }
   
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return .leastNormalMagnitude
    }
}

class DeviceCell: UITableViewCell {
    private let titleView = UILabel()
    let actionButton = UIButton(type: .custom)
    private var eventHandler: (() -> Void)?
   
    func configure(title: String, eventHandler: @escaping () -> Void) {
        titleView.text = title
        self.eventHandler = eventHandler
    }
   
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
       
        selectionStyle = .none
       
        actionButton.setTitle("Connect", for: .normal)
        actionButton.setTitleColor(.white, for: .normal)
        actionButton.contentEdgeInsets = UIEdgeInsets(top: 5.0, left: 15.0, bottom: 5.0, right: 15.0)
        actionButton.layer.cornerRadius = 5.0
        actionButton.backgroundColor = .blue
       
        contentView.addSubview(titleView)
        contentView.addSubview(actionButton)
       
        NSLayoutConstraint.activate([
            titleView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 15.0),
            titleView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 15.0),
            titleView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -15.0),
           
            actionButton.leftAnchor.constraint(greaterThanOrEqualTo: titleView.rightAnchor, constant: 15.0),
            actionButton.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -15.0),
            actionButton.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 15.0),
            actionButton.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -15.0),
            actionButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ])
       
        contentView.subviews.forEach({ $0.translatesAutoresizingMaskIntoConstraints = false })
       
        actionButton.addTarget(self, action: #selector(onActionPerformed(_:)), for: .touchUpInside)
    }
   
    @objc
    private func onActionPerformed(_ button: UIButton) {
        self.eventHandler?()
    }
   
    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


Screenshots:
https://i.imgur.com/GgTFV3f.png
https://i.imgur.com/PzM5v9U.png
https://i.imgur.com/Wy32uW5.png
https://i.imgur.com/4coSFep.png


If someone knows which of the above ports the switch would be on, I'd gladly connect and write something to upload the payload as I can communicate with any device.. I just don't know enough to continue atm (yet)..

Technically it should show up as TTYUSB-Serial-XXXXX right? Similar to how Linux and OSX sees usb devices.. but I never really saw that so.. not sure..

Notes: I didn't put the switch in RCM.
it didnt connect because the switch wasnt in RCM. ill have to try this code. could you make a github with your xcode project?
 
  • Like
Reactions: NiftyBeard
Do you think it's possible that you could load payloads to the switch using an iOS device like nxloader? (or has it been done before and I'm just blind?) I'm fairly sure that the iPhone would recognize it like any other device (all of this assuming you have a lightning cable to usb c adapter), but I'm not sure if you could access it with an app. Is it possible?

Hi good question there. But I think you should really get an android phone to freely inject your payloads. Although I have a Note 5 and a Tab S2, they are unfortunately not compatible. My iPhone 5S is not much used nowadays, so I just went for a new android device (J5 Prime) and I can say I am a peace now.

I was going to wait for the app to be updated to support other non-compatible devices but the rate of homebrew development made me give in.
 
Hi good question there. But I think you should really get an android phone to freely inject your payloads. Although I have a Note 5 and a Tab S2, they are unfortunately not compatible. My iPhone 5S is not much used nowadays, so I just went for a new android device (J5 Prime) and I can say I am a peace now.

I was going to wait for the app to be updated to support other non-compatible devices but the rate of homebrew development made me give in.
As i've stated numerous times, I didn't ask the question because of practicality, I asked it just to see if it's possible, and just to expand the range of devices you can use in the rare case that someone has no other options.
 
So I got it to detect the device:

IMG_0006.PNG

APX is the Nintendo Switch.. Shows up as unsupported accessory atm.. Now that it's detected, I might be able to connect via EASession (so far no luck but I think I'm missing something)..

I am literally getting no love with TTY connection though (Serial Port).
 

Site & Scene News

Popular threads in this forum