AppDelegate.m 29 KB

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