Browse Source

RC_1, added the files for the on device payload installer, lsdtrip and related source to manually copy over outside of the bootstrap to launch nitoTV after installation, needs updated sha1sums after bootstraps are updated then is pretty much release ready.

Kevin Bradley 6 years ago
parent
commit
578ed2f259

File diff suppressed because it is too large
+ 3813 - 0
nitoTV4Installer.xcodeproj/project.pbxproj


+ 59 - 37
nitoTV4Installer/AppDelegate.m

@@ -90,7 +90,8 @@ static NSString *appleTVAddress = nil;
 
 - (void)downloadFile:(NSString *)thePayload toLocation:(NSString *)downloadLocation
 {
-    statusLabel.stringValue = [NSString stringWithFormat:@"Downloading: %@...", thePayload.lastPathComponent];
+    //statusLabel.stringValue = [NSString stringWithFormat:@"Downloading: %@...", thePayload.lastPathComponent];
+    statusLabel.stringValue = @"Downloading payload...";
     
     //get the stream we want to download
     
@@ -175,52 +176,58 @@ static NSString *appleTVAddress = nil;
             
             switch (button) {
                     
-                    case NSAlertAlternateReturn:
+                case NSAlertAlternateReturn:
                     
-                        [self handleDownloadFile:downloadLocation];
+                    [self handleDownloadFile:downloadLocation];
                     break;
                     
-                    case NSAlertOtherReturn:
+                case NSAlertOtherReturn:
                     
-                        [self downloadFile:thePayload toLocation:downloadLocation];
+                    [self downloadFile:thePayload toLocation:downloadLocation];
                     break;
                     
-                    case NSAlertDefaultReturn:
+                case NSAlertDefaultReturn:
                     self.mainButton.enabled = YES;
                     break;
                     
                 default:
                     break;
             }
-        
-        
+            
+            
         } else {
-             [self handleDownloadFile:downloadLocation];
+            [self handleDownloadFile:downloadLocation];
+           // [self downloadFile:thePayload toLocation:downloadLocation];
         }
         
         /*
-       if ([self verifyChecksum:hash onFile:downloadLocation])
-       {
-           NSLog(@"file validated!");
-           
-           [self handleDownloadFile:downloadLocation];
-           return;
-           //completion(true);
-           
-       }
+         if ([self verifyChecksum:hash onFile:downloadLocation])
+         {
+         NSLog(@"file validated!");
+         
+         [self handleDownloadFile:downloadLocation];
+         return;
+         //completion(true);
+         
+         }
          */
+    } else {
+         [self downloadFile:thePayload toLocation:downloadLocation];
     }
-
+    
     
 }
 
 - (void)handleDownloadFile:(NSString *)downloadedFile
 {
     [self setDownloadProgress:0];
-    statusLabel.stringValue = [NSString stringWithFormat:@"Uploading file %@....", downloadedFile.lastPathComponent];
-    
+    //statusLabel.stringValue = [NSString stringWithFormat:@"Uploading file %@....", downloadedFile.lastPathComponent];
+    statusLabel.stringValue = @"Uploading payload...";
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
         
+        NSString *lsmPath = [[NSBundle mainBundle] pathForResource:@"lsdtrip" ofType:nil];
+        
+        [self uploadFile:lsmPath toPath:@"/usr/local/bin/"];
         
         [self uploadFile:downloadedFile withCompletion:^(BOOL success) {
             
@@ -236,7 +243,7 @@ static NSString *appleTVAddress = nil;
                     
                     NSString *installCommand = [NSString stringWithFormat:@"%@ fxpv %@ -C / ; /usr/libexec/substrate ; /usr/bin/uicache  ; /bin/bash /usr/libexec/nito/firmware.sh ; rm /var/mobile/Library/Preferences/Featured.plist", tarString, bootstrap];
                     
-                   
+                    
                     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
                         
                         NSString *returnString = [self sendCommandString:installCommand];
@@ -250,15 +257,24 @@ static NSString *appleTVAddress = nil;
                             [self sendCommandString:@"/usr/bin/killall -9 PineBoard HeadBoard lsd nitoTV"];
                         }
                         
+                        
+                        
                         dispatch_async(dispatch_get_main_queue(), ^{
-                            [[NSWorkspace sharedWorkspace] openFile:logFile];
+                            // [[NSWorkspace sharedWorkspace] openFile:logFile];
                             
-                            statusLabel.stringValue = @"Payload installed?!?!";
+                            statusLabel.stringValue = @"Waiting for relaunch...";
                             
+                            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+                                
+                                [self.progressInd stopAnimation:nil];
+                                self.mainButton.enabled = TRUE;
+                                [self sendCommandString:@"/usr/local/bin/lsdtrip launch com.nito.nitoTV4"];
+                                 statusLabel.stringValue = @"nitoTV launched?!??!";
+                            });
                             
-                            [self.progressInd stopAnimation:nil];
-                            self.mainButton.enabled = TRUE;
                         });
+                        
+                        
                     });
                     
                 } else {
@@ -271,10 +287,15 @@ static NSString *appleTVAddress = nil;
             
         }];
         
+        
+        
+        
+        
+        
     });
     
-   
-   
+    
+    
 }
 
 - (void)hideProgress
@@ -313,7 +334,7 @@ static NSString *appleTVAddress = nil;
 - (void)setStatusText:(NSString *)statusText
 {
     dispatch_async(dispatch_get_main_queue(), ^{
-       
+        
         statusLabel.stringValue = statusText;
     });
 }
@@ -381,7 +402,7 @@ static NSString *appleTVAddress = nil;
             
         case KBInstallVersionStateEleven:
             
-             path= @"/jb/bin/ls";
+            path= @"/jb/bin/ls";
             break;
         default:
             break;
@@ -545,7 +566,8 @@ static NSString *appleTVAddress = nil;
 
 - (void)uploadFile:(NSString *)theFile withCompletion:(void(^)(BOOL success))completion
 {
-    NSLog(@"uploading file: %@", theFile);
+    NSLog(@"Uploading payload...");
+    //NSLog(@"uploading file: %@", theFile);
     NSError *error = nil;
     BOOL getSession = [self connectToSSH];
     if (getSession == FALSE)
@@ -715,7 +737,7 @@ static NSString *appleTVAddress = nil;
             NSLog(@"11 payload!");
             bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap11.tar"];
         }
-            
+        
     } else {
         
         swVers = self.deviceDict[@"srcvers"];
@@ -743,15 +765,15 @@ static NSString *appleTVAddress = nil;
     }
     if ([self connectToSSH])
     {
-       // if ([self checkForFile:@"/var/root/.bootstrapped"]){
-            
-          //  NSLog(@"were already bootstrapped!");
+        // if ([self checkForFile:@"/var/root/.bootstrapped"]){
+        
+        //  NSLog(@"were already bootstrapped!");
         //} else {
         
         [self processFile:bootstrapPath];
         // [self downloadFile:bootstrapPath];
-       // }
-       //
+        // }
+        //
         //[self downloadSyslogAndShow:YES];
     } else {
         self.mainButton.enabled = TRUE;

+ 1 - 0
nitoTV4Installer/KBDownloadFile.m

@@ -6,6 +6,7 @@
 //  Copyright 2007 nito, LLC. All rights reserved.
 //
 
+#import <Foundation/Foundation.h>
 /*
  
  class adapted from hawkeye's KBYTDownloadStream class for downloading youtube files, largely pruned to remove irrelevant sections + updated to cancel the xfer + remodified/updated to use blocks instead of antiquated delegate methods.

BIN
nitoTV4Installer/lsdtrip


+ 28 - 0
nitoTV4Installer/standalone/Download/URLCredential.h

@@ -0,0 +1,28 @@
+//
+//  Credentials.h
+//  iOS-URLDownloader
+//
+//  Created by Kristijan Sedlak on 7/21/11.
+//  Copyright 2011 AppStrides. All rights reserved.
+//
+
+#define OurLog(format, ...) CFShow((__bridge CFStringRef)[NSString stringWithFormat:format, ## __VA_ARGS__]);
+
+#import <Foundation/Foundation.h>
+
+
+@interface URLCredential : NSObject
+{
+    NSString *username;
+    NSString *password;
+    NSURLCredentialPersistence persistance;
+}
+
+@property(retain) NSString *username;
+@property(retain) NSString *password;
+@property(assign) NSURLCredentialPersistence persistance;
+
++ (id)credentialWithUsername:(NSString *)user andPassword:(NSString *)pass;
+- (id)initWithDefaults;
+
+@end

+ 51 - 0
nitoTV4Installer/standalone/Download/URLCredential.m

@@ -0,0 +1,51 @@
+//
+//  Credentials.m
+//  iOS-URLDownloader
+//
+//  Created by Kristijan Sedlak on 7/21/11.
+//  Copyright 2011 AppStrides. All rights reserved.
+//
+
+#import "URLCredential.h"
+
+
+#pragma mark -
+
+@implementation URLCredential
+
+@synthesize username;
+@synthesize password;
+@synthesize persistance;
+
+#pragma mark General
+
+- (void)dealloc 
+{
+	//[username release];
+	//[password release];
+    
+    //[super dealloc];
+}
+
+- (id)initWithDefaults
+{
+	if(self == [super init])
+	{
+		self.username = nil;
+		self.password = nil;
+        self.persistance = NSURLCredentialPersistenceForSession;
+	}
+	
+	return self;
+}
+
++ (id)credentialWithUsername:(NSString *)user andPassword:(NSString *)pass
+{
+    URLCredential *credential = [[URLCredential alloc] initWithDefaults];
+    credential.username = user;
+    credential.password = pass;
+
+    return credential;
+}
+
+@end

+ 85 - 0
nitoTV4Installer/standalone/Download/URLDownloader.h

@@ -0,0 +1,85 @@
+//
+//  URLDownloader.h
+//  iOS-URLDownloader
+//
+//  Created by Kristijan Sedlak on 7/21/11.
+//  Copyright 2011 AppStrides. All rights reserved.
+//
+
+
+
+#import <Foundation/Foundation.h>
+#import "URLCredential.h"
+
+@class URLDownloader;
+
+
+#pragma mark -
+
+typedef enum
+{
+	URLDownloaderStateInactive = 0,
+	URLDownloaderStateConnecting = 1,
+	URLDownloaderStateAuthenticating = 2,
+	URLDownloaderStateStarted = 3,
+	URLDownloaderStateDownloading = 4,
+	URLDownloaderStateFinished = 5,
+	URLDownloaderStateCanceled = 6
+} 
+URLDownloaderState;
+
+
+#pragma mark -
+
+@protocol URLDownloaderDelegate <NSObject>
+
+@required
+- (void)urlDownloader:(URLDownloader *)urlDownloader didFinishWithData:(NSData *)data;
+- (void)urlDownloader:(URLDownloader *)urlDownloader didFailOnAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
+- (void)urlDownloader:(URLDownloader *)urlDownloader didFailWithError:(NSError *)error;
+- (void)urlDownloader:(URLDownloader *)urlDownloader didFailWithNotConnectedToInternetError:(NSError *)error;
+
+@optional
+- (void)urlDownloaderDidStart:(URLDownloader *)urlDownloader;
+- (void)urlDownloaderDidCancelDownloading:(URLDownloader *)urlDownloader;
+- (void)urlDownloader:(URLDownloader *)urlDownloader didReceiveData:(NSData *)data;
+- (void)urlDownloader:(URLDownloader *)urlDownloader didChangeStateTo:(URLDownloaderState)state;
+
+@end
+
+
+#pragma mark -
+
+@interface URLDownloader : NSObject
+{
+    id <URLDownloaderDelegate> delegate;
+
+	NSURLConnection *urlConnection;
+    NSURLResponse *urlResponse;
+	NSMutableData *urlData;
+    URLCredential *urlCredential;
+    URLDownloaderState state;
+    float						bytesReceived;
+}
+
+@property(retain) id <URLDownloaderDelegate> delegate;
+@property(nonatomic, readonly) URLDownloaderState state;
+@property (strong, atomic) void (^CompletedBlock)(NSString *downloadedFile);
+
+typedef void(^DownloadCompletedBlock)(NSString *downloadedFile);
+@property (nonatomic, retain) NSString *downloadLocation;
+
+- (void)downloadFileWithURL:(NSURL *)url
+                 toLocation:(NSString *)dlLocation
+             withCredential:(URLCredential *)credential
+                  completed:(DownloadCompletedBlock)completedBlock;
+
++ (id)downloaderWithDelegate:(id)obj;
+- (id)initWithDelegate:(id)obj;
+- (void)download:(NSURLRequest *)request withCredential:(URLCredential *)credential;
+- (void)cancel;
+- (int)fullContentSize;
+- (int)downloadedContentSize;
+- (float)downloadCompleteProcent;
+
+@end

+ 258 - 0
nitoTV4Installer/standalone/Download/URLDownloader.m

@@ -0,0 +1,258 @@
+//
+//  Downloader.m
+//  iOS-URLDownloader
+//
+//  Created by Kristijan Sedlak on 7/21/11.
+//  Copyright 2011 AppStrides. All rights reserved.
+//
+
+#import "URLDownloader.h"
+#import <UIKit/UIKit.h>
+
+#pragma mark -
+
+@interface URLDownloader()
+
+@property(retain) NSURLConnection *urlConnection;
+@property(retain) NSURLResponse *urlResponse;
+@property(retain) NSMutableData *urlData;
+@property(retain) URLCredential *urlCredential;
+
+@end
+
+
+#pragma mark -
+
+@implementation URLDownloader
+
+@synthesize delegate;
+@synthesize state;
+
+@synthesize urlConnection;
+@synthesize urlResponse;
+@synthesize urlData;
+@synthesize urlCredential;
+
+#pragma mark Setters
+
+- (void)setState:(URLDownloaderState)downloaderState
+{
+    if (downloaderState != state) 
+    {
+        state = downloaderState;
+        if ([self.delegate respondsToSelector:@selector(urlDownloader:didChangeStateTo:)])
+        {
+            [self.delegate urlDownloader:self didChangeStateTo:downloaderState];
+        }
+    }
+}
+
+#pragma mark General
+
+- (void)dealloc 
+{
+    [urlConnection cancel];
+
+}
+
+- (id)initWithDelegate:(id)obj
+{
+	if(self == [self init])
+	{
+		self.delegate = obj;
+        [self setState:URLDownloaderStateInactive];
+	}
+	return self;
+}
+
++ (id)downloaderWithDelegate:(id)obj
+{
+    return [[URLDownloader alloc] initWithDelegate:obj];
+}
+
+- (void)reset
+{
+}
+
+#pragma mark Actions
+- (void)downloadFileWithURL:(NSURL *)url
+                 toLocation:(NSString *)dlLocation
+             withCredential:(URLCredential *)credential
+                  completed:(DownloadCompletedBlock)completedBlock
+{
+    NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
+    self.urlCredential = credential;
+    self.urlResponse = nil;
+    self.urlData = [[NSMutableData alloc] init];
+    self.downloadLocation = dlLocation;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+    self.urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
+#pragma clang diagnostic pop
+    self.CompletedBlock = completedBlock;
+    [self.urlConnection start];
+    OurLog(@"[URLDownloader] Download started");
+}
+
+
+- (void)download:(NSURLRequest *)request withCredential:(URLCredential *)credential
+{
+    [self setState:URLDownloaderStateConnecting];
+    
+    self.urlCredential = credential;
+    self.urlResponse = nil;
+	self.urlData = [[NSMutableData alloc] init];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+	self.urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
+#pragma clang diagnostic pop
+
+    [self.urlConnection start];
+#if TARGET_OS_IOS
+    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
+#endif
+	OurLog(@"[URLDownloader] Download started");
+}
+
+- (void)cancel
+{
+
+	[urlConnection cancel];
+	
+	OurLog(@"[URLDownloader] Download canceled");
+    if ([self.delegate respondsToSelector:@selector(urlDownloaderDidCancelDownloading:)])
+    {
+        [self.delegate urlDownloaderDidCancelDownloading:self];
+    }
+    
+    [self setState:URLDownloaderStateCanceled];
+    if (self.CompletedBlock != nil){
+        self.CompletedBlock(nil);
+    }
+}
+
+#pragma mark Information
+
+- (int)fullContentSize
+{
+    @try 
+    {
+        return [[NSNumber numberWithLongLong:[urlResponse expectedContentLength]] intValue]; 
+    }
+    @catch (NSException * e) 
+    {
+        return 0;
+    }
+}
+
+- (int)downloadedContentSize
+{
+    @try 
+    {
+        return [[NSNumber numberWithInteger:[self.urlData length]] intValue];
+    }
+    @catch (NSException * e) 
+    {
+        return 0;
+    }
+}
+
+- (float)downloadCompleteProcent
+{
+    float contentSize = [self fullContentSize];
+    float downloadedSize = [self downloadedContentSize];
+
+    return contentSize > 0.0 ? downloadedSize / contentSize : 0.0;
+}
+
+#pragma mark Connection
+
+- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
+{
+    [self setState:URLDownloaderStateAuthenticating];
+    
+	if ([challenge previousFailureCount] == 0)
+	{
+		OurLog(@"[URLDownloader] Authentication challenge received");
+		
+		NSURLCredential *credential = [NSURLCredential credentialWithUser:self.urlCredential.username
+																 password:self.urlCredential.password
+															  persistence:self.urlCredential.persistance];
+		[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
+
+		OurLog(@"[URLDownloader] Credentials sent");
+	}
+	else
+	{
+   //     [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
+        
+		OurLog(@"[URLDownloader] Authentication failed");
+        [self.delegate urlDownloader:self didFailOnAuthenticationChallenge:challenge];
+	}
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
+{
+    self.urlResponse = response;
+    [self.urlData setLength:0]; // in case of 302
+
+    [self setState:URLDownloaderStateDownloading];
+    OurLog(@"[URLDownloader] Downloading %@ ...", [[response URL] absoluteString]);
+    if ([self.delegate respondsToSelector:@selector(urlDownloaderDidStart:)])
+    {
+        [self.delegate urlDownloaderDidStart:self];
+    }
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
+{
+    
+	[self.urlData appendData:data];
+    
+    if ([self.delegate respondsToSelector:@selector(urlDownloader:didReceiveData:)])
+    {
+        [self.delegate urlDownloader:self didReceiveData:data];
+    }
+
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
+{
+    
+    [self setState:URLDownloaderStateInactive];
+
+    OurLog(@"[URLDownloader] Error: %@, %ld", error, (long)[error code]);
+	switch ([error code])
+	{
+		case NSURLErrorNotConnectedToInternet:
+			[self.delegate urlDownloader:self didFailWithNotConnectedToInternetError:error];
+			break;
+		default:
+            [self.delegate urlDownloader:self didFailWithError:error];;
+			break;
+	}
+    if (self.CompletedBlock != nil){
+        self.CompletedBlock(nil);
+    }
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection
+{
+
+    OurLog(@"[URLDownloader] Download finished");
+
+    NSData *data = [NSData dataWithData:self.urlData];
+    
+    if ([self.delegate respondsToSelector:@selector(urlDownloader:didFinishWithData:)])
+    {
+        [self.delegate urlDownloader:self didFinishWithData:data];
+    }
+    [data writeToFile:[self downloadLocation] atomically:NO];
+    [self setState:URLDownloaderStateFinished];
+    if (self.CompletedBlock != nil){
+        self.CompletedBlock([self downloadLocation]);
+    }
+    
+}
+
+@end

+ 1 - 0
nitoTV4Installer/standalone/dpkg

@@ -0,0 +1 @@
+Subproject commit 76405be99d6f6b59e72b645c2a78544119849057

+ 774 - 0
nitoTV4Installer/standalone/ls.m

@@ -0,0 +1,774 @@
+#define ARM
+
+#include <objc/runtime.h>
+#include <libgen.h>
+#include <dlfcn.h>
+#include <CoreFoundation/CoreFoundation.h>
+#ifdef ARM
+#include <Foundation/Foundation.h> // NSObject
+#endif
+
+@interface LSApplicationWorkspaceRemoteObserver : NSObject
+-(id)localObservers;
+@end
+
+
+@interface LSApplicationWorkspace : NSObject
++ (id) defaultWorkspace;
+@property (readonly) LSApplicationWorkspaceRemoteObserver * remoteObserver;
+- (BOOL) _LSPrivateSyncWithMobileInstallation;
+@end
+
+@interface PBAppDepot : NSObject
++ (id)sharedInstance;
+@property(retain, nonatomic) NSMutableDictionary *internalAppState;
+- (id)_addAppStateForIdentifier:(id)arg1;
+- (void)_save;
+- (void)_setNeedsNotifyAppStateDidChange;
+@end
+
+
+
+//#import "KBHelperClass.h"
+
+//
+// LSDTrip: A simple tool to demonstrate LaunchServices in OS X *and* iOS
+// -------
+//
+// Jonathan Levin, http://NeWOSXBook.com  - @Morpheus______/@TechnoloGeeks
+//
+// License: free, but if you GitHub it or mod it, at least leave a mention.
+//          And maybe get the book when it's finally out :-)
+//
+// Compile: gcc ls.m -o lsdtrip -lobjc -framework Foundation
+//  or
+// gcc-iphone -DARM ls.m -o lsdtrip -lobjc -framework Foundation -framework MobileCoreServices
+//
+// (that "MobileCoreServices" is required for the dlopen(3) to work)
+//
+//
+// To run:
+// Usage: ls [apps|plugins|publicurls|privateurls] [-v]
+//            whohas _url_
+//	      types
+//            dump <<-- Try this
+//
+// Explanation:
+//
+//   Shows what are (IMHO) the most useful of the LaunchServices APIs, as exported by
+//   [Mobile]CoreServices. In iOS most of the good stuff isn't exported (t), instead wrapped
+//   by LSApplicationWorkSpace, LSApplicationProxy, and friends. Even though the straight LS
+//   and _LS calls are much nicer, I chose to use objective-C here in order to maintain
+//   write-once-run-everywhere, and demonstrate yet again just how OS X and iOS really are
+//   so similar.
+//
+// How this works:
+//
+//   Over rather nice XPC to lsd (10.11/9.0) and friends. Full explanation in MOXiI 2.
+//  (Chapter 5, "Promenade, A Tour of the OS X and iOS Frameworks", to be exact, where
+//  many more "private" frameworks are exposed). But since I'm running behind with it,
+//  I thought people would appreciate a hands-on preview :-) This is just one of the many
+//  open source examples I have planned.
+//
+//   PLEASE BE PATIENT. This should give you an idea of why MOXiI is delayed. At least I try
+//   to keep everyone updated and drop examples along the way. And there's plenty more where
+//   this came from. Just wait.
+//
+//
+// Improvements:
+//
+//   - Can be made into pure C (i.e. not objective-C, .m), but that would make
+//     the already ugly [] syntax even worse..
+//
+//   - There's a whole undocumented side to LaunchServices (even as an allegedly
+//     "public" framework that it is) one can add here. And I left the other
+//     methods out in an #if 0 block, for future use.
+//
+//   - The UUIDs aren't actually CFUUIDs. They're some $%#$%#$ NS...UUID, which
+//     isn't compatible, so the UUIDs come with some 0s at the end. Meh.
+//
+
+// Ok. Let's begin:
+
+// My own prototypes and globals:
+
+CFStringRef dumpApp (NSObject *AppRef, int Verbose) ;
+
+NSObject * workspace; // Intentionally void * so as to NOT involve @interface files
+
+// OS X and iOS APIs are virtually identical, but the framework name is different
+#ifdef ARM
+#define  CORE_SERVICE_FRAMEWORK  "/System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices"
+#else
+#define  CORE_SERVICE_FRAMEWORK  "/System/Library/Frameworks/CoreServices.framework/CoreServices"
+#endif
+
+
+CFStringRef
+serializeCFArrayToCFString (CFArrayRef Array, CFStringRef Delimiter)
+{
+    
+    if (!Array) { return (CFSTR("(null)"));}
+    CFMutableStringRef returned = CFStringCreateMutable(kCFAllocatorDefault, // CFAllocatorRef alloc,
+                                                        4096); //CFIndex maxLength);
+    
+    
+    int len = CFArrayGetCount(Array);
+    int i = 0;
+    for (i = 0; i < len ; i++)
+        
+    {
+        CFTypeRef val = (CFTypeRef) CFArrayGetValueAtIndex(Array, i);
+        
+        if (i > 0) CFStringAppend(returned, Delimiter);
+        
+        // UGLY, I know. But PoC, people. PoC
+        if (CFGetTypeID(val) == CFStringGetTypeID()) CFStringAppend(returned, val);
+        
+        else
+            if (CFGetTypeID(val) == CFUUIDGetTypeID()){
+                CFStringRef UUID = CFUUIDCreateString(kCFAllocatorDefault, val);
+                CFStringAppend(returned, UUID);
+                
+            }
+            else {
+                CFStringAppend(returned, dumpApp (val, 0));
+            };
+    }
+    
+    
+    return (returned);
+    
+    
+} // serializeCFArrayToCFstring
+
+
+CFStringRef
+dumpApp (NSObject *AppRef, int Verbose)
+{
+    // App is an LSApplicationProxy object
+    
+    CFStringRef appID = (CFStringRef) [AppRef performSelector:@selector( applicationIdentifier)];
+    CFStringRef appName =  (CFStringRef)[AppRef performSelector:@selector(localizedName)];
+    
+    if (!appName) { appName = CFSTR("Not Set");}
+    CFMutableStringRef out = CFStringCreateMutable(kCFAllocatorDefault, // CFAllocatorRef alloc,
+                                                   4096); //CFIndex maxLength);
+    CFStringAppendFormat(out, // CFMutableStringRef theString,
+                         NULL,   // CFDictionaryRef formatOptions,
+                         CFSTR("\t%@ (%@)\n"),
+                         appName, appID);
+    
+    
+#if 0
+    // Can also use objective-C to enumerate ivars..
+    unsigned int ivarCount;
+    Ivar *ivars = class_copyIvarList([AppRef class], &ivarCount);
+    
+    int i = 0;
+    for (i = 0; i < ivarCount; i++)
+    {
+        fprintf(stderr,"\t%s: \n" , ivar_getName(ivars[i]));
+        // etc.
+    }
+#endif
+    if (Verbose)
+    {
+        // Dump more
+        
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tExecutable: %@\n"),
+                             [AppRef performSelector:@selector(bundleExecutable)]);
+        
+#ifdef ARM
+        // Set on iOS. Can also use versionIdentifier here, but that requires working back from the
+        // number to a version string (which LaunchServices lets you do with
+        // LSVersionNumberCopyStringRepresentation/LSVersionNumberGetXXX()
+        // But why bother when you have a short version string..
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tVersion: %@\n"),
+                             (CFStringRef)[AppRef performSelector:@selector(shortVersionString)]);
+        
+#endif
+        
+        // This is apparently unset..
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tVendor Name: %@\n"),
+                             (CFStringRef)[AppRef performSelector:@selector(vendorName)]);
+        
+        
+        
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tMach-O UUIDs: %@\n"),
+                             serializeCFArrayToCFString((CFArrayRef)[AppRef performSelector:@selector(machOUUIDs)], CFSTR(",")));
+        
+        
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tDisk Usage (Static): %@\n"),
+                             (CFStringRef)[AppRef performSelector:@selector(staticDiskUsage)]);
+        
+        
+#if 0
+        // This apparently doesn't work in 9.2 anymore. Not sure about this..
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tDisk Usage (Dynamic): %@\n"),
+                             (CFStringRef)[AppRef performSelector:@selector(dynamicDiskUsage)]);
+        
+#endif
+        
+        CFArrayRef UIBackgroundModes = (CFArrayRef) [AppRef performSelector: @selector(UIBackgroundModes)];
+        
+        
+        // This is a CFArray
+        if (!CFArrayGetCount(UIBackgroundModes)) {
+            CFStringAppend (out,CFSTR("\t\tno BackgroundModes"));
+        }
+        
+        else
+            CFStringAppendFormat(out, // CFMutableStringRef theString,
+                                 NULL,   // CFDictionaryRef formatOptions,
+                                 CFSTR("\t\t   BackgroundModes: %@"),
+                                 serializeCFArrayToCFString((CFArrayRef)UIBackgroundModes, CFSTR(",")));
+        
+        CFStringAppend(out, CFSTR("\n"));
+        
+#ifdef ARM
+        // Only on iOS
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tApplication DSID: %@\n"),
+                             (CFStringRef)[AppRef performSelector:@selector(applicationDSID)]);
+        
+        
+        
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tPurchaser DSID: %@\n"),
+                             (CFStringRef)[AppRef performSelector:@selector(purchaserDSID)]);
+        
+        
+        
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tDownloader DSID: %@\n"),
+                             (CFStringRef)[AppRef performSelector:@selector(downloaderDSID)]);
+#endif
+        
+        
+#if 0
+        uint64_t  modTime = (uint64_t)([AppRef performSelector:@selector( bundleModTime)]);
+        fprintf(stderr, "\t\tBundle Mod Time: %llu\n", modTime);
+#endif
+        
+        
+        int  cont = (int)([AppRef performSelector:@selector( isContainerized)]);
+        int  restricted  = (int)([AppRef performSelector:@selector( isRestricted)]);
+        
+        CFStringAppendFormat(out,
+                             NULL,
+                             CFSTR("\t\tContainerized: %@\n\t\tRestricted: %@\n"),
+                             (cont ? CFSTR("YES (q.v. App-Store Receipt URL for container)") : CFSTR("NO")),
+                             (restricted ? CFSTR("YES") : CFSTR("NO")));
+        
+        
+        
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tApp Store Receipt URL: %@\n"),
+                             (CFStringRef)[AppRef performSelector:@selector( appStoreReceiptURL)]);
+        
+        
+        // These are from LSBundleProxy, which is the parent of LSApplicationProxy
+        CFStringAppendFormat(out, // CFMutableStringRef theString,
+                             NULL,   // CFDictionaryRef formatOptions,
+                             CFSTR("\t\tContainer URL: %@\n"),
+                             [AppRef performSelector:@selector(containerURL)]);
+        
+        CFDictionaryRef entitlements = (CFDictionaryRef) [AppRef performSelector:@selector(entitlements)];
+        
+        if (entitlements &&  CFDictionaryGetCount(entitlements) )
+        {
+            
+            CFDataRef xml = CFPropertyListCreateXMLData(kCFAllocatorDefault,
+                                                        (CFPropertyListRef)entitlements);
+            
+            CFStringRef xmlAsString = CFStringCreateFromExternalRepresentation(NULL, xml, kCFStringEncodingUTF8);
+            
+            if (xmlAsString) {
+                CFStringAppendFormat(out, // CFMutableStringRef theString,
+                                     NULL,   // CFDictionaryRef formatOptions,
+                                     CFSTR("\t\tEntitlements:\n-----\n %@\n-----\n"),
+                                     xmlAsString);
+                
+            }
+            else { CFStringAppend (out, CFSTR("\t\tEntitlements: Internal error\n"));}
+            
+            
+        } // entitlements
+        else
+            CFStringAppend(out,CFSTR("\t\tEntitlements: None\n"));
+        
+    } // Verbose
+    
+    return (out);
+}
+
+
+/*
+ *
+ Soooo much more, courtesy of JTool:
+ 
+ -[LSApplicationProxy applicationIdentifier]:
+ -[LSApplicationProxy roleIdentifier]:
+ -[LSApplicationProxy bundleModTime]:
+ -[LSApplicationProxy registeredDate]:
+ -[LSApplicationProxy deviceFamily]:
+ -[LSApplicationProxy minimumSystemVersion]:
+ -[LSApplicationProxy sdkVersion]:
+ -[LSApplicationProxy applicationType]:
+ -[LSApplicationProxy itemName]:
+ -[LSApplicationProxy sourceAppIdentifier]:
+ -[LSApplicationProxy companionApplicationIdentifier]:
+ -[LSApplicationProxy applicationVariant]:
+ -[LSApplicationProxy storeCohortMetadata]:
+ -[LSApplicationProxy shortVersionString]:
+ -[LSApplicationProxy preferredArchitecture]:
+ -[LSApplicationProxy familyID]:
+ -[LSApplicationProxy groupContainers]:
+ -[LSApplicationProxy directionsModes]:
+ -[LSApplicationProxy UIBackgroundModes]:
+ -[LSApplicationProxy audioComponents]:
+ -[LSApplicationProxy externalAccessoryProtocols]:
+ -[LSApplicationProxy VPNPlugins]:
+ -[LSApplicationProxy plugInKitPlugins]:
+ -[LSApplicationProxy appTags]:
+ -[LSApplicationProxy requiredDeviceCapabilities]:
+ -[LSApplicationProxy deviceIdentifierForVendor]:
+ -[LSApplicationProxy ODRDiskUsage]:
+ -[LSApplicationProxy storeFront]:
+ -[LSApplicationProxy externalVersionIdentifier]:
+ -[LSApplicationProxy betaExternalVersionIdentifier]:
+ -[LSApplicationProxy appStoreReceiptURL]:
+ -[LSApplicationProxy installProgress]:
+ -[LSApplicationProxy installProgressSync]:
+ -[LSApplicationProxy resourcesDirectoryURL]:
+ -[LSApplicationProxy privateDocumentIconNames]:
+ -[LSApplicationProxy setPrivateDocumentIconNames:]:
+ -[LSApplicationProxy privateIconsDictionary]:
+ -[LSApplicationProxy privateDocumentIconAllowOverride]:
+ -[LSApplicationProxy setPrivateDocumentIconAllowOverride:]:
+ -[LSApplicationProxy iconDataForVariant:]:
+ -[LSApplicationProxy privateDocumentTypeOwner]:
+ -[LSApplicationProxy setPrivateDocumentTypeOwner:]:
+ -[LSApplicationProxy localizedName]:
+ -[LSApplicationProxy localizedShortName]:
+ -[LSApplicationProxy localizedNameForContext:]:
+ -[LSApplicationProxy iconIsPrerendered]:
+ -[LSApplicationProxy fileSharingEnabled]:
+ -[LSApplicationProxy profileValidated]:
+ -[LSApplicationProxy isAdHocCodeSigned]:
+ -[LSApplicationProxy isPlaceholder]:
+ -[LSApplicationProxy isAppUpdate]:
+ -[LSApplicationProxy isNewsstandApp]:
+ -[LSApplicationProxy isRestricted]:
+ -[LSApplicationProxy supportsAudiobooks]:
+ -[LSApplicationProxy supportsExternallyPlayableContent]:
+ -[LSApplicationProxy supportsOpenInPlace]:
+ -[LSApplicationProxy hasSettingsBundle]:
+ -[LSApplicationProxy isInstalled]:
+ -[LSApplicationProxy isWhitelisted]:
+ -[LSApplicationProxy isBetaApp]:
+ -[LSApplicationProxy isPurchasedReDownload]:
+ -[LSApplicationProxy isWatchKitApp]:
+ -[LSApplicationProxy hasMIDBasedSINF]:
+ -[LSApplicationProxy missingRequiredSINF]:
+ -[LSApplicationProxy isEqual:]:
+ -[LSApplicationProxy hash]:
+ -[LSApplicationProxy description]:
+ -[LSApplicationProxy iconStyleDomain]:
+ -[LSApplicationProxy userActivityStringForAdvertisementData:]:
+ -[LSApplicationProxy populateNotificationData]:
+ -[LSApplicationProxy itemID]:
+ -[LSApplicationProxy installType]:
+ -[LSApplicationProxy originalInstallType]:
+ -[LSApplicationProxy groupIdentifiers]:
+ -[LSApplicationProxy teamID]:
+ -[LSApplicationProxy isContainerized]:
+ */
+
+
+void
+dumpURL (CFStringRef URL, int Verbose)
+{
+    
+    CFMutableStringRef out = CFStringCreateMutable(kCFAllocatorDefault, // CFAllocatorRef alloc,
+                                                   4096); //CFIndex maxLength);
+    
+    CFStringAppend(out, URL);
+    if (Verbose) {
+        CFArrayRef appsForURL = (CFArrayRef) [workspace performSelector:@selector(applicationsAvailableForHandlingURLScheme:) withObject:(id)URL];
+        
+        // This is a CFArray
+        if (CFGetTypeID(appsForURL) != CFArrayGetTypeID())
+        {
+            fprintf(stderr, "Was expecting a CFArray of Apps!\n");
+            exit(2);
+        }
+        
+        if (!CFArrayGetCount (appsForURL)) CFStringAppend(out,CFSTR(" - Not claimed by anyone"));
+        else {
+            
+            CFStringRef apps = serializeCFArrayToCFString (appsForURL, CFSTR("\n\t\t\t"));
+            
+            CFStringAppend (out, apps);
+            CFRelease (apps);
+        }
+    }
+    
+    CFShow(out);
+    CFRelease(out);
+}
+void
+dumpPlugin (NSObject *PluginRef, int Verbose)
+{
+    
+    CFStringRef pluginName = (CFStringRef) [PluginRef performSelector:@selector( localizedName)];
+    CFStringRef pluginID = (CFStringRef) [PluginRef performSelector:@selector( pluginIdentifier)];
+    CFUUIDRef pluginUUID =  (CFUUIDRef)[PluginRef performSelector:@selector(pluginUUID)];
+    
+    CFStringRef out = CFStringCreateWithFormat(kCFAllocatorDefault,
+                                               NULL, CFSTR("\t%@ (%@) - %@"),
+                                               pluginName,pluginID, CFUUIDCreateString(kCFAllocatorDefault, pluginUUID));
+    
+    CFShow(out);
+    
+    
+} // end Dump
+
+int
+main (int argc, char **argv)
+{
+    
+    int verbose = 0;
+    
+    // So - why dup? because CFShow(), which I use extensively, write to stderr.
+    // And I really CANNOT stand the whole CFStringToCstr $%#$%#$ just to get a stupid
+    // printout! So instead, I save stderr, and then reopen stderr to stdout. This
+    // way, the output can be grep(1)-ed easily.
+    
+    dup2(2,3);
+    dup2(1,2);
+    
+    
+    if (argc < 2)
+    {
+        
+        fprintf(stderr, "Usage: %s [apps|plugins|publicurls|privateurls] [-v]\n", basename(argv[0]));
+        fprintf(stderr, "                app _bundle_id_ (implies -v for this app)\n");
+#ifdef ARM
+        fprintf(stderr, "                launch _bundle_id_\n");
+#endif
+        fprintf(stderr, "                whohas _url_ \n");
+        fprintf(stderr, "		 types\n");
+        fprintf(stderr, "                dump\n");
+        exit(0);
+    }
+    
+    if (argv[2] && strcmp(argv[2], "-v")==0) verbose++;
+    
+    // Getting the LS* classes and functions we need here:
+    // ---------------------------------------------------
+    Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
+    if (!LSApplicationWorkspace_class) {fprintf(stderr,"Unable to get Workspace class\n"); exit(1);}
+    workspace = [LSApplicationWorkspace_class performSelector:@selector (defaultWorkspace)];
+    if (!workspace) {fprintf(stderr,"Unable to get Workspace\n"); exit(2);}
+    
+    NSLog(@"workspace: %@", workspace);
+    LSApplicationWorkspaceRemoteObserver *observer = [workspace remoteObserver];
+    NSArray *localObservers = [observer localObservers];
+    NSLog(@"local observers: %@", localObservers);
+    
+    
+   // KBHelperClass *helperClass = [KBHelperClass new];
+    //[helperClass doMagic];
+    
+    void *CS_handle = dlopen (CORE_SERVICE_FRAMEWORK, RTLD_NOW);
+    if (!CS_handle) { fprintf(stderr,"Can't find %s!\n", CORE_SERVICE_FRAMEWORK); exit(2); };
+    
+    
+    if (strncmp(argv[1], "app",3) == 0)
+    {
+        
+        CFStringRef wantedBundleID = NULL;
+        
+        if (strcmp(argv[1], "apps")) // that is, not all apps
+        {
+            // expecting a bundle identifier here
+            
+            if (!argv[2]) { fprintf(stderr,"app option requires a specific bundle id. Try 'apps' to get a list\n");
+                exit(5); }
+            
+            wantedBundleID = CFStringCreateWithCString(kCFAllocatorDefault, // CFAllocatorRef alloc,
+                                                       argv[2],  // const char *cStr,
+                                                       kCFStringEncodingUTF8); //CFStringEncoding encoding);
+        }
+        
+        CFArrayRef apps =  (CFArrayRef) [workspace performSelector:@selector(allApplications)];
+        // This is a CFArray
+        if (CFGetTypeID(apps) != CFArrayGetTypeID())
+        {
+            fprintf(stderr, "Was expecting a CFArray of Apps!\n");
+            exit(2);
+        }
+        
+        int len = CFArrayGetCount(apps);
+        int i = 0;
+        for (i = 0; i < len ; i++)
+        {
+            CFTypeRef app = CFArrayGetValueAtIndex(apps,i);
+            
+            // Got app: Dump if want all, or if matches id.
+            // I'm sure there's some Workspace method I missed to get an app by bundle ID, instead
+            // of iterating over all of them..
+            
+            CFStringRef appID = (CFStringRef) [(id)app performSelector:@selector(applicationIdentifier)];
+            
+            if (appID && wantedBundleID)
+            {
+                if (CFEqual(appID, wantedBundleID))
+                {
+                    CFStringRef dump = dumpApp(app, 1);
+                    CFShow(dump);
+                    CFRelease(dump);
+                    exit(0); // only one match here.
+                }
+            }
+            else
+            {
+                CFStringRef dump = dumpApp(app, verbose);
+                CFShow(dump);
+                CFRelease(dump);
+                
+                
+            }
+        }
+        
+        if ( wantedBundleID) {
+            fprintf(stderr,"Application with Bundle ID %s not found. Try '%s apps' first, and remember case-sensitivity\n",
+                    argv[2], argv[0]);
+            exit(2);
+        }
+        
+        exit(0);
+    }
+    
+    if (strcmp(argv[1], "publicurls") == 0)
+    {
+        CFArrayRef puburls =  (CFArrayRef) [workspace performSelector:@selector(publicURLSchemes)];
+        
+        
+        // This is a CFArray
+        if (CFGetTypeID(puburls) != CFArrayGetTypeID())
+        {
+            fprintf(stderr, "Was expecting a CFArray of publicURLSchemes!\n");
+            exit(2);
+        }
+        
+        int len = CFArrayGetCount(puburls);
+        int i = 0;
+        for (i = 0; i < len ; i++)
+        {
+            CFStringRef url = CFArrayGetValueAtIndex(puburls,i);
+            dumpURL(url, verbose);
+            
+            
+            
+        }
+        exit(0);
+        
+    }
+    
+    if (strcmp(argv[1], "privateurls") == 0)
+    {
+        CFArrayRef (privurls) = (CFArrayRef) [workspace performSelector:@selector(privateURLSchemes)];
+        // This is a CFArray
+        if (CFGetTypeID(privurls) != CFArrayGetTypeID())
+        {
+            fprintf(stderr, "Was expecting a CFArray of privateURLSchemes!\n");
+            exit(2);
+        }
+        
+        int len = CFArrayGetCount(privurls);
+        int i = 0;
+        for (i = 0; i < len ; i++)
+        {
+            CFStringRef url = CFArrayGetValueAtIndex(privurls,i);
+            dumpURL(url, verbose);
+            
+            
+            
+        }
+        exit(0);
+        
+    }
+    
+    
+    if (strcmp(argv[1], "plugins") == 0)
+    {
+        CFArrayRef plugins = (CFArrayRef) [workspace performSelector:@selector(installedPlugins)];
+        // This, too, is a CFArray
+        if (CFGetTypeID(plugins) != CFArrayGetTypeID())
+        {
+            fprintf(stderr, "Was expecting a CFArray of plugins!\n");
+            exit(2);
+        }
+        
+        int len = CFArrayGetCount(plugins);
+        int i = 0;
+        for (i = 0; i < len ; i++)
+        {
+            CFTypeRef plugin = CFArrayGetValueAtIndex(plugins,i);
+            dumpPlugin(plugin, verbose);
+            
+            
+            
+        }
+        exit(0);
+    }
+    
+    if (strcmp(argv[1], "types") == 0)
+    {
+        // I resort to dlopen/dlsym here to make linking simpler. You don't have to.
+        
+        typedef CFArrayRef (*UTCopyDeclaredTypeIdentifiersFunc)(void);
+        
+        UTCopyDeclaredTypeIdentifiersFunc UTCopyDeclaredTypeIdentifiers 
+        = dlsym (CS_handle, "_UTCopyDeclaredTypeIdentifiers");
+        
+        CFArrayRef utis =  UTCopyDeclaredTypeIdentifiers();
+        
+        // As usual, this is an array..
+        int len = CFArrayGetCount(utis);
+        int i = 0;
+        for (i = 0; i < len ; i++)
+        {
+            // Seriously, AAPL - couldn't you find a better acronym?
+            CFTypeRef uti = CFArrayGetValueAtIndex(utis,i);
+            CFShow(uti);
+            
+            
+            
+        }
+        exit(0);
+        
+        
+        
+    }
+    
+    if (strcmp(argv[1], "whohas") == 0)
+    {
+        
+        if (!argv[2]) {
+            fprintf(stderr,"whohas: Option requires an URL Scheme or UTI as an argument!\n");
+            exit(3);
+        }
+        
+        
+        
+        CFStringRef url = CFStringCreateWithCString(kCFAllocatorDefault, // CFAllocatorRef alloc,
+                                                    argv[2],  // const char *cStr, 
+                                                    kCFStringEncodingUTF8); //CFStringEncoding encoding);
+        
+        
+        
+        dumpURL(url,1);
+        
+        
+        exit(0);
+    }
+    
+    
+    
+#if 0
+    // ignore this part for now
+    if (strcmp(argv[1],"claims") == 0)
+    {
+        
+        typedef CFStringRef (*LSCopyClaimedActivityIdentifiersAndDomainsFunc)(CFStringRef, CFStringRef);
+        
+        LSCopyClaimedActivityIdentifiersAndDomainsFunc 
+        LSCopyClaimedActivityIdentifiersAndDomains = dlsym (CS_handle, "_LSCopyClaimedActivityIdentifiersAndDomains");
+        
+        if (!LSCopyClaimedActivityIdentifiersAndDomains )
+        {
+            fprintf(stderr,"Unable to find LSCopyClaimedActivityIdentifiersAndDomains ");
+            exit(3);
+        }
+        
+        CFStringRef result = LSCopyClaimedActivityIdentifiersAndDomains (NULL, NULL);
+        CFShow(result);
+        exit(0);
+        
+        
+    }
+#endif
+    
+    
+    
+    if (strcmp(argv[1],"launch") == 0)
+    {
+        
+        
+        if (!argv[2]) {
+            fprintf(stderr,"launch: Option requires an argument!\n"); exit(3);
+        }
+        CFStringRef bundleID = CFStringCreateWithCString(kCFAllocatorDefault, // CFAllocatorRef alloc,
+                                                         argv[2],  // const char *cStr, 
+                                                         kCFStringEncodingUTF8); //CFStringEncoding encoding);
+        int t =  (int) [workspace performSelector:@selector(openApplicationWithBundleID:) withObject:(id) bundleID ];
+        
+        fprintf(stderr, "%s %s\n",
+                t ? "Launched" : "Unable to launch",
+                argv[2]);
+        
+        exit(0);
+        
+    }
+    
+    // And this is the real fun part:
+    //
+    // The little known "lsregister" utility in OS X is closed source, and un-man(1)ned,
+    // But one of its coolest features is "dump" - to dump the LaunchServices Database.
+    // Turns out this is a *single* line of code. And guess what -- it works on iOS too :-)
+    //
+    if (strcmp(argv[1], "dump") == 0)
+    {
+        
+        typedef void (*LSDisplayDataFunc)(char *);
+        LSDisplayDataFunc       LSDisplayData;
+        
+        // If you want to get the raw format of the file, you can dump with this, instead:
+        //extern _LSDisplayRawStoreData(char *, int);
+        //_LSDisplayRawStoreData("/var/mobile/Library/Caches/com.apple.LaunchServices-134.csstore", (void *) 0xff);
+        
+        
+        LSDisplayData = dlsym (CS_handle, "_LSDisplayData");
+        
+        if (!LSDisplayData) { fprintf(stderr, "Can't find LSDisplayData! Has Apple removed it by now? :-P"); exit(1);}
+        
+        // The argument expected here is the name of the CoreServices (LaunchServices)
+        // DataStore - in iOS "/var/mobile/Library/Caches/com.apple.LaunchServices-###.csstore"
+        // but turns out NULL works on both OS X and iOS!
+        
+        LSDisplayData("/private/var/containers/Data/System/97D6E4BA-C0BF-408B-AF63-1836844381AE/Library/Caches/com.apple.LaunchServices-175-v2.csstore");
+        exit(0);
+        
+    }
+    
+}

BIN
nitoTV4Installer/standalone/lsdtrip


BIN
nitoTV4Installer/standalone/lsm


+ 12 - 0
nitoTV4Installer/standalone/make.sh

@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -e
+
+SDKROOT=`xcrun -sdk appletvos --show-sdk-path`
+echo $SDKROOT
+xcrun -v -sdk appletvos clang -fobjc-arc -arch arm64 -IDownload -framework Foundation -framework MobileCoreServices -o nitoInstaller nitoInstaller.m Download/URLDownloader.m Download/URLCredential.m
+
+xcrun -v -sdk appletvos clang -arch arm64 ls.m -framework Foundation -framework MobileCoreServices -o lsdtrip
+
+ldid -S nitoInstaller
+ldid -S lsm

BIN
nitoTV4Installer/standalone/nitoInstaller


+ 26 - 0
nitoTV4Installer/standalone/nitoInstaller.h

@@ -0,0 +1,26 @@
+//
+//  nitoInstaller.h
+//  nitotvinstaller
+//
+//  Created by Kevin Bradley on 1/31/18.
+//  Copyright © 2018 Kevin Bradley. All rights reserved.
+//
+
+typedef enum
+{
+    InstallVersionStateNine,
+    InstallVersionStateTenOne,
+    InstallVersionStateTenTwo,
+    InstallVersionStateEleven
+}
+InstallVersionState;
+
+#import <Foundation/Foundation.h>
+#import "URLDownloader.h"
+
+@interface nitoInstaller : NSObject 
+
+@property (readwrite, assign) InstallVersionState versionState;
+@property (nonatomic, strong) URLDownloader *downloader;
+
+@end

+ 231 - 0
nitoTV4Installer/standalone/nitoInstaller.m

@@ -0,0 +1,231 @@
+//
+//  nitoInstaller.m
+//  nitotvinstaller
+//
+//  Created by Kevin Bradley on 1/31/18.
+//  Copyright © 2018 Kevin Bradley. All rights reserved.
+//
+
+#include <objc/runtime.h>
+#include <libgen.h>
+#include <dlfcn.h>
+#include <CoreFoundation/CoreFoundation.h>
+#ifdef ARM
+#include <Foundation/Foundation.h> // NSObject
+#endif
+#import "nitoInstaller.h"
+#include<stdio.h>
+//#include<conio.h>
+#include<string.h>
+
+#define  CORE_SERVICE_FRAMEWORK  "/System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices"
+
+NSObject * workspace; // Intentionally void * so as to NOT involve @interface files
+
+@interface LSApplicationWorkspace: NSObject
+
+- (id)allApplications;
+- (void)openApplicationWithBundleID:(NSString *)string;
+- (id)defaultWorkspace;
+
+
+@end
+
+void get_string(char *string);
+
+@implementation nitoInstaller
+
+- (NSString *)airPlayVersionNumber
+{
+    NSDictionary *infoDictionary = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/PrivateFrameworks/AirPlaySupport.framework/Info.plist"];
+    return infoDictionary[@"CFBundleVersion"];
+    
+}
+
++ (NSString *)getTextWithPrompt:(NSString *)thePrompt
+{
+    NSString *prompt = [NSString stringWithFormat:@"\n%@ ", thePrompt];
+    fprintf(stdout, "%s", [prompt UTF8String]);
+    
+    NSString *thestring = [NSString.alloc initWithData:
+                           [NSFileHandle.fileHandleWithStandardInput availableData]
+                                              encoding:NSUTF8StringEncoding];
+    
+    return [thestring stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+}
+
+- (NSString *)tarPathForCurrentMode
+{
+    NSString *path = nil;
+    switch (self.versionState) {
+        case InstallVersionStateNine:
+            
+            path= @"/usr/bin/tar";
+            break;
+            
+        case InstallVersionStateTenOne:
+            
+            path= @"/tmp/usr/bin/tar"; //need to verify
+            break;
+            
+        case InstallVersionStateTenTwo:
+            
+            path= @"/usr/bin/tar";
+            break;
+            
+        case InstallVersionStateEleven:
+            
+            path= @"/jb/usr/bin/tar";
+            break;
+        default:
+            break;
+    }
+    return path;
+}
+
+- (NSString *)bootstrapPathForVersion:(NSString *)swVers
+{
+    NSString *bootstrapPath = @"https://nitosoft.com/ATV4/bootstraps/";
+    {
+        OurLog(@"srcvers: %@", swVers);
+        if ([swVers compare:@"353.50" options:NSNumericSearch] != NSOrderedAscending)
+        {
+            OurLog(@"11.x or greater!");
+            self.versionState = InstallVersionStateEleven;
+            bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap11.tar"];
+        } else if([swVers compare:@"320.20.1" options:NSNumericSearch] != NSOrderedAscending)
+        {
+            OurLog(@"10.2.2 or greater!");
+            self.versionState = InstallVersionStateTenTwo;
+            bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap11.tar"];
+        } else if([swVers compare:@"301.44.3" options:NSNumericSearch] != NSOrderedAscending)
+        {
+            OurLog(@"10.0 or greater");
+            self.versionState = InstallVersionStateTenOne;
+            bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap9.tar"];
+        } else {
+            OurLog(@"9.0 or greater?");
+            self.versionState = InstallVersionStateNine;
+            bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap9.tar"];
+            
+        }
+        
+    }
+    return bootstrapPath;
+}
+
++ (int)returnForProcess:(NSString *)call
+{
+    if (call==nil)
+        return 0;
+    char line[200];
+    
+    FILE* fp = popen([call UTF8String], "r");
+    //NSMutableArray *lines = [[NSMutableArray alloc]init];
+    if (fp)
+    {
+        while (fgets(line, sizeof line, fp))
+        {
+            NSString *s = [NSString stringWithCString:line encoding:NSUTF8StringEncoding];
+            s = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+            OurLog(@"%@",s);
+            // [lines addObject:s];
+        }
+    }
+    int returnStatus = pclose(fp);
+    return returnStatus;
+}
+
+
+
+int main(int argc, const char * argv[]) {
+
+    NSString *runPath = [[NSFileManager defaultManager] currentDirectoryPath];
+    nitoInstaller *installer = [nitoInstaller new];
+    NSString *fileCheck = @"/var/lib/dpkg/status";
+    OurLog(@"\n\nWelcome to the nitoTV 1.0 & bootstrap installer script!");
+    OurLog(@"\nWe will detect your tvOS version and install the compatible bootstrap nitoTV, upon completion nitoTV should appear on your AppleTV\n\n");
+    if ([[NSFileManager defaultManager] fileExistsAtPath:fileCheck])
+    {
+        //char *prompt = (char *)malloc(200*sizeof(char));
+        char c;
+        printf("\nIt appears you have already installed nitoTV and the bootstrap, it is NOT recommended to do this again, are you sure you want to continue? [y/n]?");
+        c=getchar();
+        while(c!='y' && c!='n')
+        {
+            if (c!='\n'){
+                printf("[y/n]");
+            }
+            c=getchar();
+        }
+        //*prompt=c;
+        //OurLog(@"%@",[NSString stringWithUTF8String:science]);
+        
+        if (c == 'n')
+        {
+            OurLog(@"\nsmart move... exiting\n");
+            return 0;
+        } else if (c == 'y') {
+            OurLog(@"\nits your funeral....\n");
+        }
+    }
+
+    [installer installPackageInPath:runPath];
+    
+    CFRunLoopRun();
+    return 0;
+    
+}
+
+- (void)openApp:(NSString *)bundleID;
+{
+    Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");    if (!LSApplicationWorkspace_class) {fprintf(stderr,"Unable to get Workspace class\n"); }    workspace = [LSApplicationWorkspace_class performSelector:@selector (defaultWorkspace)];    if (!workspace) {fprintf(stderr,"Unable to get Workspace\n"); }
+    void *CS_handle = dlopen (CORE_SERVICE_FRAMEWORK, RTLD_NOW);    if (!CS_handle) {
+        fprintf(stderr,"Can't find %s!\n", CORE_SERVICE_FRAMEWORK);
+        return;
+    }
+    
+    if (workspace){
+        [workspace performSelector:@selector(openApplicationWithBundleID:) withObject:(id) bundleID ];
+    }
+    
+    
+}
+
+- (void)installPackageInPath:(NSString *)thePath
+{
+    NSString *bootstrapPath = [self bootstrapPathForVersion:[self airPlayVersionNumber]];
+    
+    NSURL *url = [NSURL URLWithString:bootstrapPath];
+    OurLog(@"Downloading file: %@", bootstrapPath);
+    NSString *downloadLocation = [thePath stringByAppendingPathComponent:bootstrapPath.lastPathComponent];
+    
+    
+    self.downloader = [URLDownloader new];
+    [self.downloader downloadFileWithURL:url toLocation:downloadLocation withCredential:nil completed:^(NSString *downloadedFile) {
+        OurLog(@"Download complete: %@", downloadedFile);
+        
+        OurLog(@"Installing...");
+        NSString *tarPath = [self tarPathForCurrentMode];
+        NSString *installString = [NSString stringWithFormat:@"%@ fxpv %@ -C / ; /usr/libexec/substrate ; /usr/bin/uicache  ; /bin/bash /usr/libexec/nito/firmware.sh ; rm /var/mobile/Library/Preferences/Featured.plist", tarPath, downloadedFile];
+        
+        OurLog(@"install string: %@", installString);
+        
+        
+        [nitoInstaller returnForProcess:installString];
+        if (self.versionState == InstallVersionStateNine)
+        {
+            [nitoInstaller returnForProcess:@"/usr/bin/killall -9 PineBoard HeadBoard lsd nitoTV"];
+        }
+        
+        
+        sleep(10);
+        
+        [self openApp:@"com.nito.nitoTV4"];
+        
+         CFRunLoopStop(CFRunLoopGetCurrent());
+    }];
+
+}
+
+@end