@@ -125,48 +125,116 @@ class OfficialCertificateManager: ObservableObject {
125125 do {
126126 try FileManager . default. createDirectory ( at: tempDir, withIntermediateDirectories: true , attributes: nil )
127127
128+ certs [ index] . status = " Downloading zip... "
129+ currentStatus = certs [ index] . status
128130 let ( zipData, _) = try await URLSession . shared. data ( from: cert. downloadURL)
129131 let tempZipURL = tempDir. appendingPathComponent ( " cert.zip " )
130132 try zipData. write ( to: tempZipURL)
131133
134+ certs [ index] . status = " Unzipping... "
135+ currentStatus = certs [ index] . status
132136 // Unzip using ZIPFoundation
133137 try FileManager . default. unzipItem ( at: tempZipURL, to: tempDir)
134138
135- // Find the extraction directory (root or single subdir)
136- let contents = try FileManager . default. contentsOfDirectory ( at: tempDir, includingPropertiesForKeys: nil , options: . skipsHiddenFiles)
137- var searchDir = tempDir
138- if contents. count == 1 {
139- let firstItem = contents [ 0 ]
139+ // Recursively enumerate files and ignore any path that contains __MACOSX
140+ var p12Urls : [ URL ] = [ ]
141+ var provUrls : [ URL ] = [ ]
142+ var txtUrls : [ URL ] = [ ]
143+
144+ let enumerator = FileManager . default. enumerator ( at: tempDir, includingPropertiesForKeys: nil , options: [ . skipsHiddenFiles] , errorHandler: nil )
145+ while let item = enumerator? . nextObject ( ) as? URL {
146+ // skip anything in a __MACOSX folder
147+ if item. pathComponents. contains ( " __MACOSX " ) { continue }
148+ // only consider files
140149 var isDir : ObjCBool = false
141- if FileManager . default. fileExists ( atPath: firstItem. path, isDirectory: & isDir) , isDir. boolValue {
142- searchDir = firstItem
150+ if FileManager . default. fileExists ( atPath: item. path, isDirectory: & isDir) , isDir. boolValue { continue }
151+
152+ let ext = item. pathExtension. lowercased ( )
153+ if ext == " p12 " {
154+ p12Urls. append ( item)
155+ } else if ext == " mobileprovision " {
156+ provUrls. append ( item)
157+ } else if ext == " txt " {
158+ txtUrls. append ( item)
143159 }
144160 }
145161
146- let fileContents = try FileManager . default. contentsOfDirectory ( at: searchDir, includingPropertiesForKeys: nil , options: . skipsHiddenFiles)
147- var p12URL : URL ?
148- var provURL : URL ?
149- var txtURL : URL ?
162+ // Helper: extract the highest integer from a filename (returns nil if none)
163+ func highestNumber( in string: String ) -> Int ? {
164+ do {
165+ let regex = try NSRegularExpression ( pattern: " \\ d+ " , options: [ ] )
166+ let ns = string as NSString
167+ let matches = regex. matches ( in: string, options: [ ] , range: NSRange ( location: 0 , length: ns. length) )
168+ let ints = matches. compactMap { match -> Int ? in
169+ let numStr = ns. substring ( with: match. range)
170+ return Int ( numStr)
171+ }
172+ return ints. max ( )
173+ } catch {
174+ return nil
175+ }
176+ }
150177
151- for url in fileContents {
152- let ext = url. pathExtension. lowercased ( )
153- if ext == " p12 " { p12URL = url }
154- else if ext == " mobileprovision " { provURL = url }
155- else if ext == " txt " { txtURL = url }
178+ // Choose mobile provision: if multiple, pick one with highest number in filename, otherwise random
179+ var chosenProvURL : URL ?
180+ if provUrls. count == 1 {
181+ chosenProvURL = provUrls. first
182+ } else if provUrls. count > 1 {
183+ // Map each URL to its highest number (if any)
184+ var best : ( url: URL , num: Int ? ) ?
185+ for u in provUrls {
186+ let name = u. lastPathComponent
187+ let num = highestNumber ( in: name)
188+ if best == nil {
189+ best = ( u, num)
190+ } else {
191+ switch ( best!. num, num) {
192+ case ( nil , nil ) :
193+ // keep current best (we'll pick random fallback below if none have numbers)
194+ break
195+ case ( nil , . some) :
196+ best = ( u, num)
197+ case ( . some( let a) , . some( let b) ) :
198+ if b > a { best = ( u, num) }
199+ case ( . some, nil ) :
200+ break
201+ }
202+ }
203+ }
204+ if let bestNum = best? . num {
205+ // There was at least one with a number — pick the one with max number
206+ let maxNumber = bestNum
207+ if let pick = provUrls. first ( where: { highestNumber ( in: $0. lastPathComponent) == maxNumber } ) {
208+ chosenProvURL = pick
209+ }
210+ } else {
211+ // no numbers found in any filename — pick random
212+ chosenProvURL = provUrls. randomElement ( )
213+ }
156214 }
157215
158- guard let p12U = p12URL, let provU = provURL, let txtU = txtURL else {
216+ // Basic selection for p12 and txt: pick the first found (could be improved if you want)
217+ let chosenP12URL = p12Urls. first
218+ let chosenTxtURL = txtUrls. first
219+
220+ // Validate that we have required files
221+ guard let p12U = chosenP12URL, let provU = chosenProvURL, let txtU = chosenTxtURL else {
159222 certs [ index] . status = " Error: Missing p12, mobileprovision or txt "
160223 currentStatus = certs [ index] . status
161224 throw NSError ( domain: " MissingFiles " , code: 1 , userInfo: [ NSLocalizedDescriptionKey: " Zip missing p12, provision, or txt " ] )
162225 }
163226
227+ certs [ index] . status = " Reading files... "
228+ currentStatus = certs [ index] . status
229+
164230 let txtData = try Data ( contentsOf: txtU)
165231 let password = String ( data: txtData, encoding: . utf8) ? . trimmingCharacters ( in: . whitespacesAndNewlines) ?? " "
166232
167233 let p12Data = try Data ( contentsOf: p12U)
168234 let provData = try Data ( contentsOf: provU)
169235
236+ certs [ index] . status = " Verifying certificate... "
237+ currentStatus = certs [ index] . status
170238 let result = CertificatesManager . check ( p12Data: p12Data, password: password, mobileProvisionData: provData)
171239
172240 switch result {
0 commit comments