2019-06-10 15:03:47 -07:00
//
// I n s t a l l A p p O p e r a t i o n . s w i f t
// A l t S t o r e
//
2019-06-21 11:20:03 -07:00
// C r e a t e d b y R i l e y T e s t u t o n 6 / 1 9 / 1 9 .
2019-06-10 15:03:47 -07:00
// C o p y r i g h t © 2 0 1 9 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import Foundation
import Network
2020-09-03 16:39:08 -07:00
import AltStoreCore
2019-07-28 15:08:13 -07:00
import AltSign
2019-06-21 11:20:03 -07:00
import Roxas
2023-04-01 16:02:12 -07:00
import minimuxer
2019-06-10 15:03:47 -07:00
@objc ( InstallAppOperation )
2023-01-04 09:52:12 -05:00
final class InstallAppOperation : ResultOperation < InstalledApp >
2019-06-10 15:03:47 -07:00
{
2020-03-06 17:08:35 -08:00
let context : InstallAppOperationContext
2019-06-10 15:03:47 -07:00
2019-12-11 12:26:48 -08:00
private var didCleanUp = false
2020-03-06 17:08:35 -08:00
init ( context : InstallAppOperationContext )
2019-06-10 15:03:47 -07:00
{
2019-06-21 11:20:03 -07:00
self . context = context
2019-06-10 15:03:47 -07:00
super . init ( )
2019-06-21 11:20:03 -07:00
self . progress . totalUnitCount = 100
2019-06-10 15:03:47 -07:00
}
override func main ( )
{
super . main ( )
2019-06-21 11:20:03 -07:00
if let error = self . context . error
{
self . finish ( . failure ( error ) )
return
}
2019-06-10 15:03:47 -07:00
2019-06-21 11:20:03 -07:00
guard
2020-03-06 17:08:35 -08:00
let certificate = self . context . certificate ,
2022-11-02 17:58:59 -07:00
let resignedApp = self . context . resignedApp
2019-06-21 11:20:03 -07:00
else { return self . finish ( . failure ( OperationError . invalidParameters ) ) }
2019-07-28 15:08:13 -07:00
let backgroundContext = DatabaseManager . shared . persistentContainer . newBackgroundContext ( )
backgroundContext . perform {
2020-01-24 15:03:16 -08:00
/* A p p */
2019-07-28 15:08:13 -07:00
let installedApp : InstalledApp
// F e t c h + u p d a t e r a t h e r t h a n i n s e r t + r e s o l v e m e r g e c o n f l i c t s t o p r e v e n t p o t e n t i a l c o n t e x t - l e v e l c o n f l i c t s .
if let app = InstalledApp . first ( satisfying : NSPredicate ( format : " %K == %@ " , # keyPath ( InstalledApp . bundleIdentifier ) , self . context . bundleIdentifier ) , in : backgroundContext )
2019-06-10 15:03:47 -07:00
{
2019-07-28 15:08:13 -07:00
installedApp = app
}
else
{
2020-03-06 17:08:35 -08:00
installedApp = InstalledApp ( resignedApp : resignedApp , originalBundleIdentifier : self . context . bundleIdentifier , certificateSerialNumber : certificate . serialNumber , context : backgroundContext )
2019-07-28 15:08:13 -07:00
}
2020-03-06 17:08:35 -08:00
installedApp . update ( resignedApp : resignedApp , certificateSerialNumber : certificate . serialNumber )
2020-10-01 11:51:39 -07:00
installedApp . needsResign = false
2020-01-24 15:03:16 -08:00
if let team = DatabaseManager . shared . activeTeam ( in : backgroundContext )
{
installedApp . team = team
}
/* A p p E x t e n s i o n s */
2020-01-21 16:53:34 -08:00
var installedExtensions = Set < InstalledExtension > ( )
if
let bundle = Bundle ( url : resignedApp . fileURL ) ,
let directory = bundle . builtInPlugInsURL ,
let enumerator = FileManager . default . enumerator ( at : directory , includingPropertiesForKeys : nil , options : [ . skipsSubdirectoryDescendants ] )
{
for case let fileURL as URL in enumerator
{
guard let appExtensionBundle = Bundle ( url : fileURL ) else { continue }
guard let appExtension = ALTApplication ( fileURL : appExtensionBundle . bundleURL ) else { continue }
2020-01-24 15:03:16 -08:00
let parentBundleID = self . context . bundleIdentifier
let resignedParentBundleID = resignedApp . bundleIdentifier
let resignedBundleID = appExtension . bundleIdentifier
let originalBundleID = resignedBundleID . replacingOccurrences ( of : resignedParentBundleID , with : parentBundleID )
2022-12-03 17:25:15 -05:00
print ( " `parentBundleID`: \( parentBundleID ) " )
print ( " `resignedParentBundleID`: \( resignedParentBundleID ) " )
print ( " `resignedBundleID`: \( resignedBundleID ) " )
print ( " `originalBundleID`: \( originalBundleID ) " )
2020-01-21 16:53:34 -08:00
let installedExtension : InstalledExtension
2020-01-24 15:03:16 -08:00
if let appExtension = installedApp . appExtensions . first ( where : { $0 . bundleIdentifier = = originalBundleID } )
2020-01-21 16:53:34 -08:00
{
installedExtension = appExtension
}
else
{
2020-01-24 15:03:16 -08:00
installedExtension = InstalledExtension ( resignedAppExtension : appExtension , originalBundleIdentifier : originalBundleID , context : backgroundContext )
2020-01-21 16:53:34 -08:00
}
2020-01-24 15:03:16 -08:00
installedExtension . update ( resignedAppExtension : appExtension )
2020-01-21 16:53:34 -08:00
installedExtensions . insert ( installedExtension )
}
}
installedApp . appExtensions = installedExtensions
2021-10-25 22:27:30 -07:00
self . context . beginInstallationHandler ? ( installedApp )
2019-12-11 12:26:48 -08:00
// T e m p o r a r y d i r e c t o r y a n d r e s i g n e d . i p a n o l o n g e r n e e d e d , s o d e l e t e t h e m n o w t o e n s u r e A l t S t o r e d o e s n ' t q u i t b e f o r e w e g e t t h e c h a n c e t o .
self . cleanUp ( )
2020-03-11 14:43:19 -07:00
var activeProfiles : Set < String > ?
if let sideloadedAppsLimit = UserDefaults . standard . activeAppsLimit
{
// W h e n i n s t a l l i n g t h e s e n e w p r o f i l e s , A l t S e r v e r w i l l r e m o v e a l l n o n - a c t i v e p r o f i l e s t o e n s u r e w e r e m a i n u n d e r l i m i t .
let fetchRequest = InstalledApp . activeAppsFetchRequest ( )
fetchRequest . includesPendingChanges = false
var activeApps = InstalledApp . fetch ( fetchRequest , in : backgroundContext )
if ! activeApps . contains ( installedApp )
{
2020-05-17 23:36:30 -07:00
let activeAppsCount = activeApps . map { $0 . requiredActiveSlots } . reduce ( 0 , + )
2020-03-11 14:43:19 -07:00
2020-05-17 23:36:30 -07:00
let availableActiveApps = max ( sideloadedAppsLimit - activeAppsCount , 0 )
if installedApp . requiredActiveSlots <= availableActiveApps
2020-03-11 14:43:19 -07:00
{
// T h i s a p p h a s n o t b e e n e x p l i c i t l y a c t i v a t e d , b u t t h e r e a r e e n o u g h s l o t s a v a i l a b l e ,
// s o i m p l i c i t l y a c t i v a t e i t .
installedApp . isActive = true
activeApps . append ( installedApp )
}
else
{
installedApp . isActive = false
}
}
activeProfiles = Set ( activeApps . flatMap { ( installedApp ) -> [ String ] in
let appExtensionProfiles = installedApp . appExtensions . map { $0 . resignedBundleIdentifier }
return [ installedApp . resignedBundleIdentifier ] + appExtensionProfiles
} )
}
2023-04-13 07:20:36 -07:00
var installing = true
if installedApp . storeApp ? . bundleIdentifier = = Bundle . Info . appbundleIdentifier {
2023-05-06 19:25:37 -07:00
// R e i n s t a l l i n g o u r s e l f w i l l h a n g u n t i l w e l e a v e t h e a p p , s o w e n e e d t o e x i t i t w i t h o u t f o r c e c l o s i n g
2023-04-13 07:20:36 -07:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 3 ) {
2023-05-06 19:25:37 -07:00
if UIApplication . shared . applicationState != . active {
print ( " We are not in the foreground, let's not do anything " )
return
}
if ! installing {
2023-04-13 07:20:36 -07:00
print ( " Installing finished " )
2023-05-06 19:25:37 -07:00
return
}
print ( " We are still installing after 3 seconds " )
UNUserNotificationCenter . current ( ) . getNotificationSettings { settings in
switch ( settings . authorizationStatus ) {
case . authorized , . ephemeral , . provisional :
print ( " Notifications are enabled " )
let content = UNMutableNotificationContent ( )
content . title = " Refreshing... "
content . body = " To finish refreshing, SideStore must be moved to the background, which it does by opening Safari. Please reopen SideStore after it is done refreshing! "
let notification = UNNotificationRequest ( identifier : Bundle . Info . appbundleIdentifier + " .FinishRefreshNotification " , content : content , trigger : UNTimeIntervalNotificationTrigger ( timeInterval : 2 , repeats : false ) )
UNUserNotificationCenter . current ( ) . add ( notification )
DispatchQueue . main . async { UIApplication . shared . open ( URL ( string : " x-web-search:// " ) ! ) }
break
default :
print ( " Notifications are not enabled " )
let alert = UIAlertController ( title : " Finish Refresh " , message : " To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen or open Safari by pressing Continue. Please reopen SideStore after doing this. " , preferredStyle : . alert )
alert . addAction ( UIAlertAction ( title : NSLocalizedString ( " Continue " , comment : " " ) , style : . default , handler : { _ in
print ( " Opening Safari " )
DispatchQueue . main . async { UIApplication . shared . open ( URL ( string : " x-web-search:// " ) ! ) }
} ) )
DispatchQueue . main . async {
let keyWindow = UIApplication . shared . windows . filter { $0 . isKeyWindow } . first
if var topController = keyWindow ? . rootViewController {
while let presentedViewController = topController . presentedViewController {
topController = presentedViewController
}
topController . present ( alert , animated : true )
} else {
print ( " No key window? Let's just open Safari " )
UIApplication . shared . open ( URL ( string : " x-web-search:// " ) ! )
}
}
break
}
2023-04-13 07:20:36 -07:00
}
}
}
2023-04-01 16:02:12 -07:00
do {
try install_ipa ( installedApp . bundleIdentifier )
2023-04-13 07:20:36 -07:00
installing = false
2023-04-01 16:02:12 -07:00
} catch {
2023-04-13 07:20:36 -07:00
installing = false
2023-04-11 21:04:07 -07:00
return self . finish ( . failure ( error ) )
2019-06-10 15:03:47 -07:00
}
2023-04-01 16:02:12 -07:00
installedApp . refreshedDate = Date ( )
self . finish ( . success ( installedApp ) )
2019-06-10 15:03:47 -07:00
}
}
2019-12-11 12:26:48 -08:00
override func finish ( _ result : Result < InstalledApp , Error > )
{
self . cleanUp ( )
2020-06-04 19:53:10 -07:00
// O n l y r e m o v e r e f r e s h e d I P A w h e n f i n i s h e d .
if let app = self . context . app
{
let fileURL = InstalledApp . refreshedIPAURL ( for : app )
do
{
try FileManager . default . removeItem ( at : fileURL )
2023-04-01 16:02:12 -07:00
print ( " Removed refreshed IPA " )
2020-06-04 19:53:10 -07:00
}
catch
{
2023-04-01 16:02:12 -07:00
print ( " Failed to remove refreshed .ipa: \( error ) " )
2020-06-04 19:53:10 -07:00
}
}
2019-12-11 12:26:48 -08:00
super . finish ( result )
}
}
private extension InstallAppOperation
{
func cleanUp ( )
{
guard ! self . didCleanUp else { return }
self . didCleanUp = true
do
{
try FileManager . default . removeItem ( at : self . context . temporaryDirectory )
}
catch
{
print ( " Failed to remove temporary directory. " , error )
}
}
2019-06-10 15:03:47 -07:00
}