2019-06-10 15:03:47 -07:00
//
// R e s i g n 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
//
// C r e a t e d b y R i l e y T e s t u t o n 6 / 7 / 1 9 .
// 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 Roxas
2023-02-19 08:06:33 -08:00
import SwiftUI
import ZIPFoundation
2019-06-10 15:03:47 -07:00
2020-09-03 16:39:08 -07:00
import AltStoreCore
2019-06-10 15:03:47 -07:00
import AltSign
@objc ( ResignAppOperation )
2023-01-04 09:52:12 -05:00
final class ResignAppOperation : ResultOperation < ALTApplication >
2019-06-10 15:03:47 -07:00
{
2023-02-19 08:06:33 -08:00
static var skipResign : Bool = false
static var skipResignBinding : Binding < Bool > { Binding < Bool > ( get : { skipResign } , set : { skipResign = $0 } ) }
2020-03-06 17:08:35 -08:00
let context : InstallAppOperationContext
2019-06-10 15:03:47 -07:00
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 ( )
self . progress . totalUnitCount = 3
}
override func main ( )
{
super . main ( )
2019-06-21 11:20:03 -07:00
if let error = self . context . error
{
self . finish ( . failure ( error ) )
return
}
guard
2019-07-28 15:08:13 -07:00
let app = self . context . app ,
2020-03-06 17:08:35 -08:00
let profiles = self . context . provisioningProfiles ,
let team = self . context . team ,
let certificate = self . context . certificate
2019-06-21 11:20:03 -07:00
else { return self . finish ( . failure ( OperationError . invalidParameters ) ) }
2020-03-06 17:08:35 -08:00
// P r e p a r e a p p b u n d l e
let prepareAppProgress = Progress . discreteProgress ( totalUnitCount : 2 )
self . progress . addChild ( prepareAppProgress , withPendingUnitCount : 3 )
let prepareAppBundleProgress = self . prepareAppBundle ( for : app , profiles : profiles ) { ( result ) in
guard let appBundleURL = self . process ( result ) else { return }
2019-06-21 11:20:03 -07:00
2023-02-19 08:06:33 -08:00
if ResignAppOperation . skipResign {
print ( " ⚠️ WARNING: Skipping resign. Unless you correctly resigned the IPA before installing it, things will not work! Also, this might crash SideStore. You have been warned! " )
let ipaFile = self . context . temporaryDirectory . appendingPathComponent ( " App.ipa " )
let archive = Archive ( url : ipaFile , accessMode : . create ) !
for case let fileURL as URL in FileManager . default . enumerator ( at : appBundleURL , includingPropertiesForKeys : [ ] ) ! {
let relative = fileURL . description . replacingOccurrences ( of : appBundleURL . description , with : " " ) . removingPercentEncoding !
try ! archive . addEntry ( with : " Payload/App.app \( relative ) " , fileURL : fileURL )
}
let destinationURL = InstalledApp . refreshedIPAURL ( for : app )
try ! FileManager . default . copyItem ( at : ipaFile , to : destinationURL , shouldReplace : true )
// U s e a p p B u n d l e U R L s i n c e w e n e e d a n a p p b u n d l e , n o t . i p a .
let resignedApplication = ALTApplication ( fileURL : appBundleURL ) !
self . finish ( . success ( resignedApplication ) )
return
}
2020-03-06 17:08:35 -08:00
print ( " Resigning App: " , self . context . bundleIdentifier )
2020-01-21 15:12:48 -08:00
2020-03-06 17:08:35 -08:00
// R e s i g n a p p b u n d l e
let resignProgress = self . resignAppBundle ( at : appBundleURL , team : team , certificate : certificate , profiles : Array ( profiles . values ) ) { ( result ) in
guard let resignedURL = self . process ( result ) else { return }
2019-06-10 15:03:47 -07:00
2020-03-06 17:08:35 -08:00
// F i n i s h
do
{
let destinationURL = InstalledApp . refreshedIPAURL ( for : app )
try FileManager . default . copyItem ( at : resignedURL , to : destinationURL , shouldReplace : true )
2019-07-28 15:08:13 -07:00
2020-03-06 17:08:35 -08:00
// U s e a p p B u n d l e U R L s i n c e w e n e e d a n a p p b u n d l e , n o t . i p a .
guard let resignedApplication = ALTApplication ( fileURL : appBundleURL ) else { throw OperationError . invalidApp }
self . finish ( . success ( resignedApplication ) )
}
catch
{
self . finish ( . failure ( error ) )
2019-06-10 15:03:47 -07:00
}
}
2020-03-06 17:08:35 -08:00
prepareAppProgress . addChild ( resignProgress , withPendingUnitCount : 1 )
2019-06-10 15:03:47 -07:00
}
2020-03-06 17:08:35 -08:00
prepareAppProgress . addChild ( prepareAppBundleProgress , withPendingUnitCount : 1 )
2019-06-10 15:03:47 -07:00
}
func process < T > ( _ result : Result < T , Error > ) -> T ?
{
switch result
{
case . failure ( let error ) :
self . finish ( . failure ( error ) )
return nil
case . success ( let value ) :
guard ! self . isCancelled else {
self . finish ( . failure ( OperationError . cancelled ) )
return nil
}
return value
}
}
}
private extension ResignAppOperation
{
2019-07-28 15:08:13 -07:00
func prepareAppBundle ( for app : ALTApplication , profiles : [ String : ALTProvisioningProfile ] , completionHandler : @ escaping ( Result < URL , Error > ) -> Void ) -> Progress
2019-06-10 15:03:47 -07:00
{
let progress = Progress . discreteProgress ( totalUnitCount : 1 )
2019-07-28 15:08:13 -07:00
let bundleIdentifier = app . bundleIdentifier
let openURL = InstalledApp . openAppURL ( for : app )
2019-06-10 15:03:47 -07:00
2019-07-28 15:08:13 -07:00
let fileURL = app . fileURL
2019-06-21 11:20:03 -07:00
2019-06-25 12:44:48 -07:00
func prepare ( _ bundle : Bundle , additionalInfoDictionaryValues : [ String : Any ] = [ : ] ) throws
{
guard let identifier = bundle . bundleIdentifier else { throw ALTError ( . missingAppBundle ) }
guard let profile = profiles [ identifier ] else { throw ALTError ( . missingProvisioningProfile ) }
2020-06-10 14:58:25 -07:00
guard var infoDictionary = bundle . completeInfoDictionary else { throw ALTError ( . missingInfoPlist ) }
2019-06-25 12:44:48 -07:00
infoDictionary [ kCFBundleIdentifierKey as String ] = profile . bundleIdentifier
2020-05-15 14:44:06 -07:00
infoDictionary [ Bundle . Info . altBundleID ] = identifier
2022-11-02 17:58:59 -07:00
infoDictionary [ Bundle . Info . devicePairingString ] = Bundle . main . object ( forInfoDictionaryKey : " ALTPairingFile " ) as ? String
2020-09-04 16:29:01 -07:00
2019-06-25 12:44:48 -07:00
for ( key , value ) in additionalInfoDictionaryValues
{
infoDictionary [ key ] = value
}
2020-09-04 16:29:01 -07:00
2019-06-25 14:26:09 -07:00
if let appGroups = profile . entitlements [ . appGroups ] as ? [ String ]
{
infoDictionary [ Bundle . Info . appGroups ] = appGroups
2020-09-04 16:29:01 -07:00
// T o k e e p f i l e p r o v i d e r s w o r k i n g , r e m a p t h e N S E x t e n s i o n F i l e P r o v i d e r D o c u m e n t G r o u p , i f t h e r e i s o n e .
if var extensionInfo = infoDictionary [ " NSExtension " ] as ? [ String : Any ] ,
let appGroup = extensionInfo [ " NSExtensionFileProviderDocumentGroup " ] as ? String ,
let localAppGroup = appGroups . filter ( { $0 . contains ( appGroup ) } ) . min ( by : { $0 . count < $1 . count } )
{
extensionInfo [ " NSExtensionFileProviderDocumentGroup " ] = localAppGroup
infoDictionary [ " NSExtension " ] = extensionInfo
}
2019-06-25 14:26:09 -07:00
}
2019-12-17 19:17:45 -08:00
// A d d a p p - s p e c i f i c e x p o r t e d U T I s o w e c a n c h e c k l a t e r i f t h i s a p p ( e x t e n s i o n ) i s i n s t a l l e d o r n o t .
let installedAppUTI = [ " UTTypeConformsTo " : [ ] ,
" UTTypeDescription " : " AltStore Installed App " ,
" UTTypeIconFiles " : [ ] ,
" UTTypeIdentifier " : InstalledApp . installedAppUTI ( forBundleIdentifier : profile . bundleIdentifier ) ,
" UTTypeTagSpecification " : [ : ] ] as [ String : Any ]
var exportedUTIs = infoDictionary [ Bundle . Info . exportedUTIs ] as ? [ [ String : Any ] ] ? ? [ ]
exportedUTIs . append ( installedAppUTI )
infoDictionary [ Bundle . Info . exportedUTIs ] = exportedUTIs
2019-06-25 12:44:48 -07:00
try ( infoDictionary as NSDictionary ) . write ( to : bundle . infoPlistURL )
}
2019-06-10 15:03:47 -07:00
DispatchQueue . global ( ) . async {
do
{
2019-07-28 15:08:13 -07:00
let appBundleURL = self . context . temporaryDirectory . appendingPathComponent ( " App.app " )
2019-06-21 11:20:03 -07:00
try FileManager . default . copyItem ( at : fileURL , to : appBundleURL )
2019-06-10 15:03:47 -07:00
// B e c o m e c u r r e n t s o w e c a n o b s e r v e p r o g r e s s f r o m u n z i p A p p B u n d l e ( ) .
progress . becomeCurrent ( withPendingUnitCount : 1 )
2019-06-25 12:44:48 -07:00
guard let appBundle = Bundle ( url : appBundleURL ) else { throw ALTError ( . missingAppBundle ) }
2020-06-10 14:58:25 -07:00
guard let infoDictionary = appBundle . completeInfoDictionary else { throw ALTError ( . missingInfoPlist ) }
2019-06-10 15:03:47 -07:00
var allURLSchemes = infoDictionary [ Bundle . Info . urlTypes ] as ? [ [ String : Any ] ] ? ? [ ]
let altstoreURLScheme = [ " CFBundleTypeRole " : " Editor " ,
" CFBundleURLName " : bundleIdentifier ,
" CFBundleURLSchemes " : [ openURL . scheme ! ] ] as [ String : Any ]
allURLSchemes . append ( altstoreURLScheme )
2019-06-25 12:44:48 -07:00
var additionalValues : [ String : Any ] = [ Bundle . Info . urlTypes : allURLSchemes ]
2020-12-03 14:45:34 -06:00
if app . isAltStoreApp
2019-06-18 18:32:49 -07:00
{
guard let udid = Bundle . main . object ( forInfoDictionaryKey : Bundle . Info . deviceID ) as ? String else { throw OperationError . unknownUDID }
2022-11-02 17:58:59 -07:00
guard let pairingFileString = Bundle . main . object ( forInfoDictionaryKey : Bundle . Info . devicePairingString ) as ? String else { throw OperationError . unknownUDID }
additionalValues [ Bundle . Info . devicePairingString ] = pairingFileString
2019-06-25 12:44:48 -07:00
additionalValues [ Bundle . Info . deviceID ] = udid
2019-09-09 17:40:05 -07:00
additionalValues [ Bundle . Info . serverID ] = UserDefaults . standard . preferredServerID
2019-10-28 13:16:55 -07:00
if
let data = Keychain . shared . signingCertificate ,
let signingCertificate = ALTCertificate ( p12Data : data , password : nil ) ,
let encryptingPassword = Keychain . shared . signingCertificatePassword
{
additionalValues [ Bundle . Info . certificateID ] = signingCertificate . serialNumber
let encryptedData = signingCertificate . encryptedP12Data ( withPassword : encryptingPassword )
try encryptedData ? . write ( to : appBundle . certificateURL , options : . atomic )
}
else
{
// T h e e m b e d d e d c e r t i f i c a t e + c e r t i f i c a t e i d e n t i f i e r a r e a l r e a d y i n a p p b u n d l e , n o n e e d t o u p d a t e t h e m .
}
2019-06-18 18:32:49 -07:00
}
2021-09-01 16:46:39 -05:00
else if infoDictionary . keys . contains ( Bundle . Info . deviceID ) , let udid = Bundle . main . object ( forInfoDictionaryKey : Bundle . Info . deviceID ) as ? String
{
// T h e r e i s a n A L T D e v i c e I D e n t r y , s o a s s u m e t h e a p p i s u s i n g A l t K i t a n d r e p l a c e i t w i t h t h e d e v i c e ' s U D I D .
additionalValues [ Bundle . Info . deviceID ] = udid
2021-10-04 16:06:32 -07:00
additionalValues [ Bundle . Info . serverID ] = UserDefaults . standard . preferredServerID
2021-09-01 16:46:39 -05:00
}
2019-06-18 18:32:49 -07:00
2020-10-01 14:09:45 -07:00
let iconScale = Int ( UIScreen . main . scale )
if let alternateIconURL = self . context . alternateIconURL ,
case let data = try Data ( contentsOf : alternateIconURL ) ,
let image = UIImage ( data : data ) ,
let icon = image . resizing ( toFill : CGSize ( width : 60 * iconScale , height : 60 * iconScale ) ) ,
let iconData = icon . pngData ( )
{
let iconName = " AltIcon "
let iconURL = appBundleURL . appendingPathComponent ( iconName + " @ \( iconScale ) x.png " )
try iconData . write ( to : iconURL , options : . atomic )
let iconDictionary = [ " CFBundlePrimaryIcon " : [ " CFBundleIconFiles " : [ iconName ] ] ]
additionalValues [ " CFBundleIcons " ] = iconDictionary
}
2019-06-25 12:44:48 -07:00
// P r e p a r e a p p
try prepare ( appBundle , additionalInfoDictionaryValues : additionalValues )
if let directory = appBundle . builtInPlugInsURL , let enumerator = FileManager . default . enumerator ( at : directory , includingPropertiesForKeys : nil , options : [ . skipsSubdirectoryDescendants ] )
{
for case let fileURL as URL in enumerator
{
guard let appExtension = Bundle ( url : fileURL ) else { throw ALTError ( . missingAppBundle ) }
try prepare ( appExtension )
}
}
2019-06-10 15:03:47 -07:00
completionHandler ( . success ( appBundleURL ) )
}
catch
{
completionHandler ( . failure ( error ) )
}
}
return progress
}
2020-03-06 17:08:35 -08:00
func resignAppBundle ( at fileURL : URL , team : ALTTeam , certificate : ALTCertificate , profiles : [ ALTProvisioningProfile ] , completionHandler : @ escaping ( Result < URL , Error > ) -> Void ) -> Progress
2019-06-10 15:03:47 -07:00
{
2020-03-06 17:08:35 -08:00
let signer = ALTSigner ( team : team , certificate : certificate )
2019-06-25 12:44:48 -07:00
let progress = signer . signApp ( at : fileURL , provisioningProfiles : profiles ) { ( success , error ) in
2019-06-10 15:03:47 -07:00
do
{
try Result ( success , error ) . get ( )
let ipaURL = try FileManager . default . zipAppBundle ( at : fileURL )
completionHandler ( . success ( ipaURL ) )
}
catch
{
completionHandler ( . failure ( error ) )
}
}
return progress
}
}