AppDelegate.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. //
  2. // AppDelegate.m
  3. // nitoTV4Installer
  4. //
  5. // Created by Kevin Bradley on 1/24/18.
  6. // Copyright © 2018 Kevin Bradley. All rights reserved.
  7. //
  8. #import "AppDelegate.h"
  9. #import "ATVDeviceController.h"
  10. @interface AppDelegate ()
  11. @property (weak) IBOutlet NSWindow *window;
  12. //@property (readwrite, assign) BOOL atvAvailable;
  13. //@property (nonatomic, strong) NSString *bundleID;
  14. @property (nonatomic, strong) IBOutlet ATVDeviceController *deviceController;
  15. @end
  16. @implementation AppDelegate
  17. @synthesize window,workingPath, sshSession, isSending, downloading, downloadFile;
  18. @synthesize deviceController;
  19. static NSString *appleTVAddress = nil;
  20. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  21. // Insert code here to initialize your application
  22. //deviceController = [[ATVDeviceController alloc] init];
  23. //variables that was a carry over from code i migrated this from, can probably be pruned, dont think i use it.
  24. appleTVAddress = APPLE_TV_ADDRESS;
  25. NSLog(@"SELECTED_VALUE: %@", SELECTED_VALUE);
  26. if (SELECTED_VALUE != nil)
  27. {
  28. [deviceController.theComboBox selectItemWithTitle:SELECTED_VALUE];
  29. [deviceController menuItemSelected:deviceController.theComboBox];
  30. }
  31. NSLog(@"appleTVAddress: %@", appleTVAddress);
  32. if ([[appleTVAddress componentsSeparatedByString:@":"] count] < 2)
  33. {
  34. [self resetServerSettings];
  35. }
  36. }
  37. - (NSString *)appSupportFolder
  38. {
  39. NSString *supportFolder = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/nitoDownload"];
  40. NSLog(@"dir: %@", supportFolder);
  41. if (![FM fileExistsAtPath:supportFolder])
  42. {
  43. [FM createDirectoryAtPath:supportFolder withIntermediateDirectories:YES attributes:nil error:nil];
  44. }
  45. return supportFolder;
  46. }
  47. + (NSArray *)returnForProcess:(NSString *)call
  48. {
  49. if (call==nil)
  50. return 0;
  51. char line[200];
  52. FILE* fp = popen([call UTF8String], "r");
  53. NSMutableArray *lines = [[NSMutableArray alloc]init];
  54. if (fp)
  55. {
  56. while (fgets(line, sizeof line, fp))
  57. {
  58. NSString *s = [NSString stringWithCString:line encoding:NSUTF8StringEncoding];
  59. s = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  60. [lines addObject:s];
  61. }
  62. }
  63. pclose(fp);
  64. return lines;
  65. }
  66. //59293ffbdee698488aba799a96fcfe95e1165fa10f760362a2ce910576134e39
  67. - (BOOL)verifyChecksum:(NSString *)checksum onFile:(NSString *)file
  68. {
  69. NSString *processReturn = [[AppDelegate returnForProcess:[NSString stringWithFormat:@"/usr/bin/shasum -a 256 \"%@\" | cut -c 1-64", file]] componentsJoinedByString:@"\n"];
  70. NSLog(@"return: -%@-", processReturn);
  71. return [processReturn isEqualToString:checksum];
  72. }
  73. - (void)downloadFile:(NSString *)thePayload toLocation:(NSString *)downloadLocation
  74. {
  75. //statusLabel.stringValue = [NSString stringWithFormat:@"Downloading: %@...", thePayload.lastPathComponent];
  76. statusLabel.stringValue = @"Downloading payload...";
  77. //get the stream we want to download
  78. [downloadFile downloadFileWithURL:[NSURL URLWithString:thePayload] toLocation:downloadLocation progress:^(double percentComplete) {
  79. [self setDownloadProgress:percentComplete];
  80. } completed:^(NSString *downloadedFile) {
  81. //[self hideProgress];
  82. self.downloading = false;
  83. [self setDownloadProgress:0];
  84. statusLabel.stringValue = [NSString stringWithFormat:@"Uploading file %@....", downloadedFile.lastPathComponent];
  85. [self handleDownloadFile:downloadedFile];
  86. //[self uploadFile:downloadedFile];
  87. }];
  88. }
  89. - (void)processFile:(NSString *)thePayload
  90. {
  91. //we're already downloading, cancel
  92. //TODO: make downloading NSOperation/NSOperationQueue based
  93. if (self.downloading == true)
  94. {
  95. [downloadFile cancel];
  96. self.downloading = false;
  97. statusLabel.stringValue = @"";
  98. [self.progressInd setDoubleValue:0];
  99. [self.progressInd setHidden:TRUE];
  100. self.mainButton.enabled = TRUE;
  101. return;
  102. }
  103. //create instance of downloader class
  104. downloadFile = [KBDownloadFile new];;
  105. //self.downloadButton.title = @"Cancel";
  106. self.downloading = true;
  107. NSString *downloadLocation = [[self appSupportFolder] stringByAppendingPathComponent:[thePayload lastPathComponent]];
  108. if ([FM fileExistsAtPath:downloadLocation])
  109. {
  110. NSString *hash = @"59293ffbdee698488aba799a96fcfe95e1165fa10f760362a2ce910576134e39";
  111. switch (self.versionState) {
  112. case KBInstallVersionStateEleven:
  113. case KBInstallVersionStateTenTwo:
  114. hash = @"59293ffbdee698488aba799a96fcfe95e1165fa10f760362a2ce910576134e39";
  115. break;
  116. case KBInstallVersionStateNine:
  117. case KBInstallVersionStateTenOne:
  118. hash = @"327c7cea49282109e23c9415d318121f91b79ca7bb813c8724a1c0752e8311af";
  119. break;
  120. default:
  121. break;
  122. }
  123. BOOL verify = [self verifyChecksum:hash onFile:downloadLocation];
  124. if (!verify)
  125. {
  126. NSLog(@"invalid selection");
  127. NSAlert *alert = [NSAlert alertWithMessageText:@"This payload does not match our checksum, if you did not modify the payload you should not proceed any further! Otherwise press continue."
  128. defaultButton:@"Cancel"
  129. alternateButton:@"Continue"
  130. otherButton:@"Download Again"
  131. informativeTextWithFormat:@""];
  132. alert.alertStyle = NSCriticalAlertStyle;
  133. NSInteger button = [alert runModal];
  134. switch (button) {
  135. case NSAlertAlternateReturn:
  136. [self handleDownloadFile:downloadLocation];
  137. break;
  138. case NSAlertOtherReturn:
  139. [self downloadFile:thePayload toLocation:downloadLocation];
  140. break;
  141. case NSAlertDefaultReturn:
  142. self.mainButton.enabled = YES;
  143. break;
  144. default:
  145. break;
  146. }
  147. } else {
  148. [self handleDownloadFile:downloadLocation];
  149. // [self downloadFile:thePayload toLocation:downloadLocation];
  150. }
  151. /*
  152. if ([self verifyChecksum:hash onFile:downloadLocation])
  153. {
  154. NSLog(@"file validated!");
  155. [self handleDownloadFile:downloadLocation];
  156. return;
  157. //completion(true);
  158. }
  159. */
  160. } else {
  161. [self downloadFile:thePayload toLocation:downloadLocation];
  162. }
  163. }
  164. - (void)handleDownloadFile:(NSString *)downloadedFile
  165. {
  166. [self setDownloadProgress:0];
  167. //statusLabel.stringValue = [NSString stringWithFormat:@"Uploading file %@....", downloadedFile.lastPathComponent];
  168. statusLabel.stringValue = @"Uploading payload...";
  169. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  170. NSString *lsmPath = [[NSBundle mainBundle] pathForResource:@"lsdtrip" ofType:nil];
  171. [self uploadFile:lsmPath toPath:@"/usr/local/bin/"];
  172. [self uploadFile:downloadedFile withCompletion:^(BOOL success) {
  173. dispatch_async(dispatch_get_main_queue(), ^{
  174. if (success)
  175. {
  176. NSLog(@"file uploaded successfully!");
  177. statusLabel.stringValue = @"File uploaded successfully!";
  178. statusLabel.stringValue = @"Installing payload...";
  179. NSString *tarString = [self tarPathForCurrentMode];
  180. NSString *bootstrap = [NSString stringWithFormat:@"/var/mobile/Documents/%@", downloadedFile.lastPathComponent];
  181. 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];
  182. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  183. NSString *returnString = [self sendCommandString:installCommand];
  184. NSLog(@"return string: %@", returnString);
  185. NSString *logFile = [[self appSupportFolder] stringByAppendingPathComponent:@"install.log"];
  186. [returnString writeToFile:logFile atomically:YES encoding:NSUTF8StringEncoding error:nil];
  187. if (self.versionState == KBInstallVersionStateNine)
  188. {
  189. [self sendCommandString:@"/usr/bin/killall -9 PineBoard HeadBoard lsd nitoTV"];
  190. }
  191. dispatch_async(dispatch_get_main_queue(), ^{
  192. // [[NSWorkspace sharedWorkspace] openFile:logFile];
  193. statusLabel.stringValue = @"Waiting for relaunch...";
  194. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
  195. [self.progressInd stopAnimation:nil];
  196. self.mainButton.enabled = TRUE;
  197. [self sendCommandString:@"/usr/local/bin/lsdtrip launch com.nito.nitoTV4"];
  198. statusLabel.stringValue = @"nitoTV launched?!??!";
  199. });
  200. });
  201. });
  202. } else {
  203. statusLabel.stringValue = @"File failed to upload!";
  204. [self.progressInd stopAnimation:nil];
  205. self.mainButton.enabled = TRUE;
  206. }
  207. });
  208. }];
  209. });
  210. }
  211. - (void)hideProgress
  212. {
  213. dispatch_async(dispatch_get_main_queue(), ^{
  214. statusLabel.stringValue = @"";
  215. [[self progressInd] stopAnimation:nil];
  216. [[self progressInd] setDoubleValue:0];
  217. [[self progressInd] setHidden:true];
  218. });
  219. }
  220. - (void)setDownloadProgress:(double)theProgress
  221. {
  222. dispatch_async(dispatch_get_main_queue(), ^{
  223. if (theProgress == 0)
  224. {
  225. [self.progressInd setIndeterminate:TRUE];
  226. [self.progressInd setHidden:FALSE];
  227. [self.progressInd setNeedsDisplay:YES];
  228. [self.progressInd setUsesThreadedAnimation:YES];
  229. [self.progressInd startAnimation:self];
  230. return;
  231. }
  232. [self.progressInd setIndeterminate:FALSE];
  233. [self.progressInd startAnimation:self];
  234. [self.progressInd setHidden:FALSE];
  235. [self.progressInd setNeedsDisplay:YES];
  236. [self.progressInd setDoubleValue:theProgress];
  237. });
  238. }
  239. - (void)setStatusText:(NSString *)statusText
  240. {
  241. dispatch_async(dispatch_get_main_queue(), ^{
  242. statusLabel.stringValue = statusText;
  243. });
  244. }
  245. - (BOOL)atvAvailable
  246. {
  247. return _atvAvailable;
  248. }
  249. - (void)setAtvAvailable:(BOOL)atvAvailable
  250. {
  251. _atvAvailable = atvAvailable;
  252. }
  253. - (void)applicationWillTerminate:(NSNotification *)aNotification {
  254. // Insert code here to tear down your application
  255. }
  256. - (NSString *)tarPathForCurrentMode
  257. {
  258. NSString *path = nil;
  259. switch (self.versionState) {
  260. case KBInstallVersionStateNine:
  261. path= @"/usr/bin/tar";
  262. break;
  263. case KBInstallVersionStateTenOne:
  264. path= @"/tmp/usr/bin/tar"; //need to verify
  265. break;
  266. case KBInstallVersionStateTenTwo:
  267. path= @"/usr/bin/tar";
  268. break;
  269. case KBInstallVersionStateEleven:
  270. path= @"/jb/usr/bin/tar";
  271. break;
  272. default:
  273. break;
  274. }
  275. return path;
  276. }
  277. - (NSString *)lsPathForCurrentMode
  278. {
  279. NSString *path = nil;
  280. switch (self.versionState) {
  281. case KBInstallVersionStateNine:
  282. path= @"/bin/ls";
  283. break;
  284. case KBInstallVersionStateTenOne:
  285. path= @"/tmp/bin/ls";
  286. break;
  287. case KBInstallVersionStateTenTwo:
  288. path= @"/usr/bin/ls";
  289. break;
  290. case KBInstallVersionStateEleven:
  291. path= @"/jb/bin/ls";
  292. break;
  293. default:
  294. break;
  295. }
  296. return path;
  297. }
  298. - (BOOL)checkForFile:(NSString *)file
  299. {
  300. //@"ls /usr/bin/ | grep appinst"
  301. NSString *lsCommand = [self lsPathForCurrentMode];
  302. NSString *path = [file stringByDeletingLastPathComponent];
  303. NSString *bareFile = [file lastPathComponent];
  304. NSString *theReturn = [self sendCommandString:[NSString stringWithFormat:@"%@ -a %@ | grep %@", lsCommand, path, bareFile]];
  305. if (theReturn != nil)
  306. { return (TRUE);
  307. } else { return (FALSE);}
  308. return (FALSE);
  309. }
  310. - (void)checkJailbreakWithCompletion:(void(^)(BOOL jailbroken))completion
  311. {
  312. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  313. BOOL jb = [self isJailbroken];
  314. if (jb)
  315. {
  316. NSLog(@"we outchea");
  317. } else {
  318. NSLog(@"nope!");
  319. }
  320. completion(jb);
  321. });
  322. }
  323. - (BOOL)isJailbroken
  324. {
  325. NSError *error = nil;
  326. if (APPLE_TV_ADDRESS != nil)
  327. {
  328. ObjSSH *ssh = [ObjSSH connectToHost:APPLE_TV_ADDRESS withUsername:@"root" password:@"alpine" error:&error];
  329. if (error)
  330. {
  331. NSLog(@"error: %@", [error localizedDescription]);
  332. if ([[error localizedDescription] isEqualToString:@"Failed to connect"])
  333. {
  334. [ssh disconnect];
  335. return (FALSE);
  336. }
  337. [ssh disconnect];
  338. }
  339. } else {
  340. return (FALSE);
  341. }
  342. return (TRUE);
  343. }
  344. //if they changed password from alpine this is used to get the proper password to connect
  345. - (void)resetServerSettings
  346. {
  347. [DEFAULTS removeObjectForKey:@"appleTVHost"];
  348. [DEFAULTS removeObjectForKey:ATV_OS];
  349. [DEFAULTS removeObjectForKey:ATV_API];
  350. [DEFAULTS setObject:@"Choose Apple TV" forKey:@"selectedValue"];
  351. appleTVAddress = nil;
  352. [[self progressInd] stopAnimation:nil];
  353. }
  354. - (NSString *)secureInput: (NSString *)prompt defaultValue: (NSString *)defaultValue {
  355. NSAlert *alert = [NSAlert alertWithMessageText: prompt
  356. defaultButton:@"OK"
  357. alternateButton:@"Cancel"
  358. otherButton:nil
  359. informativeTextWithFormat:@""];
  360. NSSecureTextField *input = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
  361. [input setStringValue:defaultValue];
  362. [alert setAccessoryView:input];
  363. NSInteger button = [alert runModal];
  364. if (button == NSAlertDefaultReturn) {
  365. [input validateEditing];
  366. NSString *inputString = [input stringValue];
  367. return inputString;
  368. } else if (button == NSAlertAlternateReturn) {
  369. return nil;
  370. } else {
  371. return nil;
  372. }
  373. }
  374. //add a password to the keychain for SSH
  375. - (void)addkeychainPassword:(NSString *)password
  376. {
  377. //kSecProtocolTypeSSH
  378. [EMInternetKeychainItem addInternetKeychainItemForServer:APPLE_TV_ADDRESS withUsername:@"root" password:password path:@"/usr/bin/ssh" port:22 protocol:kSecProtocolTypeSSH];
  379. }
  380. - (void)removeKeychainForHost:(NSString *)ipAddress
  381. {
  382. //kSecProtocolTypeSSH
  383. EMInternetKeychainItem *keychainItem = [EMInternetKeychainItem internetKeychainItemForServer:ipAddress withUsername:@"root" path:@"/usr/bin/ssh" port:22 protocol:kSecProtocolTypeSSH];
  384. [keychainItem removeFromKeychain];
  385. }
  386. //fetch the password from the keychain for the specified ip address
  387. - (NSString *)passwordForHost:(NSString *)ipAddress
  388. {
  389. EMInternetKeychainItem *keychainItem = [EMInternetKeychainItem internetKeychainItemForServer:ipAddress withUsername:@"root" path:@"/usr/bin/ssh" port:22 protocol:kSecProtocolTypeSSH];
  390. //Grab the password.
  391. if (keychainItem != nil)
  392. {
  393. //Grab the password.
  394. NSString *password = keychainItem.password;
  395. return password;
  396. }
  397. NSLog(@"nothing!");
  398. return nil;
  399. }
  400. - (BOOL)uploadFile:(NSString *)theFile toPath:(NSString *)newPath
  401. {
  402. NSLog(@"uploading file: %@", theFile);
  403. NSError *error = nil;
  404. BOOL getSession = [self connectToSSH];
  405. if (getSession == FALSE)
  406. {
  407. NSLog(@"failed to get session!");
  408. return (FALSE);
  409. }
  410. NSString *finalPath = [newPath stringByAppendingPathComponent:[theFile lastPathComponent]];
  411. BOOL uploadFile = [sshSession uploadFile:theFile to:finalPath error:&error];
  412. if (error)
  413. {
  414. NSLog(@"ERROR!: %@", error);
  415. }
  416. return (uploadFile);
  417. }
  418. - (void)uploadFile:(NSString *)theFile withCompletion:(void(^)(BOOL success))completion
  419. {
  420. NSLog(@"Uploading payload...");
  421. //NSLog(@"uploading file: %@", theFile);
  422. NSError *error = nil;
  423. BOOL getSession = [self connectToSSH];
  424. if (getSession == FALSE)
  425. {
  426. NSLog(@"failed to get session!");
  427. completion(false);
  428. }
  429. BOOL uploadFile = [self uploadFile:theFile toPath:@"/var/mobile/Documents"];
  430. completion(uploadFile);
  431. }
  432. //upload a file over SSH to the selected AppleTV
  433. - (BOOL)uploadFile:(NSString *)theFile
  434. {
  435. NSLog(@"uploading file: %@", theFile);
  436. NSError *error = nil;
  437. BOOL getSession = [self connectToSSH];
  438. if (getSession == FALSE)
  439. {
  440. NSLog(@"failed to get session!");
  441. return (FALSE);
  442. }
  443. statusLabel.stringValue = [NSString stringWithFormat:@"Uploading file %@....", theFile.lastPathComponent];
  444. BOOL uploadFile = [sshSession uploadFile:theFile to:[theFile lastPathComponent] error:&error];
  445. if (error)
  446. {
  447. NSLog(@"ERROR!: %@", error);
  448. }
  449. return (uploadFile);
  450. }
  451. //used to send basic commands to the jailbroken AppleTV over SSH
  452. - (NSString *)sendCommandString:(NSString *)theCommand
  453. {
  454. NSLog(@"%@", theCommand);
  455. NSError *error = nil;
  456. BOOL getSession = [self connectToSSH];
  457. if (getSession == FALSE)
  458. {
  459. NSLog(@"failed to get session!");
  460. return nil;
  461. }
  462. NSString *response = [sshSession execute:theCommand error:&error];
  463. return response;
  464. }
  465. //open the SSH session
  466. - (BOOL)connectToSSH
  467. {
  468. NSError *error = nil;
  469. if (sshSession == nil)
  470. {
  471. //NSLog(@"APPLE_TV_ADDRESS: %@", APPLE_TV_ADDRESS);
  472. sshSession = [ObjSSH connectToHost:APPLE_TV_ADDRESS withUsername:@"root" password:@"alpine" error:&error];
  473. if (sshSession == nil)
  474. {
  475. NSLog(@"error: %@ get password!", error);
  476. NSString *passwordForHost = [self passwordForHost:APPLE_TV_ADDRESS];
  477. NSString *output = nil;
  478. if (passwordForHost != nil)
  479. {
  480. output = passwordForHost;
  481. } else {
  482. output = [self secureInput:@"Enter Password" defaultValue:@""];
  483. }
  484. if ([output length] == 0)
  485. {
  486. NSLog(@"no password to send!! return!");
  487. return (FALSE);
  488. } else {
  489. error = nil;
  490. sshSession = [ObjSSH connectToHost:APPLE_TV_ADDRESS withUsername:@"root" password:output error:&error];
  491. if (error != nil)
  492. {
  493. NSLog(@"error: %@ password failed!", error);
  494. [self removeKeychainForHost:APPLE_TV_ADDRESS];
  495. return (FALSE);
  496. } else {
  497. [self addkeychainPassword:output];
  498. }
  499. }
  500. }
  501. }
  502. if (sshSession != nil)
  503. return (TRUE);
  504. return (FALSE);
  505. }
  506. - (void)showNotJailbrokenWarning
  507. {
  508. NSAlert *alert = [NSAlert alertWithMessageText:@"This Apple TV isn't jailbroken, please jailbreak it first! Would you like to visit our web site for further assistance?"
  509. defaultButton:@"Yes"
  510. alternateButton:@"Cancel"
  511. otherButton:nil
  512. informativeTextWithFormat:@""];
  513. NSInteger button = [alert runModal];
  514. if (button == NSAlertDefaultReturn) {
  515. [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://wiki.awkwardtv.org"]];
  516. }
  517. }
  518. - (void)showATVWarning
  519. {
  520. NSAlert *alert = [NSAlert alertWithMessageText:@"Only the AppleTV 4 is supported"
  521. defaultButton:@"OK"
  522. alternateButton:nil
  523. otherButton:nil
  524. informativeTextWithFormat:@""];
  525. [alert runModal];
  526. }
  527. - (NSString *)logLocation
  528. {
  529. NSString *location = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Logs/iReSign"];
  530. if (![FM fileExistsAtPath:location])
  531. {
  532. [FM createDirectoryAtPath:location withIntermediateDirectories:true attributes:nil error:nil];
  533. }
  534. return location;
  535. }
  536. //versions
  537. //265.5 = 9.0
  538. //301.44.3 = 10.0
  539. //310.17 = 10.1 (either .0 or .1)
  540. //320.20.1 = 10.2.2
  541. //353.50 = 11.1
  542. - (IBAction)doIt:(id)sender
  543. {
  544. [self.mainButton setEnabled:NO];
  545. NSString *bootstrapPath = @"https://nitosoft.com/ATV4/bootstraps/";
  546. NSString *swVers = self.deviceDict[@"osvers"];
  547. if (swVers.length > 0)
  548. {
  549. NSLog(@"our sw vers: %@", swVers);
  550. if ([swVers floatValue] >= 11)
  551. {
  552. self.versionState = KBInstallVersionStateEleven;
  553. NSLog(@"11 payload!");
  554. bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap11.tar"];
  555. }
  556. } else {
  557. swVers = self.deviceDict[@"srcvers"];
  558. {
  559. NSLog(@"our src vers: %@", swVers);
  560. if([swVers compare:@"320.20.1" options:NSNumericSearch] != NSOrderedAscending)
  561. {
  562. self.versionState = KBInstallVersionStateTenTwo;
  563. NSLog(@"10.2.2 or greater!");
  564. bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap11.tar"];
  565. } else if([swVers compare:@"301.44.3" options:NSNumericSearch] != NSOrderedAscending)
  566. {
  567. NSLog(@"10.0 or greater");
  568. self.versionState = KBInstallVersionStateTenOne;
  569. bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap9.tar"];
  570. } else {
  571. NSLog(@"9.0 or greater?");
  572. self.versionState = KBInstallVersionStateNine;
  573. bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap9.tar"];
  574. }
  575. }
  576. }
  577. if ([self connectToSSH])
  578. {
  579. // if ([self checkForFile:@"/var/root/.bootstrapped"]){
  580. // NSLog(@"were already bootstrapped!");
  581. //} else {
  582. [self processFile:bootstrapPath];
  583. // [self downloadFile:bootstrapPath];
  584. // }
  585. //
  586. //[self downloadSyslogAndShow:YES];
  587. } else {
  588. self.mainButton.enabled = TRUE;
  589. }
  590. }
  591. - (BOOL)downloadSyslogAndShow:(BOOL)show
  592. {
  593. BOOL getSession = [self connectToSSH];
  594. if (getSession == FALSE)
  595. {
  596. NSLog(@"failed to get session!");
  597. return false;
  598. }
  599. NSError *error = nil;
  600. NSString *newSyslog = [[self logLocation] stringByAppendingPathComponent:@"syslog.log"];
  601. if ([[NSFileManager defaultManager] fileExistsAtPath:newSyslog])
  602. {
  603. [[NSFileManager defaultManager] removeItemAtPath:newSyslog error:nil];
  604. }
  605. //echo latest syslog output to a new file for downloadin..
  606. [self sendCommandString:@"/jb/usr/bin/syslog > syslog.log"];
  607. BOOL downloadFile = [sshSession downloadFile:@"/var/root/syslog.log" to:newSyslog error:&error];
  608. if (downloadFile)
  609. {
  610. NSLog(@"File downloaded Successfully!");
  611. if (show == true)
  612. {
  613. [[NSWorkspace sharedWorkspace] openFile:newSyslog];
  614. }
  615. return true;
  616. }
  617. return false;
  618. }
  619. - (void)installFile:(NSString *)theFile withCompletionBlock:(void(^)(BOOL success))completionBlock
  620. {
  621. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  622. @autoreleasepool {
  623. BOOL success = FALSE;
  624. if ([self uploadFile:finalDestination toPath:@"/var/mobile/Documents"] == true)
  625. {
  626. dispatch_async(dispatch_get_main_queue(), ^{
  627. [statusLabel setStringValue:[NSString stringWithFormat:@"Installing file %@...", fileName]];
  628. });
  629. /*
  630. Massively kludgy but it works, for some reason when appinst runs it doesnt go to stdout (or something) so i /need/ to redirect it to a text file, then cat that text file to check for whether or not "installed <bundle_id>" exists
  631. */
  632. NSString *checkResponse = [NSString stringWithFormat:@"installed %@", self.bundleID];
  633. NSString *runLine = [NSString stringWithFormat:@"/usr/bin/appinst /var/mobile/Documents/%@ 2> install.txt ; cat install.txt", fileName];
  634. //NSString *runLine = [NSString stringWithFormat:@"/usr/bin/appinst /var/mobile/Documents/%@", fileName];
  635. NSString *response = [self sendCommandString:runLine];
  636. //using rangeOfString because containsString is too new for backwards compat.
  637. if ([response rangeOfString:checkResponse].location == NSNotFound)
  638. {
  639. NSString *errorLog = [[self logLocation] stringByAppendingFormat:@"/%@.log", self.bundleID];
  640. //remove old copies
  641. if ([FM fileExistsAtPath:errorLog])
  642. {
  643. [FM removeItemAtPath:errorLog error:nil];
  644. }
  645. //the response above has a bunch of garbled text in it, download the install file "proper" to get the cleaner version
  646. BOOL downloadFile = [sshSession downloadFile:@"/var/root/install.txt" to:errorLog error:nil];
  647. if (downloadFile == false)
  648. {
  649. //if that fails for some reason write the version with the garbage at the end
  650. [response writeToFile:errorLog atomically:true encoding:NSUTF8StringEncoding error:nil];
  651. }
  652. response = [NSString stringWithContentsOfFile:errorLog encoding:NSUTF8StringEncoding error:nil];
  653. NSLog(@"INSTALLATION FAILED WITH LOG: %@", response);
  654. //grab latest relevant syslog chunk
  655. [self downloadSyslogAndShow:false];
  656. } else {
  657. runLine = [NSString stringWithFormat:@"/bin/rm /var/mobile/Documents/%@", fileName];
  658. [self sendCommandString:runLine];
  659. success = true;
  660. }
  661. }
  662. dispatch_async(dispatch_get_main_queue(), ^{
  663. completionBlock(success);
  664. });
  665. }
  666. });
  667. }
  668. @end