PBReloadHelper.m 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. //
  2. // PBReloadHelper.m
  3. // ATVWebRemote
  4. //
  5. // Created by Kevin Bradley on 3/21/17.
  6. // Copyright © 2017 nito. All rights reserved.
  7. //
  8. #import <Foundation/Foundation.h>
  9. #import "NSTask.h"
  10. #import <objc/runtime.h>
  11. //#import "rocketbootstrap.h"
  12. #import "AppSupport/CPDistributedMessagingCenter.h"
  13. @interface LSApplicationProxy : NSObject
  14. @property (nonatomic,readonly) NSURL * bundleURL;
  15. @property (nonatomic,readonly) NSString * bundleIdentifier;
  16. @property (nonatomic,readonly) NSString * bundleType; //@synthesize bundleType=_bundleType - In the implementation block
  17. @property (nonatomic,readonly) NSString * localizedShortName;
  18. @end
  19. @interface NSDistributedNotificationCenter : NSNotificationCenter
  20. + (id)defaultCenter;
  21. - (void)addObserver:(id)arg1 selector:(SEL)arg2 name:(id)arg3 object:(id)arg4;
  22. - (void)postNotificationName:(id)arg1 object:(id)arg2 userInfo:(id)arg3;
  23. @end
  24. @interface PBSMutableAppState : NSObject
  25. -(void)setEnabled:(BOOL)arg1 ;
  26. -(id)initWithApplicationIdentifer:(id)arg1 ;
  27. -(BOOL)isEnabled;
  28. @end
  29. @interface LSApplicationWorkspace : NSObject
  30. + (id) defaultWorkspace;
  31. - (void)_LSClearSchemaCaches;
  32. - (_Bool)_LSPrivateRebuildApplicationDatabasesForSystemApps:(_Bool)arg1 internal:(_Bool)arg2 user:(_Bool)arg3;
  33. -(id)allInstalledApplications;
  34. -(BOOL)_LSPrivateSyncWithMobileInstallation;
  35. -(BOOL)registerApplicationDictionary:(id)arg1 withObserverNotification:(int)arg2;
  36. -(BOOL)registerApplicationDictionary:(id)arg1 ;
  37. -(BOOL)registerApplication:(id)arg1 ;
  38. -(BOOL)unregisterApplication:(id)arg1 ;
  39. -(BOOL)registerPlugin:(id)arg1 ;
  40. -(BOOL)unregisterPlugin:(id)arg1 ;
  41. -(id)installedPlugins;
  42. @end
  43. @interface PBAppDepot : NSObject
  44. + (id)sharedInstance;
  45. @property(retain, nonatomic) NSMutableDictionary *internalAppState;
  46. - (id)_addAppStateForIdentifier:(id)arg1;
  47. - (void)_save;
  48. - (void)_setNeedsNotifyAppStateDidChange;
  49. - (void)_removeAppStateForIdentifier:(id)arg1;
  50. @end
  51. #import "PBReloadHelper.h"
  52. @implementation PBReloadHelper
  53. + (void)captureScreenshot
  54. {
  55. //CALayer *layer;
  56. // UIWindow *win = [[UIApplication sharedApplication] keyWindow];
  57. //layer = win.layer;
  58. //UIGraphicsBeginImageContextWithOptions(win.bounds.size, NO, 1.0f);
  59. //[layer.presentationLayer renderInContext:UIGraphicsGetCurrentContext()];
  60. //UIImage *screenImage = UIGraphicsGetImageFromCurrentImageContext();
  61. //return screenImage;
  62. }
  63. -(void)runAppEnabler
  64. {
  65. //NSLog(@"#### for luck!");
  66. [PBReloadHelper reloadApplications];
  67. }
  68. /**
  69. On tvOS 10+ we need to manually remove the applications from PBAppDepot after moving / removing
  70. them from the /var/mobile/Applications folder. This method cycles filters allInstalledApplications
  71. in the application workspace to only applications with the bundleURL string containing
  72. /var/mobile/Applications/
  73. loop through them and see which ones actually exist, if they dont, add the bundle ID to the
  74. deleted array that is returned.
  75. */
  76. + (BOOL)containsNewApplication:(NSArray *)appIds
  77. {
  78. Class WSCLASS = objc_getClass("LSApplicationWorkspace");
  79. id workspace = nil;
  80. if (WSCLASS != nil)
  81. {
  82. workspace = [WSCLASS defaultWorkspace];
  83. }
  84. __block NSMutableArray *installedArray = [NSMutableArray new];
  85. __block BOOL containsApplication = NO;
  86. NSArray *installedApplications = [workspace allInstalledApplications];
  87. NSPredicate *pred = [NSPredicate predicateWithFormat:@"self.bundleURL.absoluteString contains[cd] %@ or self.applicationIdentifier contains[cd] %@ ", @"/Applications/", @"com.nito.nitoTV4"];
  88. NSArray <LSApplicationProxy *> *mobileApplications = [installedApplications filteredArrayUsingPredicate:pred];
  89. //NSLog(@"new array: %@", newArray);
  90. NSFileManager *man = [NSFileManager defaultManager];
  91. [mobileApplications enumerateObjectsUsingBlock:^(LSApplicationProxy * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  92. NSString *appPath = [[obj bundleURL] path];
  93. //NSLog(@"thepath: %@", appPath);
  94. if ([man fileExistsAtPath:appPath])
  95. {
  96. //NSLog(@"app removed: %@", [obj bundleIdentifier]);
  97. [installedArray addObject:[obj bundleIdentifier]];
  98. }
  99. }];
  100. [appIds enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  101. if (![installedArray containsObject:obj])
  102. {
  103. *stop = YES;
  104. containsApplication = YES;
  105. }
  106. }];
  107. return containsApplication;
  108. }
  109. + (NSArray *)deletedApplications
  110. {
  111. Class WSCLASS = objc_getClass("LSApplicationWorkspace");
  112. id workspace = nil;
  113. if (WSCLASS != nil)
  114. {
  115. workspace = [WSCLASS defaultWorkspace];
  116. }
  117. __block NSMutableArray *deletedArray = [NSMutableArray new];
  118. NSArray *installedApplications = [workspace allInstalledApplications];
  119. NSPredicate *pred = [NSPredicate predicateWithFormat:@"self.bundleURL.absoluteString contains[cd] %@ or self.applicationIdentifier contains[cd] %@ ", @"/Applications/", @"com.nito.nitoTV4"];
  120. NSArray <LSApplicationProxy *> *mobileApplications = [installedApplications filteredArrayUsingPredicate:pred];
  121. //NSLog(@"new array: %@", newArray);
  122. NSFileManager *man = [NSFileManager defaultManager];
  123. [mobileApplications enumerateObjectsUsingBlock:^(LSApplicationProxy * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  124. NSString *appPath = [[obj bundleURL] path];
  125. //NSLog(@"thepath: %@", appPath);
  126. if (![man fileExistsAtPath:appPath])
  127. {
  128. NSLog(@"app removed: %@", [obj bundleIdentifier]);
  129. [deletedArray addObject:[obj bundleIdentifier]];
  130. }
  131. }];
  132. return deletedArray;
  133. }
  134. + (void)newReloadApplications {
  135. NSFileManager *manager = [NSFileManager defaultManager];
  136. NSString *whichKill = @"/tmp/usr/bin/killall";
  137. if (![manager fileExistsAtPath:whichKill])
  138. {
  139. whichKill = @"/usr/bin/killall";
  140. }
  141. id appDepot = [objc_getClass("PBAppDepot") sharedInstance];
  142. HBLogInfo(@"appDepot: %@", appDepot);
  143. NSMutableDictionary *installedAppStates = [appDepot internalAppState];
  144. NSLog(@"installedAppStates: %@", installedAppStates);
  145. id nitoTV = [installedAppStates objectForKey:@"com.nito.nitoTV4"];
  146. NSError *error = nil;
  147. NSString *whitelistFile = @"/var/mobile/Library/Preferences/com.nito.whitelist.plist";
  148. NSString *recentlyDeleted = @"/var/mobile/Library/Preferences/com.nito.deleted.plist";
  149. NSArray *whiteList = [NSArray arrayWithContentsOfFile:whitelistFile];
  150. if (!whiteList) {
  151. whiteList = @[@"com.nito.nitoTV4"];
  152. }
  153. NSMutableArray *recentDeletions = [NSMutableArray new];
  154. NSMutableArray *mutableWhiteList = [whiteList mutableCopy];
  155. BOOL addNito = [manager fileExistsAtPath:@"/Applications/nitoTV.app/nitoTV"];
  156. NSMutableArray *appArray = [NSMutableArray new];
  157. NSMutableArray *identifierArray = [NSMutableArray new];
  158. BOOL addedApps = FALSE;
  159. NSArray *apps = [manager contentsOfDirectoryAtPath:@"/Applications" error:&error];
  160. for (NSString *app in apps)
  161. if ([app hasSuffix:@".app"]) {
  162. NSString *path = [@"/Applications" stringByAppendingPathComponent:app];
  163. NSString *newPl = [path stringByAppendingPathComponent:@"Info.plist"];
  164. NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:newPl];
  165. NSString *identifier = [info objectForKey:@"CFBundleIdentifier"];
  166. NSLog(@"identifier: %@", identifier);
  167. id existingObject = (PBSMutableAppState*)[installedAppStates objectForKey:identifier];
  168. if (existingObject != nil)
  169. {
  170. //[installedAppStates setObject:existingObject forKey:identifier];
  171. if (![existingObject isEnabled]){
  172. NSLog(@"its disabled: %@", existingObject);
  173. if (![mutableWhiteList containsObject:identifier])
  174. {
  175. [mutableWhiteList addObject:identifier];
  176. }
  177. NSDictionary *appDict = @{@"ApplicationType": @"System", @"CFBundleIdentifier": identifier, @"CodeInfoIdentifier": identifier, @"Path": path};
  178. [appArray addObject:appDict];
  179. [identifierArray addObject:identifier];
  180. addedApps = YES;
  181. }
  182. } else {
  183. NSLog(@"didnt find object for key: %@", identifier);
  184. if (![mutableWhiteList containsObject:identifier])
  185. {
  186. [mutableWhiteList addObject:identifier];
  187. }
  188. NSDictionary *appDict = @{@"ApplicationType": @"System", @"CFBundleIdentifier": identifier, @"CodeInfoIdentifier": identifier, @"Path": path};
  189. [appArray addObject:appDict];
  190. [identifierArray addObject:identifier];
  191. addedApps = YES;
  192. /*
  193. Class PBSMASC = objc_getClass("PBSMutableAppState");
  194. id appState = [[PBSMASC alloc] initWithApplicationIdentifer:identifier];
  195. [appState setEnabled:YES];
  196. //[appState setCacheDeleting:YES];
  197. [appState incrementCacheDeleting];
  198. NSLog(@"app state: %@", appState);
  199. [installedAppStates setObject:appState forKey:identifier];
  200. [workspace registerApplicationDictionary:info];
  201. */
  202. }
  203. }
  204. NSString* pth = @"/var/mobile/Library/Preferences/kjc.appenabler.state.plist";
  205. [appArray writeToFile:pth atomically:YES];
  206. Class WSCLASS = objc_getClass("LSApplicationWorkspace");
  207. id workspace = nil;
  208. if (WSCLASS != nil)
  209. {
  210. workspace = [WSCLASS defaultWorkspace];
  211. }
  212. NSArray *deletedApps = [self deletedApplications];
  213. BOOL removedApps = ([deletedApps count] > 0);
  214. if ([workspace respondsToSelector:@selector(_LSPrivateSyncWithMobileInstallation)]){
  215. if (addedApps || removedApps){
  216. //rm /var/containers/Data/System/97D6E4BA-C0BF-408B-AF63-1836844381AE/Library/Caches/com.apple.LaunchServices-175-v2.csstore
  217. //delete the csstore file, its run through bash so the wildcards are completed properly
  218. //[NSTask launchedTaskWithLaunchPath:@"/bin/bash" arguments:@[@"/bin/rm", @"/var/containers/Data/System/*/Library/Caches/com.apple.LaunchServices*.csstore"]];
  219. //FIXME: i dont like calling this chmod +x shell script
  220. //its probably a security risk of some type, but the line above
  221. //didnt work properly and i couldnt figure out why
  222. [NSTask launchedTaskWithLaunchPath:@"/bin/bash" arguments:@[@"/usr/bin/rmcache"]];
  223. if (removedApps)
  224. {
  225. NSLog(@"app ids were deleted: %@", deletedApps);
  226. [deletedApps enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  227. [mutableWhiteList removeObject:obj];
  228. [recentDeletions addObject:obj];
  229. NSLog(@"removing app state with id: %@", obj);
  230. [appDepot _removeAppStateForIdentifier:obj];
  231. }];
  232. } else {
  233. //no removed apps, need to make sure they don't mess with our reload anymore
  234. [[NSFileManager defaultManager] removeItemAtPath:recentlyDeleted error:nil];
  235. }
  236. /*
  237. _LSPrivateSyncWithMobileInstallation will actually display the application and allow you to launch it, but the AppDepot (or something related to the installed app database) is potentially harshed or out of sync after we do this, which is why we always call the killall at the very bottom of this method, also if apps are
  238. deleted their state is not updated properly without a respring either
  239. */
  240. if((addedApps || removedApps)){
  241. NSLog(@"we can haz new app");
  242. }
  243. }
  244. [workspace _LSClearSchemaCaches]; //may or may not be necessary
  245. [workspace _LSPrivateRebuildApplicationDatabasesForSystemApps:YES internal:YES user:YES];
  246. [workspace _LSPrivateSyncWithMobileInstallation];
  247. if ([workspace respondsToSelector:@selector(invalidateIconCache:)]){
  248. NSLog(@"invalidate icon cache");
  249. [workspace invalidateIconCache:nil];
  250. } else {
  251. NSLog(@"## dont invalidate icon cache??");
  252. }
  253. }
  254. [mutableWhiteList writeToFile:whitelistFile atomically:YES];
  255. [recentDeletions writeToFile:recentlyDeleted atomically:YES];
  256. if(addedApps){
  257. NSLog(@"we can haz new app");
  258. sleep(5);
  259. for (NSDictionary* o in appArray)
  260. {
  261. char retry=0;
  262. NSString* identifier = o[@"CFBundleIdentifier"];
  263. retry_:
  264. NSLog(@"identifier: %@", identifier);
  265. id existingObject = [installedAppStates objectForKey:identifier];
  266. if (existingObject != nil)
  267. {
  268. [existingObject setValue:[NSNumber numberWithBool:YES] forKey:@"_enabled"];
  269. BOOL isEnabled = [existingObject isEnabled];
  270. //[existingObject setEnabled:YES];
  271. //[installedAppStates setObject: existingObject forKey: identifier];
  272. } else {
  273. //[appDepot _addAppStateForIdentifier:identifier];
  274. NSLog(@"didnt find object for key: %@, error!!!!!!", identifier);
  275. if(retry == 0)
  276. {
  277. NSLog(@"retry");
  278. //NSLog(@"try to manually kick start substrate!");
  279. //[NSTask launchedTaskWithLaunchPath:@"/usr/bin/nitoHelper" arguments:@[@"substrate", @"1", @"2"]];
  280. //is9 = false;
  281. ///usr/bin/cynject 1 /Library/Frameworks/CydiaSubstrate.framework/Libraries/SubstrateLauncher.dylib
  282. retry = 1;
  283. goto retry_;
  284. }
  285. }
  286. }
  287. }
  288. //this notification is received by uicache binary so its knows that its safe to exit the runloop it creates so it can relay notifications back and forth during this convoluted process.
  289. [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"kjc.AppEnabler.done" object:nil userInfo:nil];
  290. //it beats killall -u mobile... right?
  291. //we dont have system() call anymore and posix_spawn is a pain to configure.
  292. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
  293. //[@"science" writeToFile:@"/var/mobile/Library/Preferences/itshappening" atomically:YES encoding:NSUTF8StringEncoding error:nil];
  294. [NSTask launchedTaskWithLaunchPath:whichKill arguments:@[@"-9", @"backboardd"]];
  295. //[NSTask launchedTaskWithLaunchPath:whichKill arguments:@[@"-9", @"PineBoard", @"HeadBoard", @"lsd", @"installd"]];
  296. });
  297. }
  298. + (void)reloadApplications
  299. {
  300. [self newReloadApplications];
  301. return;
  302. //at the moment in tvOS 10 morpheus extracts the bintools to /tmp rather than root, so .. for now, it could be in /tmp
  303. NSFileManager *manager = [NSFileManager defaultManager];
  304. NSString *whichKill = @"/tmp/usr/bin/killall";
  305. if (![manager fileExistsAtPath:whichKill])
  306. {
  307. whichKill = @"/usr/bin/killall";
  308. }
  309. /*
  310. PBAppDepot is where PineBoard keeps track of all the applications that are installed
  311. and whether or not they are new or enabled or whatever. PineBoardServices appears to communicate
  312. with the Depot to some degree over an XPC service that i couldn't quite figure out, a more
  313. /proper/ way to do this is probably through there.
  314. https://github.com/lechium/tvOS10Headers/blob/master/System/Library/PrivateFrameworks/PineBoardServices.framework/PBSAppDepotProxy.h
  315. the internalAppState is an NSDictionary where each key / value pair is the app id / PBMutableAppState
  316. that is related to each installed application.
  317. i.e.
  318. `{
  319. ...
  320. "com.Moballo.tvbrowser" = "PBSMutableAppState(identifier=com.Moballo.tvbrowser, badge=(null), badgeEnabled=1, recent=0, cacheDeleting=0, enabled=1, notifs=0)";
  321. "com.apple.AdSheetPhone" = "PBSMutableAppState(identifier=com.apple.AdSheetPhone, badge=(null), badgeEnabled=1, recent=0, cacheDeleting=0, enabled=1, notifs=0)";
  322. "com.apple.CloudKit.ShareBear" = "PBSMutableAppState(identifier=com.apple.CloudKit.ShareBear, badge=(null), badgeEnabled=1, recent=0, cacheDeleting=0, enabled=1, notifs=0)";
  323. "com.apple.DiagnosticsService" = "PBSMutableAppState(identifier=com.apple.DiagnosticsService, badge=(null), badgeEnabled=1, recent=0, cacheDeleting=0, enabled=1, notifs=0)";
  324. ...
  325. }
  326. */
  327. id appDepot = [objc_getClass("PBAppDepot") sharedInstance];
  328. HBLogInfo(@"appDepot: %@", appDepot);
  329. NSMutableDictionary *installedAppStates = [appDepot internalAppState];
  330. NSLog(@"installedAppStates: %@", installedAppStates);
  331. id nitoTV = [installedAppStates objectForKey:@"com.nito.nitoTV4"];
  332. NSError *error = nil;
  333. NSString *whitelistFile = @"/var/mobile/Library/Preferences/com.nito.whitelist.plist";
  334. NSString *recentlyDeleted = @"/var/mobile/Library/Preferences/com.nito.deleted.plist";
  335. NSArray *whiteList = [NSArray arrayWithContentsOfFile:whitelistFile];
  336. NSMutableArray *recentDeletions = [NSMutableArray new];
  337. NSMutableArray *mutableWhiteList = [whiteList mutableCopy];
  338. BOOL addNito = [manager fileExistsAtPath:@"/Applications/nitoTV.app/nitoTV"];
  339. if (whiteList == nil){
  340. if (addNito)
  341. {
  342. whiteList = @[@"com.nito.nitoTV4"];
  343. mutableWhiteList = [whiteList mutableCopy];
  344. } else {
  345. mutableWhiteList = [NSMutableArray new];
  346. }
  347. } else {
  348. if (addNito)
  349. {
  350. NSLog(@"mutableWhiteList: %@", mutableWhiteList);
  351. if (![mutableWhiteList containsObject:@"com.nito.nitoTV4"]){
  352. [mutableWhiteList addObject:@"com.nito.nitoTV4"];
  353. }
  354. }
  355. }
  356. /*
  357. Loop through our /@"/var/mobile/Applications" folder and add them to a new plist
  358. file that will be injected inside the MobileInstallation framework when loading
  359. the list of applications that are installed.
  360. */
  361. NSString *applicationPath = @"/var/mobile/Applications";
  362. NSMutableArray *appArray = [NSMutableArray new];
  363. NSMutableArray *identifierArray = [NSMutableArray new];
  364. BOOL addedApps = FALSE;
  365. NSArray *apps = [manager contentsOfDirectoryAtPath:applicationPath error:&error];
  366. NSArray *defaultApps = [manager contentsOfDirectoryAtPath:@"/Applications" error:&error];
  367. for (NSString *app in apps)
  368. {
  369. if ([app hasSuffix:@".app"]) {
  370. NSString *path = [applicationPath stringByAppendingPathComponent:app];
  371. NSString *newPl = [path stringByAppendingPathComponent:@"Info.plist"];
  372. NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:newPl];
  373. //NSLog(@"info: %@", info);
  374. if (info != nil) {
  375. NSString *identifier = [info objectForKey:@"CFBundleIdentifier"];
  376. if(identifier != nil) {
  377. if (![mutableWhiteList containsObject:identifier])
  378. {
  379. [mutableWhiteList addObject:identifier];
  380. }
  381. NSDictionary *appDict = @{@"ApplicationType": @"System", @"CFBundleIdentifier": identifier, @"CodeInfoIdentifier": identifier, @"Path": path};
  382. [appArray addObject:appDict];
  383. [identifierArray addObject:identifier];
  384. addedApps = YES;
  385. NSString *appPluginPath = [path stringByAppendingPathComponent:@"PlugIns"];
  386. if ([manager fileExistsAtPath:appPluginPath])
  387. {
  388. NSLog(@"found plugin folder for id: %@", identifier );
  389. NSArray <NSString *> *plugins = [manager contentsOfDirectoryAtPath:appPluginPath error:&error];
  390. [plugins enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  391. if ([[obj pathExtension] isEqualToString:@"appex"])
  392. {
  393. NSString *pluginPath = [appPluginPath stringByAppendingPathComponent:obj];
  394. //NSLog(@"found appex: %@", pluginPath);
  395. NSString *pluginPl = [pluginPath stringByAppendingPathComponent:@"Info.plist"];
  396. NSMutableDictionary *pluginInfo = [NSMutableDictionary dictionaryWithContentsOfFile:pluginPl];
  397. //NSLog(@"pluginInfo: %@", pluginInfo);
  398. if (pluginInfo != nil) {
  399. NSString *pluginID = [pluginInfo objectForKey:@"CFBundleIdentifier"];
  400. NSLog(@"id: %@", identifier);
  401. NSLog(@"pluginID: %@", pluginID);
  402. NSLog(@"pluginPath: %@", pluginPath);
  403. NSDictionary *pluginDict = @{@"ApplicationType": @"PluginKitPlugin", @"CFBundleIdentifier": pluginID, @"CodeInfoIdentifier": pluginID, @"Path": pluginPath, @"PluginOwnerBundleID": identifier};
  404. //NSLog(@"pluginDict: %@", pluginDict);
  405. [appArray addObject:pluginDict];
  406. }
  407. }
  408. }];
  409. }
  410. }
  411. }
  412. }
  413. }
  414. if (nitoTV == nil && addNito)
  415. {
  416. NSLog(@"nitoTV has never been loaded before! force it to load");
  417. NSString *path = @"/Applications/nitoTV.app";
  418. NSString *newPl = [path stringByAppendingPathComponent:@"Info.plist"];
  419. NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:newPl];
  420. //NSLog(@"info: %@", info);
  421. if (info != nil) {
  422. NSString *identifier = [info objectForKey:@"CFBundleIdentifier"];
  423. if(identifier != nil) {
  424. if (![mutableWhiteList containsObject:identifier])
  425. {
  426. [mutableWhiteList addObject:identifier];
  427. }
  428. NSDictionary *appDict = @{@"ApplicationType": @"System", @"CFBundleIdentifier": identifier, @"CodeInfoIdentifier": identifier, @"Path": path};
  429. [appArray addObject:appDict];
  430. [identifierArray addObject:identifier];
  431. addedApps = YES;
  432. NSString *appPluginPath = [path stringByAppendingPathComponent:@"PlugIns"];
  433. if ([manager fileExistsAtPath:appPluginPath])
  434. {
  435. NSLog(@"found plugin folder for id: %@", identifier );
  436. NSArray <NSString *> *plugins = [manager contentsOfDirectoryAtPath:appPluginPath error:&error];
  437. [plugins enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  438. if ([[obj pathExtension] isEqualToString:@"appex"])
  439. {
  440. NSString *pluginPath = [appPluginPath stringByAppendingPathComponent:obj];
  441. //NSLog(@"found appex: %@", pluginPath);
  442. NSString *pluginPl = [pluginPath stringByAppendingPathComponent:@"Info.plist"];
  443. NSMutableDictionary *pluginInfo = [NSMutableDictionary dictionaryWithContentsOfFile:pluginPl];
  444. //NSLog(@"pluginInfo: %@", pluginInfo);
  445. if (pluginInfo != nil) {
  446. NSString *pluginID = [pluginInfo objectForKey:@"CFBundleIdentifier"];
  447. NSDictionary *pluginDict = @{@"ApplicationType": @"PluginKitPlugin", @"CFBundleIdentifier": pluginID, @"CodeInfoIdentifier": pluginID, @"Path": pluginPath, @"PluginOwnerBundleID": identifier};
  448. [appArray addObject:pluginDict];
  449. }
  450. }
  451. }];
  452. }
  453. }
  454. }
  455. }
  456. //the file that we store the installed applications in
  457. NSString* pth = @"/var/mobile/Library/Preferences/kjc.appenabler.state.plist";
  458. [appArray writeToFile:pth atomically:YES];
  459. Class WSCLASS = objc_getClass("LSApplicationWorkspace");
  460. id workspace = nil;
  461. if (WSCLASS != nil)
  462. {
  463. workspace = [WSCLASS defaultWorkspace];
  464. }
  465. int is9 = 0;
  466. //tvOS 10+
  467. NSArray *deletedApps = [self deletedApplications];
  468. BOOL removedApps = ([deletedApps count] > 0);
  469. if ([workspace respondsToSelector:@selector(_LSPrivateSyncWithMobileInstallation)]){
  470. if (addedApps || removedApps){
  471. //rm /var/containers/Data/System/97D6E4BA-C0BF-408B-AF63-1836844381AE/Library/Caches/com.apple.LaunchServices-175-v2.csstore
  472. //delete the csstore file, its run through bash so the wildcards are completed properly
  473. //[NSTask launchedTaskWithLaunchPath:@"/bin/bash" arguments:@[@"/bin/rm", @"/var/containers/Data/System/*/Library/Caches/com.apple.LaunchServices*.csstore"]];
  474. //FIXME: i dont like calling this chmod +x shell script
  475. //its probably a security risk of some type, but the line above
  476. //didnt work properly and i couldnt figure out why
  477. [NSTask launchedTaskWithLaunchPath:@"/bin/bash" arguments:@[@"/usr/bin/rmcache"]];
  478. if (removedApps)
  479. {
  480. NSLog(@"app ids were deleted: %@", deletedApps);
  481. [deletedApps enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  482. [mutableWhiteList removeObject:obj];
  483. [recentDeletions addObject:obj];
  484. NSLog(@"removing app state with id: %@", obj);
  485. [appDepot _removeAppStateForIdentifier:obj];
  486. }];
  487. } else {
  488. //no removed apps, need to make sure they don't mess with our reload anymore
  489. [[NSFileManager defaultManager] removeItemAtPath:recentlyDeleted error:nil];
  490. }
  491. /*
  492. _LSPrivateSyncWithMobileInstallation will actually display the application and allow you to launch it, but the AppDepot (or something related to the installed app database) is potentially harshed or out of sync after we do this, which is why we always call the killall at the very bottom of this method, also if apps are
  493. deleted their state is not updated properly without a respring either
  494. */
  495. NSLog(@"before (addedApps || removedApps)");
  496. if((addedApps || removedApps) && [self containsNewApplication:identifierArray]){
  497. NSLog(@"we can haz new app");
  498. [workspace _LSClearSchemaCaches]; //may or may not be necessary
  499. [workspace _LSPrivateRebuildApplicationDatabasesForSystemApps:YES internal:YES user:YES];
  500. [workspace _LSPrivateSyncWithMobileInstallation];
  501. }
  502. }
  503. } else {
  504. is9 = 1;
  505. //tvOS 9
  506. /*
  507. lil bit of extra work for tvOS 9, we completely torch the lsd csstore file,
  508. from there we call a distributed notification that is relayed back to the uicache
  509. binary
  510. as far as i can remember we need to call back to uicache binary to trigger the workspace
  511. methods for recaching applications because of entitlement issues, our uicache
  512. binary that triggers these calls inside of Pineboard has the proper entitlements to
  513. make those calls, while Pineboard doesnt?
  514. */
  515. //clear cache file
  516. NSString *file = @"/var/mobile/Library/Caches/com.apple.LaunchServices-135.csstore";
  517. NSError *removeError = nil;
  518. [manager removeItemAtPath:file error:&removeError];
  519. [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"kjc.AppEnabler.rebuild_appdb" object:nil userInfo:nil];
  520. }
  521. [mutableWhiteList writeToFile:whitelistFile atomically:YES];
  522. [recentDeletions writeToFile:recentlyDeleted atomically:YES];
  523. /*
  524. this is a kludgy hack, we need to wait some time while our code is triggered in
  525. the AppEnabler tweak that injects into MobileInstallation.
  526. the code is triggered by _LSPrivateSyncWithMobileInstallation in tvOS 10+
  527. and _LSPrivateRebuildApplicationDatabasesForSystemApps:* in tvOS 9 respectively.
  528. after that tweak does its magic our applications should be added (or removed) from PBAppDepot
  529. however, at that point the applications are added but still need to be enabled in the depot
  530. below we loop through every application in appArray (all apps in /var/mobile/Applications)
  531. and make sure all of their states are set to enabled in the PBAppDepot.
  532. At that point in tvOS 10+ we still need to 'respring' to get everything loaded and working
  533. without any issues.
  534. in tvOS 9 we don't need to respring, things should appear / disappear without any
  535. extra work.
  536. */
  537. if(addedApps && [self containsNewApplication:identifierArray]){
  538. NSLog(@"we can haz new app");
  539. sleep(5);
  540. for (NSDictionary* o in appArray)
  541. {
  542. char retry=0;
  543. NSString* identifier = o[@"CFBundleIdentifier"];
  544. retry_:
  545. NSLog(@"identifier: %@", identifier);
  546. id existingObject = [installedAppStates objectForKey:identifier];
  547. if (existingObject != nil)
  548. {
  549. [existingObject setValue:[NSNumber numberWithBool:YES] forKey:@"_enabled"];
  550. BOOL isEnabled = [existingObject isEnabled];
  551. //[existingObject setEnabled:YES];
  552. //[installedAppStates setObject: existingObject forKey: identifier];
  553. } else {
  554. //[appDepot _addAppStateForIdentifier:identifier];
  555. NSLog(@"didnt find object for key: %@, error!!!!!!", identifier);
  556. if(retry == 0)
  557. {
  558. NSLog(@"retry");
  559. //NSLog(@"try to manually kick start substrate!");
  560. //[NSTask launchedTaskWithLaunchPath:@"/usr/bin/nitoHelper" arguments:@[@"substrate", @"1", @"2"]];
  561. //is9 = false;
  562. ///usr/bin/cynject 1 /Library/Frameworks/CydiaSubstrate.framework/Libraries/SubstrateLauncher.dylib
  563. retry = 1;
  564. goto retry_;
  565. }
  566. }
  567. }
  568. }
  569. //this notification is received by uicache binary so its knows that its safe to exit the runloop it creates so it can relay notifications back and forth during this convoluted process.
  570. [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"kjc.AppEnabler.done" object:nil userInfo:nil];
  571. //it beats killall -u mobile... right?
  572. //we dont have system() call anymore and posix_spawn is a pain to configure.
  573. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
  574. if (!is9){
  575. //[@"science" writeToFile:@"/var/mobile/Library/Preferences/itshappening" atomically:YES encoding:NSUTF8StringEncoding error:nil];
  576. [NSTask launchedTaskWithLaunchPath:whichKill arguments:@[@"-9", @"backboardd"]];
  577. //[NSTask launchedTaskWithLaunchPath:whichKill arguments:@[@"-9", @"PineBoard", @"HeadBoard", @"lsd"]];
  578. }
  579. });
  580. }
  581. @end