├── App Resources
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Base.lproj
│ └── LaunchScreen.storyboard
└── Info.plist
├── Source
├── Bridging.h
├── main.swift
├── Networking
│ ├── HTTPEndpoint.swift
│ ├── HTTPMethod.swift
│ ├── HTTPPayload.swift
│ ├── RedirectEndpoint.swift
│ ├── HTTPRequest.swift
│ ├── URLSession.swift
│ ├── HTTPResponse.swift
│ ├── HTTPDownloadClient.swift
│ └── HTTPClient.swift
├── Logger
│ ├── Logging.swift
│ ├── ConsoleLogger.swift
│ └── LogLevel.swift
├── IPATool.swift
├── ipatool.entitlements
├── iTunes
│ ├── iTunesEndpoint.swift
│ ├── iTunesResponse.swift
│ ├── iTunesRequest.swift
│ └── iTunesClient.swift
├── Store
│ ├── StoreEndpoint.swift
│ ├── StoreRequest.swift
│ ├── StoreResponse.swift
│ └── StoreClient.swift
├── Commands
│ ├── Search.swift
│ └── Download.swift
└── Signature
│ └── SignatureClient.swift
├── ipatool.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ └── ipatool.xcscheme
└── project.pbxproj
├── LICENSE
├── README.md
└── .gitignore
/App Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Source/Bridging.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
--------------------------------------------------------------------------------
/Source/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // Created on 6/30/20
4 | //
5 |
6 | import Foundation
7 |
8 | IPATool.main()
9 |
10 |
--------------------------------------------------------------------------------
/ipatool.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Source/Networking/HTTPEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPEndpoint.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol HTTPEndpoint {
11 | var url: URL { get }
12 | }
13 |
--------------------------------------------------------------------------------
/Source/Networking/HTTPMethod.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPMethod.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum HTTPMethod: String {
11 | case get = "GET"
12 | case post = "POST"
13 | }
14 |
--------------------------------------------------------------------------------
/Source/Networking/HTTPPayload.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPPayload.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum HTTPPayload {
11 | case xml([String: String])
12 | case urlEncoding([String: String])
13 | }
14 |
--------------------------------------------------------------------------------
/ipatool.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Source/Logger/Logging.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol Logging {
11 | func compile(_ message: String, level: LogLevel) -> String
12 | func log(_ message: String, prefix: String, level: LogLevel)
13 | func log(_ message: String, level: LogLevel)
14 | }
15 |
--------------------------------------------------------------------------------
/Source/Networking/RedirectEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedirectEndpoint.swift
3 | // ipatool
4 | //
5 | // Created by Vladislav Iakovlev on 06.10.25.
6 | //
7 |
8 | import Foundation
9 |
10 | class RedirectEndpoint: HTTPEndpoint {
11 | var url: URL
12 |
13 | init?(location: Any?) {
14 | guard let location = location as? String, let url = URL(string: location) else {
15 | return nil
16 | }
17 |
18 | self.url = url
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Source/Networking/HTTPRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPRequest.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol HTTPRequest {
11 | var method: HTTPMethod { get }
12 | var endpoint: HTTPEndpoint { get }
13 | var headers: [String: String] { get }
14 | var payload: HTTPPayload? { get }
15 | }
16 |
17 | extension HTTPRequest {
18 | var headers: [String: String] { [:] }
19 | var payload: HTTPPayload? { nil }
20 | }
21 |
--------------------------------------------------------------------------------
/Source/IPATool.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IPATool.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import ArgumentParser
9 |
10 | struct IPATool: ParsableCommand {
11 | static var configuration: CommandConfiguration {
12 | return .init(commandName: "ipatool",
13 | abstract: "A cli tool for interacting with Apple's ipa files.",
14 | version: "1.0.0",
15 | subcommands: [Download.self, Search.self])
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Source/ipatool.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | application-identifier
6 | -
7 | com.apple.developer.team-identifier
8 | -
9 | com.apple.private.security.no-container
10 |
11 | com.apple.private.skip-library-validation
12 |
13 | platform-application
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Source/iTunes/iTunesEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // iTunesEndpoint.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum iTunesEndpoint {
11 | case search
12 | case lookup
13 | }
14 |
15 | extension iTunesEndpoint: HTTPEndpoint {
16 | var url: URL {
17 | var components = URLComponents(string: path)!
18 | components.scheme = "https"
19 | components.host = "itunes.apple.com"
20 | return components.url!
21 | }
22 |
23 | private var path: String {
24 | switch self {
25 | case .search:
26 | return "/search"
27 | case .lookup:
28 | return "/lookup"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Source/Logger/ConsoleLogger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConsoleLogger.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | final class ConsoleLogger: Logging {
11 | private let level: LogLevel
12 |
13 | init(level: LogLevel) {
14 | self.level = level
15 | }
16 |
17 | func log(_ message: String, level: LogLevel) {
18 | guard level <= self.level else { return }
19 | print(compile(message, level: level))
20 | }
21 |
22 | func log(_ message: String, prefix: String, level: LogLevel) {
23 | guard level <= self.level else { return }
24 | print("\(prefix)\(compile(message, level: level))")
25 | }
26 |
27 | func compile(_ message: String, level: LogLevel) -> String {
28 | return "==> \(level.prefix)\(message)".trimmingCharacters(in: .whitespacesAndNewlines)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Source/iTunes/iTunesResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // iTunesResponse.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | struct iTunesResponse {
11 | let results: [Result]
12 | let count: Int
13 | }
14 |
15 | extension iTunesResponse {
16 | struct Result {
17 | let bundleIdentifier: String
18 | let version: String
19 | let identifier: Int
20 | let name: String
21 | }
22 | }
23 |
24 | extension iTunesResponse: Codable {
25 | enum CodingKeys: String, CodingKey {
26 | case count = "resultCount"
27 | case results
28 | }
29 | }
30 |
31 | extension iTunesResponse.Result: Codable {
32 | enum CodingKeys: String, CodingKey {
33 | case identifier = "trackId"
34 | case name = "trackName"
35 | case bundleIdentifier = "bundleId"
36 | case version
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Source/iTunes/iTunesRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // iTunesRequest.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum iTunesRequest {
11 | case search(term: String, limit: Int)
12 | case lookup(bundleIdentifier: String)
13 | }
14 |
15 | extension iTunesRequest: HTTPRequest {
16 | var method: HTTPMethod {
17 | return .get
18 | }
19 |
20 | var endpoint: HTTPEndpoint {
21 | switch self {
22 | case .lookup:
23 | return iTunesEndpoint.lookup
24 | case .search:
25 | return iTunesEndpoint.search
26 | }
27 | }
28 |
29 | var payload: HTTPPayload? {
30 | switch self {
31 | case let .lookup(bundleIdentifier):
32 | return .urlEncoding(["media": "software", "bundleId": bundleIdentifier, "limit": "1"])
33 | case let .search(term, limit):
34 | return .urlEncoding(["media": "software", "term": term, "limit": "\(limit)"])
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Source/Store/StoreEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoreEndpoint.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum StoreEndpoint {
11 | case authenticate
12 | case download(guid: String)
13 | }
14 |
15 | extension StoreEndpoint: HTTPEndpoint {
16 | var url: URL {
17 | var components = URLComponents(string: path)!
18 | components.scheme = "https"
19 | components.host = host
20 | return components.url!
21 | }
22 |
23 | private var host: String {
24 | switch self {
25 | case .authenticate:
26 | return "buy.itunes.apple.com"
27 | case .download:
28 | return "p25-buy.itunes.apple.com"
29 | }
30 | }
31 |
32 | private var path: String {
33 | switch self {
34 | case .authenticate:
35 | return "/WebObjects/MZFinance.woa/wa/authenticate"
36 | case let .download(guid):
37 | return "/WebObjects/MZFinance.woa/wa/volumeStoreDownloadProduct?guid=\(guid)"
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Source/Networking/URLSession.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLSession.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol URLSessionInterface {
11 | func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
12 | }
13 |
14 | final class URLSessionNoRedirectDelegate: NSObject, URLSessionDataDelegate {
15 | static var shared = URLSessionNoRedirectDelegate()
16 |
17 | func urlSession(_ session: URLSession,
18 | task: URLSessionTask,
19 | willPerformHTTPRedirection response: HTTPURLResponse,
20 | newRequest request: URLRequest,
21 | completionHandler: @escaping (URLRequest?) -> Void) {
22 | completionHandler(nil)
23 | }
24 | }
25 |
26 | extension URLSession: URLSessionInterface {
27 | static var noRedirect = URLSession(configuration: .default,
28 | delegate: URLSessionNoRedirectDelegate.shared,
29 | delegateQueue: nil)
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Majd Alfhaily
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Source/Logger/LogLevel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogLevel.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 | import ArgumentParser
10 |
11 | enum LogLevel: String, ExpressibleByArgument {
12 | case error
13 | case warning
14 | case info
15 | case debug
16 | }
17 |
18 | extension LogLevel {
19 | var prefix: String {
20 | switch self {
21 | case .error:
22 | return "❌\t[Error] "
23 | case .warning:
24 | return "⚠️\t[Warning] "
25 | case .info:
26 | return "ℹ️\t[Info] "
27 | case .debug:
28 | return "🛠\t[Debug] "
29 | }
30 | }
31 |
32 | var priority: Int {
33 | switch self {
34 | case .error:
35 | return 0
36 | case .warning:
37 | return 1
38 | case .info:
39 | return 2
40 | case .debug:
41 | return 3
42 | }
43 | }
44 | }
45 |
46 | extension LogLevel: Comparable {
47 | static func < (lhs: LogLevel, rhs: LogLevel) -> Bool {
48 | return lhs.priority < rhs.priority
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Source/Networking/HTTPResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPResponse.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | struct HTTPResponse {
11 | let statusCode: Int
12 | let headers: [AnyHashable: Any]
13 | let data: Data?
14 | }
15 |
16 | extension HTTPResponse {
17 | func decode(_ type: T.Type, as decoder: Decoder) throws -> T {
18 | guard let data = data else {
19 | throw Error.noData
20 | }
21 |
22 | switch decoder {
23 | case .json:
24 | let decoder = JSONDecoder()
25 | decoder.userInfo = [.init(rawValue: "data")!: data]
26 | return try decoder.decode(type, from: data)
27 | case .xml:
28 | let decoder = PropertyListDecoder()
29 | decoder.userInfo = [.init(rawValue: "data")!: data]
30 | return try decoder.decode(type, from: data)
31 | }
32 | }
33 | }
34 |
35 | extension HTTPResponse {
36 | enum Decoder {
37 | case json
38 | case xml
39 | }
40 |
41 | enum Error: Swift.Error {
42 | case noData
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## ipatool for iOS
2 | This is a port of Majd Alfhaily's [ipatool](https://github.com/majd/ipatool) adapted to run on iOS
3 |
4 | ### Build / Installation
5 | To build this, make sure you have AppSync installed on your iOS device, and then simply build the "ipatool" scheme (don't bother to change any of the signing capabilities)
6 |
7 | If the build was successful, the app will launch and immediately crash, you can also confirm this by watching xcode's console below as there is a test arguement set. Once built, navigate to the app's bundle directory and copy the 'ipatool' executable to /usr/local/bin, at this point you can feel free to delete the app.
8 |
9 | If you rather just install without building, I've added a pre-compiled .deb to the releases, and you can then read the documentation provided on Majd's repo for a full list of usages
10 |
11 | ### Credits
12 | Obviously Majd Alfhaily for providing such an awesome [tool](https://github.com/majd/ipatool) and a special thank-you to John Coates for providing [flexdecrypt](https://github.com/JohnCoates/flexdecryptl) which this projects file hierarchy is heavily based on
13 |
14 | [My Mastadon](https://mastodon.social/@dlevi)
15 |
16 | ## Troubleshooting
17 | As of today’s date (October 5th, 2023) this is *still* working. If you do happen to have any problems with 2FA, please refer to this [thread](https://github.com/dlevi309/ipatool-ios/issues/2#issuecomment-1078571991)
18 |
19 | If this ever breaks, I’ll push out an update, but at this point it seems unlikely.
--------------------------------------------------------------------------------
/Source/Commands/Search.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Search.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import ArgumentParser
9 | import Foundation
10 |
11 | struct Search: ParsableCommand {
12 | static var configuration: CommandConfiguration {
13 | return .init(abstract: "Search for iOS apps available on the App Store.")
14 | }
15 |
16 | @Option
17 | private var logLevel: LogLevel = .info
18 |
19 | @Option
20 | private var limit: Int = 5
21 |
22 | @Argument(help: "The term to search for.")
23 | var term: String
24 | }
25 |
26 | extension Search {
27 | func run() throws {
28 | let logger = ConsoleLogger(level: logLevel)
29 |
30 | logger.log("Creating HTTP client...", level: .debug)
31 | let httpClient = HTTPClient(urlSession: URLSession.shared)
32 |
33 | logger.log("Creating iTunes client...", level: .debug)
34 | let itunesClient = iTunesClient(httpClient: httpClient)
35 |
36 | do {
37 | logger.log("Searching for '\(term)'...", level: .info)
38 | let results = try itunesClient.search(term: term, limit: limit)
39 |
40 | guard !results.isEmpty else {
41 | logger.log("No results found.", level: .error)
42 | _exit(1)
43 | }
44 |
45 | logger.log("Found \(results.count) results:\n\(results.enumerated().map({ "\($0 + 1). \($1.name): \($1.bundleIdentifier) (\($1.version))." }).joined(separator: "\n"))", level: .info)
46 | } catch {
47 | logger.log("\(error)", level: .debug)
48 | logger.log("An unknown error has occurred.", level: .error)
49 | _exit(1)
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/App Resources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.xccheckout
3 |
4 | ## Build generated
5 | build/
6 | DerivedData/
7 |
8 | ## Various settings
9 | *.pbxuser
10 | !default.pbxuser
11 | *.mode1v3
12 | !default.mode1v3
13 | *.mode2v3
14 | !default.mode2v3
15 | *.perspectivev3
16 | !default.perspectivev3
17 | xcuserdata/
18 |
19 | ## Other
20 | *.moved-aside
21 | *.xcuserstate
22 |
23 | ## Obj-C/Swift specific
24 | *.hmap
25 | *.ipa
26 | *.dSYM.zip
27 | *.dSYM
28 |
29 | ## Playgrounds
30 | timeline.xctimeline
31 | playground.xcworkspace
32 |
33 | # Swift Package Manager
34 | #
35 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
36 | # Packages/
37 | .build/
38 | Package.resolved
39 | Rogue.xcodeproj
40 |
41 | # CocoaPods
42 | #
43 | # We recommend against adding the Pods directory to your .gitignore. However
44 | # you should judge for yourself, the pros and cons are mentioned at:
45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
46 | #
47 | Pods/
48 |
49 | # Carthage
50 | #
51 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
52 | Carthage/Checkouts
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 | fastlane/report.xml
62 | fastlane/Preview.html
63 | fastlane/screenshots
64 | fastlane/test_output
65 |
66 | # Codesigning
67 | *.mobileprovision
68 | *.cer
69 |
70 | ## flexdecrypt
71 |
72 | # Build Generated
73 | /Package/usr/bin/flexdecrypt
74 | /flexdecrypt.deb
--------------------------------------------------------------------------------
/App Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/App Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UISupportedInterfaceOrientations~ipad
55 |
56 | UIInterfaceOrientationPortrait
57 | UIInterfaceOrientationPortraitUpsideDown
58 | UIInterfaceOrientationLandscapeLeft
59 | UIInterfaceOrientationLandscapeRight
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/Source/Networking/HTTPDownloadClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPDownloadClient.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol HTTPDownloadClientInterface {
11 | func download(from source: URL, to target: URL, progress: @escaping (Float) -> Void, completion: @escaping (Result) -> Void)
12 | }
13 |
14 | extension HTTPDownloadClientInterface {
15 | func download(from source: URL, to target: URL, progress: @escaping (Float) -> Void) throws {
16 | let semaphore = DispatchSemaphore(value: 0)
17 | var result: Result?
18 |
19 | download(from: source, to: target, progress: progress) {
20 | result = $0
21 | semaphore.signal()
22 | }
23 |
24 | _ = semaphore.wait(timeout: .distantFuture)
25 |
26 | switch result {
27 | case .none:
28 | throw HTTPClient.Error.timeout
29 | case let .failure(error):
30 | throw error
31 | default:
32 | break
33 | }
34 | }
35 | }
36 |
37 | final class HTTPDownloadClient: NSObject, HTTPDownloadClientInterface {
38 | private var urlSession: URLSession!
39 | private var progressHandler: ((Float) -> Void)?
40 | private var completionHandler: ((Result) -> Void)?
41 | private var targetURL: URL?
42 |
43 | override init() {
44 | super.init()
45 | self.urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
46 | }
47 |
48 | func download(from source: URL, to target: URL, progress: @escaping (Float) -> Void, completion: @escaping (Result) -> Void) {
49 | assert(progressHandler == nil)
50 | assert(completionHandler == nil)
51 | assert(targetURL == nil)
52 |
53 | progressHandler = progress
54 | completionHandler = completion
55 | targetURL = target
56 | urlSession.downloadTask(with: source).resume()
57 | }
58 | }
59 |
60 | extension HTTPDownloadClient: URLSessionDownloadDelegate {
61 | func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
62 | progressHandler?(Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
63 | }
64 |
65 | func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
66 | defer {
67 | progressHandler = nil
68 | completionHandler = nil
69 | targetURL = nil
70 | }
71 |
72 | guard let target = targetURL else {
73 | return completionHandler?(.failure(Error.invalidTarget)) ?? ()
74 | }
75 |
76 | do {
77 | try FileManager.default.moveItem(at: location, to: target)
78 | completionHandler?(.success(()))
79 | } catch {
80 | completionHandler?(.failure(error))
81 | }
82 | }
83 | }
84 |
85 | extension HTTPDownloadClient {
86 | enum Error: Swift.Error {
87 | case invalidTarget
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/ipatool.xcodeproj/xcshareddata/xcschemes/ipatool.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
66 |
68 |
74 |
75 |
76 |
77 |
79 |
80 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/Source/Networking/HTTPClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPClient.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol HTTPClientInterface {
11 | func send(_ request: HTTPRequest, completion: @escaping (Result) -> Void)
12 | }
13 |
14 | extension HTTPClientInterface {
15 | func send(_ request: HTTPRequest) throws -> HTTPResponse {
16 | let semaphore = DispatchSemaphore(value: 0)
17 | var result: Result?
18 |
19 | send(request) {
20 | result = $0
21 | semaphore.signal()
22 | }
23 |
24 | _ = semaphore.wait(timeout: .distantFuture)
25 |
26 | switch result {
27 | case .none:
28 | throw HTTPClient.Error.timeout
29 | case let .failure(error):
30 | throw error
31 | case let .success(response):
32 | return response
33 | }
34 | }
35 | }
36 |
37 | final class HTTPClient: HTTPClientInterface {
38 | private let urlSession: URLSessionInterface
39 |
40 | init(urlSession: URLSessionInterface) {
41 | self.urlSession = urlSession
42 | }
43 |
44 | func send(_ request: HTTPRequest, completion: @escaping (Result) -> Void) {
45 | do {
46 | let urlRequest = try makeURLRequest(from: request)
47 |
48 | urlSession.dataTask(with: urlRequest) { (data, response, error) in
49 | if let error = error {
50 | return completion(.failure(error))
51 | }
52 |
53 | guard let response = response as? HTTPURLResponse else {
54 | return completion(.failure(Error.invalidResponse(response)))
55 | }
56 |
57 | completion(.success(.init(statusCode: response.statusCode, headers: response.allHeaderFields, data: data)))
58 | }.resume()
59 | } catch {
60 | completion(.failure(error))
61 | }
62 | }
63 |
64 | private func makeURLRequest(from request: HTTPRequest) throws -> URLRequest {
65 | var urlRequest = URLRequest(url: request.endpoint.url)
66 | urlRequest.httpMethod = request.method.rawValue
67 |
68 | switch request.payload {
69 | case .none:
70 | urlRequest.httpBody = nil
71 | case let .urlEncoding(propertyList):
72 | urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
73 |
74 | var urlComponents = URLComponents(string: request.endpoint.url.absoluteString)
75 | urlComponents?.queryItems = !propertyList.isEmpty ? propertyList.map { URLQueryItem(name: $0.0, value: $0.1.description) } : nil
76 |
77 | switch request.method {
78 | case .get:
79 | urlRequest.url = urlComponents?.url
80 | case .post:
81 | urlRequest.httpBody = urlComponents?.percentEncodedQuery?.data(using: .utf8, allowLossyConversion: false)
82 | }
83 | case let .xml(value):
84 | urlRequest.setValue("application/xml", forHTTPHeaderField: "Content-Type")
85 | urlRequest.httpBody = try PropertyListSerialization.data(fromPropertyList: value, format: .xml, options: 0)
86 | }
87 |
88 | request.headers.forEach { urlRequest.setValue($0.value, forHTTPHeaderField: $0.key) }
89 |
90 | return urlRequest
91 | }
92 | }
93 |
94 | extension HTTPClient {
95 | enum Error: Swift.Error {
96 | case invalidResponse(URLResponse?)
97 | case timeout
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Source/iTunes/iTunesClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // iTunesClient.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol iTunesClientInterface {
11 | func lookup(bundleIdentifier: String, completion: @escaping (Result) -> Void)
12 | func search(term: String, limit: Int, completion: @escaping (Result<[iTunesResponse.Result], Error>) -> Void)
13 | }
14 |
15 | extension iTunesClientInterface {
16 | func lookup(bundleIdentifier: String) throws -> iTunesResponse.Result {
17 | let semaphore = DispatchSemaphore(value: 0)
18 | var result: Result?
19 |
20 | lookup(bundleIdentifier: bundleIdentifier) {
21 | result = $0
22 | semaphore.signal()
23 | }
24 |
25 | _ = semaphore.wait(timeout: .distantFuture)
26 |
27 | switch result {
28 | case .none:
29 | throw iTunesClient.Error.timeout
30 | case let .failure(error):
31 | throw error
32 | case let .success(result):
33 | return result
34 | }
35 | }
36 |
37 | func search(term: String, limit: Int) throws -> [iTunesResponse.Result] {
38 | let semaphore = DispatchSemaphore(value: 0)
39 | var result: Result<[iTunesResponse.Result], Error>?
40 |
41 | search(term: term, limit: limit) {
42 | result = $0
43 | semaphore.signal()
44 | }
45 |
46 | _ = semaphore.wait(timeout: .distantFuture)
47 |
48 | switch result {
49 | case .none:
50 | throw iTunesClient.Error.timeout
51 | case let .failure(error):
52 | throw error
53 | case let .success(result):
54 | return result
55 | }
56 | }
57 | }
58 |
59 | final class iTunesClient: iTunesClientInterface {
60 | private let httpClient: HTTPClient
61 |
62 | init(httpClient: HTTPClient) {
63 | self.httpClient = httpClient
64 | }
65 |
66 | func lookup(bundleIdentifier: String, completion: @escaping (Result) -> Void) {
67 | let request = iTunesRequest.lookup(bundleIdentifier: bundleIdentifier)
68 |
69 | httpClient.send(request) { result in
70 | switch result {
71 | case let .success(response):
72 | do {
73 | let decoded = try response.decode(iTunesResponse.self, as: .json)
74 | guard let result = decoded.results.first else { return completion(.failure(Error.appNotFound)) }
75 | completion(.success(result))
76 | } catch {
77 | completion(.failure(error))
78 | }
79 | case let .failure(error):
80 | completion(.failure(error))
81 | }
82 | }
83 | }
84 |
85 | func search(term: String, limit: Int, completion: @escaping (Result<[iTunesResponse.Result], Swift.Error>) -> Void) {
86 | let request = iTunesRequest.search(term: term, limit: limit)
87 |
88 | httpClient.send(request) { result in
89 | switch result {
90 | case let .success(response):
91 | do {
92 | let decoded = try response.decode(iTunesResponse.self, as: .json)
93 | completion(.success(decoded.results))
94 | } catch {
95 | completion(.failure(error))
96 | }
97 | case let .failure(error):
98 | completion(.failure(error))
99 | }
100 | }
101 | }
102 | }
103 |
104 | extension iTunesClient {
105 | enum Error: Swift.Error {
106 | case timeout
107 | case appNotFound
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Source/Store/StoreRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoreRequest.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum StoreRequest {
11 | case authenticate(email: String, password: String, code: String? = nil, attempt: Int? = nil, redirect: HTTPEndpoint? = nil)
12 | case download(appIdentifier: String, directoryServicesIdentifier: String)
13 | }
14 |
15 | extension StoreRequest: HTTPRequest {
16 | var endpoint: HTTPEndpoint {
17 | switch self {
18 | case let .authenticate(_, _, _, _, redirect):
19 | return redirect ?? StoreEndpoint.authenticate
20 | case .download:
21 | return StoreEndpoint.download(guid: guid)
22 | }
23 | }
24 |
25 | var method: HTTPMethod {
26 | return .post
27 | }
28 |
29 | var headers: [String: String] {
30 | var headers: [String: String] = [
31 | "User-Agent": "Configurator/2.0 (Macintosh; OS X 10.12.6; 16G29) AppleWebKit/2603.3.8",
32 | "Content-Type": "application/x-www-form-urlencoded"
33 | ]
34 |
35 | switch self {
36 | case .authenticate:
37 | break
38 | case let .download(_, directoryServicesIdentifier):
39 | headers["X-Dsid"] = directoryServicesIdentifier
40 | headers["iCloud-DSID"] = directoryServicesIdentifier
41 | }
42 |
43 | return headers
44 | }
45 |
46 | var payload: HTTPPayload? {
47 | switch self {
48 | case let .authenticate(email, password, code, attempt, _):
49 | return .xml([
50 | "appleId": email,
51 | "attempt": String(attempt ?? 1),
52 | "guid": guid,
53 | "password": "\(password)\(code ?? "")",
54 | "rmp": "0",
55 | "why": "signIn"
56 | ])
57 | case let .download(appIdentifier, _):
58 | return .xml([
59 | "creditDisplay": "",
60 | "guid": guid,
61 | "salableAdamId": "\(appIdentifier)"
62 | ])
63 | }
64 | }
65 | }
66 |
67 | extension StoreRequest {
68 | // This identifier is calculated by reading the MAC address of the device and stripping the nonalphabetic characters from the string.
69 | // https://stackoverflow.com/a/31838376
70 | private var guid: String {
71 | let MAC_ADDRESS_LENGTH = 6
72 | let bsds: [String] = ["en0", "en1"]
73 | var bsd: String = bsds[0]
74 |
75 | var length : size_t = 0
76 | var buffer : [CChar]
77 |
78 | var bsdIndex = Int32(if_nametoindex(bsd))
79 | if bsdIndex == 0 {
80 | bsd = bsds[1]
81 | bsdIndex = Int32(if_nametoindex(bsd))
82 | guard bsdIndex != 0 else { fatalError("Could not read MAC address") }
83 | }
84 |
85 | let bsdData = Data(bsd.utf8)
86 | var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, bsdIndex]
87 |
88 | guard sysctl(&managementInfoBase, 6, nil, &length, nil, 0) >= 0 else { fatalError("Could not read MAC address") }
89 |
90 | buffer = [CChar](unsafeUninitializedCapacity: length, initializingWith: {buffer, initializedCount in
91 | for x in 0..= 0 else { fatalError("Could not read MAC address") }
96 |
97 | let infoData = Data(bytes: buffer, count: length)
98 | let indexAfterMsghdr = MemoryLayout.stride + 1
99 | let rangeOfToken = infoData[indexAfterMsghdr...].range(of: bsdData)!
100 | let lower = rangeOfToken.upperBound
101 | let upper = lower + MAC_ADDRESS_LENGTH
102 | let macAddressData = infoData[lower..(archive: Archive, matchingSuffix: String, type: T.Type) throws -> T {
78 | guard let entry = archive.first(where: { $0.path.hasSuffix(matchingSuffix) }) else {
79 | throw Error.fileNotFound(matchingSuffix)
80 | }
81 |
82 | let url = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("plist")
83 | _ = try archive.extract(entry, to: url)
84 |
85 | let data = try Data(contentsOf: url)
86 | let plist = try PropertyListDecoder().decode(type, from: data)
87 |
88 | try FileManager.default.removeItem(at: url)
89 |
90 | return plist
91 | }
92 | }
93 |
94 | extension SignatureClient {
95 | struct Manifest: Codable {
96 | let paths: [String]
97 |
98 | enum CodingKeys: String, CodingKey {
99 | case paths = "SinfPaths"
100 | }
101 | }
102 |
103 | enum Error: Swift.Error {
104 | case invalidArchive
105 | case invalidAppBundle
106 | case invalidSignature
107 | case fileNotFound(String)
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Source/Store/StoreResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoreResponse.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | enum StoreResponse {
11 | case failure(error: Swift.Error)
12 | case account(Account)
13 | case item(Item)
14 | }
15 |
16 | extension StoreResponse {
17 | struct Account {
18 | let firstName: String
19 | let lastName: String
20 | let directoryServicesIdentifier: String
21 | }
22 |
23 | struct Item {
24 | let url: URL
25 | let md5: String
26 | let signatures: [Signature]
27 | let metadata: [String: Any]
28 | }
29 |
30 | enum Error: Int, Swift.Error {
31 | case unknownError = 0
32 | case genericError = 5002
33 | case codeRequired = 1
34 | case invalidLicense = 9610
35 | case invalidCredentials = -5000
36 | case invalidAccount = 5001
37 | case invalidItem = -10000
38 | case lockedAccount = -10001
39 | }
40 | }
41 |
42 | extension StoreResponse: Decodable {
43 | init(from decoder: Decoder) throws {
44 | let container = try decoder.container(keyedBy: CodingKeys.self)
45 |
46 | let error = try container.decodeIfPresent(String.self, forKey: .error)
47 | let message = try container.decodeIfPresent(String.self, forKey: .message)
48 |
49 | if container.contains(.account) {
50 | let directoryServicesIdentifier = try container.decode(String.self, forKey: .directoryServicesIdentifier)
51 | let accountContainer = try container.nestedContainer(keyedBy: AccountInfoCodingKeys.self, forKey: .account)
52 | let addressContainer = try accountContainer.nestedContainer(keyedBy: AddressCodingKeys.self, forKey: .address)
53 | let firstName = try addressContainer.decode(String.self, forKey: .firstName)
54 | let lastName = try addressContainer.decode(String.self, forKey: .lastName)
55 |
56 | self = .account(.init(firstName: firstName, lastName: lastName, directoryServicesIdentifier: directoryServicesIdentifier))
57 | } else if let items = try container.decodeIfPresent([Item].self, forKey: .items), let item = items.first {
58 | self = .item(item)
59 | } else if let error = error, !error.isEmpty {
60 | self = .failure(error: Error(rawValue: Int(error) ?? 0) ?? .unknownError)
61 | } else {
62 | switch message {
63 | case "Your account information was entered incorrectly.":
64 | self = .failure(error: Error.invalidCredentials)
65 | case "An Apple ID verification code is required to sign in. Type your password followed by the verification code shown on your other devices.", "MZFinance.BadLogin.Configurator_message":
66 | self = .failure(error: Error.codeRequired)
67 | case "This Apple ID has been locked for security reasons. Visit iForgot to reset your account (https://iforgot.apple.com).":
68 | self = .failure(error: Error.lockedAccount)
69 | default:
70 | self = .failure(error: Error.unknownError)
71 | }
72 | }
73 | }
74 |
75 | private enum CodingKeys: String, CodingKey {
76 | case directoryServicesIdentifier = "dsPersonId"
77 | case message = "customerMessage"
78 | case items = "songList"
79 | case error = "failureType"
80 | case account = "accountInfo"
81 | }
82 |
83 | private enum AccountInfoCodingKeys: String, CodingKey {
84 | case address
85 | }
86 |
87 | private enum AddressCodingKeys: String, CodingKey {
88 | case firstName
89 | case lastName
90 | }
91 | }
92 |
93 | extension StoreResponse.Item: Decodable {
94 | init(from decoder: Decoder) throws {
95 | let container = try decoder.container(keyedBy: CodingKeys.self)
96 | let md5 = try container.decode(String.self, forKey: .md5)
97 |
98 | guard let key = CodingUserInfoKey(rawValue: "data"),
99 | let data = decoder.userInfo[key] as? Data,
100 | let json = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any],
101 | let items = json["songList"] as? [[String: Any]],
102 | let item = items.first(where: { $0["md5"] as? String == md5 }),
103 | let metadata = item["metadata"] as? [String: Any]
104 | else { throw StoreResponse.Error.invalidItem }
105 |
106 | let absoluteUrl = try container.decode(String.self, forKey: .url)
107 |
108 | self.md5 = md5
109 | self.metadata = metadata
110 | self.signatures = try container.decode([Signature].self, forKey: .signatures)
111 |
112 | if let url = URL(string: absoluteUrl) {
113 | self.url = url
114 | } else {
115 | let context = DecodingError.Context(codingPath: [CodingKeys.url], debugDescription: "URL contains illegal characters: \(absoluteUrl).")
116 | throw DecodingError.keyNotFound(CodingKeys.url, context)
117 | }
118 | }
119 |
120 | struct Signature: Decodable {
121 | let id: Int
122 | let sinf: Data
123 | }
124 |
125 | enum CodingKeys: String, CodingKey {
126 | case url = "URL"
127 | case metadata
128 | case md5
129 | case signatures = "sinfs"
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Source/Store/StoreClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoreClient.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol StoreClientInterface {
11 | func authenticate(email: String, password: String, code: String?, completion: @escaping (Result) -> Void)
12 | func item(identifier: String, directoryServicesIdentifier: String, completion: @escaping (Result) -> Void)
13 | }
14 |
15 | extension StoreClientInterface {
16 | func authenticate(email: String,
17 | password: String,
18 | code: String? = nil,
19 | completion: @escaping (Result) -> Void) {
20 | authenticate(email: email, password: password, code: code, completion: completion)
21 | }
22 |
23 | func authenticate(email: String, password: String, code: String? = nil) throws -> StoreResponse.Account {
24 | let semaphore = DispatchSemaphore(value: 0)
25 | var result: Result?
26 |
27 | authenticate(email: email, password: password, code: code) {
28 | result = $0
29 | semaphore.signal()
30 | }
31 |
32 | _ = semaphore.wait(timeout: .distantFuture)
33 |
34 | switch result {
35 | case .none:
36 | throw StoreClient.Error.timeout
37 | case let .failure(error):
38 | throw error
39 | case let .success(result):
40 | return result
41 | }
42 | }
43 |
44 | func item(identifier: String, directoryServicesIdentifier: String) throws -> StoreResponse.Item {
45 | let semaphore = DispatchSemaphore(value: 0)
46 | var result: Result?
47 |
48 | item(identifier: identifier, directoryServicesIdentifier: directoryServicesIdentifier) {
49 | result = $0
50 | semaphore.signal()
51 | }
52 |
53 | _ = semaphore.wait(timeout: .distantFuture)
54 |
55 | switch result {
56 | case .none:
57 | throw StoreClient.Error.timeout
58 | case let .failure(error):
59 | throw error
60 | case let .success(result):
61 | return result
62 | }
63 | }
64 | }
65 |
66 | final class StoreClient: StoreClientInterface {
67 | private let httpClient: HTTPClient
68 |
69 | init(httpClient: HTTPClient) {
70 | self.httpClient = httpClient
71 | }
72 |
73 | func authenticate(email: String, password: String, code: String?, completion: @escaping (Result) -> Void) {
74 | authenticate(email: email,
75 | password: password,
76 | code: code,
77 | attempt: 1,
78 | redirect: nil,
79 | completion: completion)
80 | }
81 |
82 | private func authenticate(email: String,
83 | password: String,
84 | code: String?,
85 | attempt: Int,
86 | redirect: HTTPEndpoint?,
87 | completion: @escaping (Result) -> Void) {
88 | if attempt > 4 {
89 | completion(.failure(Error.tooManyAttempts))
90 | }
91 |
92 | let request = StoreRequest.authenticate(email: email, password: password, code: code, attempt: attempt, redirect: redirect)
93 |
94 | httpClient.send(request) { [weak self] result in
95 | switch result {
96 | case let .success(response):
97 | do {
98 | if response.statusCode == 302 {
99 | let redirect = RedirectEndpoint(location: response.headers["Location"])
100 |
101 | return self?.authenticate(email: email,
102 | password: password,
103 | code: code,
104 | attempt: attempt + 1,
105 | redirect: redirect,
106 | completion: completion) ?? ()
107 | }
108 |
109 | let decoded = try response.decode(StoreResponse.self, as: .xml)
110 |
111 | switch decoded {
112 | case let .account(account):
113 | completion(.success(account))
114 | case .item:
115 | completion(.failure(Error.invalidResponse))
116 | case let .failure(error):
117 | switch error {
118 | case StoreResponse.Error.invalidCredentials:
119 | if attempt == 1 {
120 | return self?.authenticate(email: email,
121 | password: password,
122 | code: code,
123 | attempt: attempt + 1,
124 | redirect: nil,
125 | completion: completion) ?? ()
126 | }
127 |
128 | completion(.failure(error))
129 | default:
130 | completion(.failure(error))
131 | }
132 | }
133 | } catch {
134 | completion(.failure(error))
135 | }
136 | case let .failure(error):
137 | completion(.failure(error))
138 | }
139 | }
140 | }
141 |
142 | func item(identifier: String, directoryServicesIdentifier: String, completion: @escaping (Result) -> Void) {
143 | let request = StoreRequest.download(appIdentifier: identifier, directoryServicesIdentifier: directoryServicesIdentifier)
144 |
145 | httpClient.send(request) { result in
146 | switch result {
147 | case let .success(response):
148 | do {
149 | let decoded = try response.decode(StoreResponse.self, as: .xml)
150 |
151 | switch decoded {
152 | case let .item(item):
153 | completion(.success(item))
154 | case .account:
155 | completion(.failure(Error.invalidResponse))
156 | case let .failure(error):
157 | completion(.failure(error))
158 | }
159 | } catch {
160 | completion(.failure(error))
161 | }
162 | case let .failure(error):
163 | completion(.failure(error))
164 | }
165 | }
166 | }
167 | }
168 |
169 | extension StoreClient {
170 | enum Error: Swift.Error {
171 | case timeout
172 | case invalidResponse
173 | case tooManyAttempts
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/Source/Commands/Download.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Download.swift
3 | // IPATool
4 | //
5 | // Created by Majd Alfhaily on 22.05.21.
6 | //
7 |
8 | import ArgumentParser
9 | import Foundation
10 |
11 | struct Download: ParsableCommand {
12 | static var configuration: CommandConfiguration {
13 | return .init(abstract: "Download (encrypted) iOS app packages from the App Store.")
14 | }
15 |
16 | @Option(name: [.short, .long], help: "The bundle identifier of the target iOS app.")
17 | private var bundleIdentifier: String
18 |
19 | @Option(name: [.short, .long], help: "The email address for the Apple ID.")
20 | private var email: String?
21 |
22 | @Option(name: [.short, .long], help: "The password for the Apple ID.")
23 | private var password: String?
24 |
25 | @Option
26 | private var logLevel: LogLevel = .info
27 | }
28 |
29 | extension Download {
30 | func run() throws {
31 | let logger = ConsoleLogger(level: logLevel)
32 |
33 | logger.log("Creating HTTP client...", level: .debug)
34 | let httpClient = HTTPClient(urlSession: URLSession.shared)
35 | let httpStoreClient = HTTPClient(urlSession: URLSession.noRedirect)
36 |
37 | logger.log("Creating iTunes client...", level: .debug)
38 | let itunesClient = iTunesClient(httpClient: httpClient)
39 |
40 | logger.log("Creating App Store client...", level: .debug)
41 | let storeClient = StoreClient(httpClient: httpStoreClient)
42 |
43 | logger.log("Creating download client...", level: .debug)
44 | let downloadClient = HTTPDownloadClient()
45 |
46 | logger.log("Querying the iTunes store for '\(bundleIdentifier)'...", level: .info)
47 | let app: iTunesResponse.Result
48 |
49 | do {
50 | app = try itunesClient.lookup(bundleIdentifier: bundleIdentifier)
51 | } catch {
52 | logger.log("\(error)", level: .debug)
53 |
54 | switch error {
55 | case iTunesClient.Error.appNotFound:
56 | logger.log("Could not find app.", level: .error)
57 | default:
58 | logger.log("An unknown error has occurred.", level: .error)
59 | }
60 |
61 | _exit(1)
62 | }
63 | logger.log("Found app: \(app.name) (\(app.version)).", level: .debug)
64 |
65 | let email: String
66 | let password: String
67 |
68 | if let cliEmail = self.email {
69 | email = cliEmail
70 | } else if let envEmail = ProcessInfo.processInfo.environment["IPATOOL_EMAIL"] {
71 | email = envEmail
72 | } else if let inputEmail = String(validatingUTF8: UnsafePointer(getpass(logger.compile("Enter Apple ID email: ", level: .warning)))) {
73 | email = inputEmail
74 | } else {
75 | logger.log("An Apple ID email address is required.", level: .error)
76 | _exit(1)
77 | }
78 |
79 | if let cliPassword = self.password {
80 | password = cliPassword
81 | } else if let envPassword = ProcessInfo.processInfo.environment["IPATOOL_PASSWORD"] {
82 | password = envPassword
83 | } else if let inputPassword = String(validatingUTF8: UnsafePointer(getpass(logger.compile("Enter Apple ID password: ", level: .warning)))) {
84 | password = inputPassword
85 | } else {
86 | logger.log("An Apple ID password is required.", level: .error)
87 | _exit(1)
88 | }
89 |
90 | let account: StoreResponse.Account
91 |
92 | do {
93 | logger.log("Authenticating with the App Store...", level: .info)
94 | account = try storeClient.authenticate(email: email, password: password)
95 | } catch {
96 | switch error {
97 | case StoreResponse.Error.codeRequired:
98 | let code = String(validatingUTF8: UnsafePointer(getpass(logger.compile("Enter 2FA code: ", level: .warning))))
99 |
100 | do {
101 | account = try storeClient.authenticate(email: email, password: password, code: code)
102 | } catch {
103 | logger.log("\(error)", level: .debug)
104 |
105 | switch error {
106 | case StoreClient.Error.invalidResponse:
107 | logger.log("Received invalid response.", level: .error)
108 | case StoreClient.Error.tooManyAttempts:
109 | logger.log("Too many attempts.", level: .error)
110 | case StoreResponse.Error.invalidAccount:
111 | logger.log("This Apple ID has not been set up to use the App Store.", level: .error)
112 | case StoreResponse.Error.invalidCredentials:
113 | logger.log("Invalid credentials.", level: .error)
114 | case StoreResponse.Error.lockedAccount:
115 | logger.log("This Apple ID has been disabled for security reasons.", level: .error)
116 | default:
117 | logger.log("An unknown error has occurred.", level: .error)
118 | }
119 |
120 | _exit(1)
121 | }
122 | default:
123 | logger.log("\(error)", level: .debug)
124 |
125 | switch error {
126 | case StoreClient.Error.invalidResponse:
127 | logger.log("Received invalid response.", level: .error)
128 | case StoreClient.Error.tooManyAttempts:
129 | logger.log("Too many attempts.", level: .error)
130 | case StoreResponse.Error.invalidAccount:
131 | logger.log("This Apple ID has not been set up to use the App Store.", level: .error)
132 | case StoreResponse.Error.invalidCredentials:
133 | logger.log("Invalid credentials.", level: .error)
134 | case StoreResponse.Error.lockedAccount:
135 | logger.log("This Apple ID has been disabled for security reasons.", level: .error)
136 | default:
137 | logger.log("An unknown error has occurred.", level: .error)
138 | }
139 |
140 | _exit(1)
141 | }
142 | }
143 | logger.log("Authenticated as '\(account.firstName) \(account.lastName)'.", level: .info)
144 |
145 | logger.log("Requesting a signed copy of '\(app.identifier)' from the App Store...", level: .info)
146 | let item = try storeClient.item(identifier: "\(app.identifier)", directoryServicesIdentifier: account.directoryServicesIdentifier)
147 | logger.log("Received a response of the signed copy: \(item.md5).", level: .debug)
148 |
149 | logger.log("Creating signature client...", level: .debug)
150 | let path = FileManager.default.currentDirectoryPath
151 | .appending("/\(bundleIdentifier)_\(app.identifier)_v\(app.version)_\(Int.random(in: 100...999))")
152 | .appending(".ipa")
153 |
154 | logger.log("Output path: \(path).", level: .debug)
155 | let signatureClient = SignatureClient(fileManager: .default, filePath: path)
156 |
157 | logger.log("Downloading app package...", level: .info)
158 | try downloadClient.download(from: item.url, to: URL(fileURLWithPath: path)) { progress in
159 | logger.log("Downloading app package... [\(Int((progress * 100).rounded()))%]",
160 | prefix: "\u{1B}[1A\u{1B}[K",
161 | level: .info)
162 | }
163 | logger.log("Saved app package to \(URL(fileURLWithPath: path).lastPathComponent).", level: .info)
164 |
165 | logger.log("Applying patches...", level: .info)
166 | try signatureClient.appendMetadata(item: item, email: email)
167 | try signatureClient.appendSignature(item: item)
168 |
169 | logger.log("Done.", level: .info)
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/ipatool.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 65EECE2E2673C81200014AF4 /* IPATool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE2D2673C81200014AF4 /* IPATool.swift */; };
11 | 65EECE4F2673C88F00014AF4 /* iTunesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE4B2673C88F00014AF4 /* iTunesResponse.swift */; };
12 | 65EECE502673C88F00014AF4 /* iTunesEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE4C2673C88F00014AF4 /* iTunesEndpoint.swift */; };
13 | 65EECE512673C88F00014AF4 /* iTunesClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE4D2673C88F00014AF4 /* iTunesClient.swift */; };
14 | 65EECE522673C88F00014AF4 /* iTunesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE4E2673C88F00014AF4 /* iTunesRequest.swift */; };
15 | 65EECE572673C89B00014AF4 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE542673C89B00014AF4 /* Logging.swift */; };
16 | 65EECE582673C89B00014AF4 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE552673C89B00014AF4 /* LogLevel.swift */; };
17 | 65EECE592673C89B00014AF4 /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE562673C89B00014AF4 /* ConsoleLogger.swift */; };
18 | 65EECE632673C8A800014AF4 /* HTTPEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE5B2673C8A800014AF4 /* HTTPEndpoint.swift */; };
19 | 65EECE642673C8A800014AF4 /* HTTPPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE5C2673C8A800014AF4 /* HTTPPayload.swift */; };
20 | 65EECE652673C8A800014AF4 /* HTTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE5D2673C8A800014AF4 /* HTTPRequest.swift */; };
21 | 65EECE662673C8A800014AF4 /* HTTPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE5E2673C8A800014AF4 /* HTTPResponse.swift */; };
22 | 65EECE672673C8A800014AF4 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE5F2673C8A800014AF4 /* HTTPMethod.swift */; };
23 | 65EECE682673C8A800014AF4 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE602673C8A800014AF4 /* HTTPClient.swift */; };
24 | 65EECE692673C8A800014AF4 /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE612673C8A800014AF4 /* URLSession.swift */; };
25 | 65EECE6A2673C8A800014AF4 /* HTTPDownloadClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE622673C8A800014AF4 /* HTTPDownloadClient.swift */; };
26 | 65EECE6D2673C8B500014AF4 /* SignatureClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE6C2673C8B500014AF4 /* SignatureClient.swift */; };
27 | 65EECE732673C8BE00014AF4 /* StoreClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE6F2673C8BE00014AF4 /* StoreClient.swift */; };
28 | 65EECE742673C8BE00014AF4 /* StoreRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE702673C8BE00014AF4 /* StoreRequest.swift */; };
29 | 65EECE752673C8BE00014AF4 /* StoreEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE712673C8BE00014AF4 /* StoreEndpoint.swift */; };
30 | 65EECE762673C8BE00014AF4 /* StoreResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE722673C8BE00014AF4 /* StoreResponse.swift */; };
31 | 65EECE7A2673C8EA00014AF4 /* Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE782673C8EA00014AF4 /* Download.swift */; };
32 | 65EECE7B2673C8EA00014AF4 /* Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EECE792673C8EA00014AF4 /* Search.swift */; };
33 | 65EECE7E2673C97500014AF4 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 65EECE7D2673C97500014AF4 /* ZIPFoundation */; };
34 | F5BBBA3C2E9366CA00A929EE /* RedirectEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BBBA3B2E9366C400A929EE /* RedirectEndpoint.swift */; };
35 | FA8FFD3D24ABB7D0008FD73C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA8FFD3C24ABB7D0008FD73C /* Assets.xcassets */; };
36 | FA8FFD4024ABB7D0008FD73C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA8FFD3E24ABB7D0008FD73C /* LaunchScreen.storyboard */; };
37 | FA8FFD4B24ABBDB0008FD73C /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = FA8FFD4A24ABBDB0008FD73C /* ArgumentParser */; };
38 | FA8FFD4D24ABBE14008FD73C /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8FFD4C24ABBE14008FD73C /* main.swift */; };
39 | /* End PBXBuildFile section */
40 |
41 | /* Begin PBXFileReference section */
42 | 65EECE2D2673C81200014AF4 /* IPATool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = IPATool.swift; path = Source/IPATool.swift; sourceTree = SOURCE_ROOT; };
43 | 65EECE4B2673C88F00014AF4 /* iTunesResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iTunesResponse.swift; sourceTree = ""; };
44 | 65EECE4C2673C88F00014AF4 /* iTunesEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iTunesEndpoint.swift; sourceTree = ""; };
45 | 65EECE4D2673C88F00014AF4 /* iTunesClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iTunesClient.swift; sourceTree = ""; };
46 | 65EECE4E2673C88F00014AF4 /* iTunesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iTunesRequest.swift; sourceTree = ""; };
47 | 65EECE542673C89B00014AF4 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; };
48 | 65EECE552673C89B00014AF4 /* LogLevel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogLevel.swift; sourceTree = ""; };
49 | 65EECE562673C89B00014AF4 /* ConsoleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsoleLogger.swift; sourceTree = ""; };
50 | 65EECE5B2673C8A800014AF4 /* HTTPEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPEndpoint.swift; sourceTree = ""; };
51 | 65EECE5C2673C8A800014AF4 /* HTTPPayload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPPayload.swift; sourceTree = ""; };
52 | 65EECE5D2673C8A800014AF4 /* HTTPRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPRequest.swift; sourceTree = ""; };
53 | 65EECE5E2673C8A800014AF4 /* HTTPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPResponse.swift; sourceTree = ""; };
54 | 65EECE5F2673C8A800014AF4 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; };
55 | 65EECE602673C8A800014AF4 /* HTTPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; };
56 | 65EECE612673C8A800014AF4 /* URLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = ""; };
57 | 65EECE622673C8A800014AF4 /* HTTPDownloadClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPDownloadClient.swift; sourceTree = ""; };
58 | 65EECE6C2673C8B500014AF4 /* SignatureClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignatureClient.swift; sourceTree = ""; };
59 | 65EECE6F2673C8BE00014AF4 /* StoreClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreClient.swift; sourceTree = ""; };
60 | 65EECE702673C8BE00014AF4 /* StoreRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreRequest.swift; sourceTree = ""; };
61 | 65EECE712673C8BE00014AF4 /* StoreEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreEndpoint.swift; sourceTree = ""; };
62 | 65EECE722673C8BE00014AF4 /* StoreResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreResponse.swift; sourceTree = ""; };
63 | 65EECE782673C8EA00014AF4 /* Download.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Download.swift; sourceTree = ""; };
64 | 65EECE792673C8EA00014AF4 /* Search.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Search.swift; sourceTree = ""; };
65 | F5BBBA3B2E9366C400A929EE /* RedirectEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedirectEndpoint.swift; sourceTree = ""; };
66 | FA8FFD3024ABB7CB008FD73C /* ipatool.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ipatool.app; sourceTree = BUILT_PRODUCTS_DIR; };
67 | FA8FFD3C24ABB7D0008FD73C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
68 | FA8FFD3F24ABB7D0008FD73C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
69 | FA8FFD4124ABB7D0008FD73C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
70 | FA8FFD4724ABB96A008FD73C /* ipatool.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ipatool.entitlements; path = Source/ipatool.entitlements; sourceTree = SOURCE_ROOT; };
71 | FA8FFD4C24ABBE14008FD73C /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
72 | /* End PBXFileReference section */
73 |
74 | /* Begin PBXFrameworksBuildPhase section */
75 | FA8FFD2D24ABB7CB008FD73C /* Frameworks */ = {
76 | isa = PBXFrameworksBuildPhase;
77 | buildActionMask = 2147483647;
78 | files = (
79 | FA8FFD4B24ABBDB0008FD73C /* ArgumentParser in Frameworks */,
80 | 65EECE7E2673C97500014AF4 /* ZIPFoundation in Frameworks */,
81 | );
82 | runOnlyForDeploymentPostprocessing = 0;
83 | };
84 | /* End PBXFrameworksBuildPhase section */
85 |
86 | /* Begin PBXGroup section */
87 | 65EECE4A2673C88F00014AF4 /* iTunes */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 65EECE4B2673C88F00014AF4 /* iTunesResponse.swift */,
91 | 65EECE4C2673C88F00014AF4 /* iTunesEndpoint.swift */,
92 | 65EECE4D2673C88F00014AF4 /* iTunesClient.swift */,
93 | 65EECE4E2673C88F00014AF4 /* iTunesRequest.swift */,
94 | );
95 | path = iTunes;
96 | sourceTree = "";
97 | };
98 | 65EECE532673C89B00014AF4 /* Logger */ = {
99 | isa = PBXGroup;
100 | children = (
101 | 65EECE542673C89B00014AF4 /* Logging.swift */,
102 | 65EECE552673C89B00014AF4 /* LogLevel.swift */,
103 | 65EECE562673C89B00014AF4 /* ConsoleLogger.swift */,
104 | );
105 | path = Logger;
106 | sourceTree = "";
107 | };
108 | 65EECE5A2673C8A800014AF4 /* Networking */ = {
109 | isa = PBXGroup;
110 | children = (
111 | F5BBBA3B2E9366C400A929EE /* RedirectEndpoint.swift */,
112 | 65EECE5B2673C8A800014AF4 /* HTTPEndpoint.swift */,
113 | 65EECE5C2673C8A800014AF4 /* HTTPPayload.swift */,
114 | 65EECE5D2673C8A800014AF4 /* HTTPRequest.swift */,
115 | 65EECE5E2673C8A800014AF4 /* HTTPResponse.swift */,
116 | 65EECE5F2673C8A800014AF4 /* HTTPMethod.swift */,
117 | 65EECE602673C8A800014AF4 /* HTTPClient.swift */,
118 | 65EECE612673C8A800014AF4 /* URLSession.swift */,
119 | 65EECE622673C8A800014AF4 /* HTTPDownloadClient.swift */,
120 | );
121 | path = Networking;
122 | sourceTree = "";
123 | };
124 | 65EECE6B2673C8B500014AF4 /* Signature */ = {
125 | isa = PBXGroup;
126 | children = (
127 | 65EECE6C2673C8B500014AF4 /* SignatureClient.swift */,
128 | );
129 | path = Signature;
130 | sourceTree = "";
131 | };
132 | 65EECE6E2673C8BE00014AF4 /* Store */ = {
133 | isa = PBXGroup;
134 | children = (
135 | 65EECE6F2673C8BE00014AF4 /* StoreClient.swift */,
136 | 65EECE702673C8BE00014AF4 /* StoreRequest.swift */,
137 | 65EECE712673C8BE00014AF4 /* StoreEndpoint.swift */,
138 | 65EECE722673C8BE00014AF4 /* StoreResponse.swift */,
139 | );
140 | path = Store;
141 | sourceTree = "";
142 | };
143 | 65EECE772673C8EA00014AF4 /* Commands */ = {
144 | isa = PBXGroup;
145 | children = (
146 | 65EECE782673C8EA00014AF4 /* Download.swift */,
147 | 65EECE792673C8EA00014AF4 /* Search.swift */,
148 | );
149 | path = Commands;
150 | sourceTree = "";
151 | };
152 | FA8FFD2724ABB7CB008FD73C = {
153 | isa = PBXGroup;
154 | children = (
155 | FA8FFD8624AC1642008FD73C /* Source */,
156 | FA8FFD8924AC16A2008FD73C /* App Resources */,
157 | FA8FFD3124ABB7CB008FD73C /* Products */,
158 | );
159 | sourceTree = "";
160 | };
161 | FA8FFD3124ABB7CB008FD73C /* Products */ = {
162 | isa = PBXGroup;
163 | children = (
164 | FA8FFD3024ABB7CB008FD73C /* ipatool.app */,
165 | );
166 | name = Products;
167 | sourceTree = "";
168 | };
169 | FA8FFD8624AC1642008FD73C /* Source */ = {
170 | isa = PBXGroup;
171 | children = (
172 | FA8FFD4C24ABBE14008FD73C /* main.swift */,
173 | 65EECE6E2673C8BE00014AF4 /* Store */,
174 | 65EECE6B2673C8B500014AF4 /* Signature */,
175 | 65EECE5A2673C8A800014AF4 /* Networking */,
176 | 65EECE532673C89B00014AF4 /* Logger */,
177 | 65EECE4A2673C88F00014AF4 /* iTunes */,
178 | 65EECE2D2673C81200014AF4 /* IPATool.swift */,
179 | 65EECE772673C8EA00014AF4 /* Commands */,
180 | FA8FFD4724ABB96A008FD73C /* ipatool.entitlements */,
181 | );
182 | path = Source;
183 | sourceTree = "";
184 | };
185 | FA8FFD8924AC16A2008FD73C /* App Resources */ = {
186 | isa = PBXGroup;
187 | children = (
188 | FA8FFD4124ABB7D0008FD73C /* Info.plist */,
189 | FA8FFD3C24ABB7D0008FD73C /* Assets.xcassets */,
190 | FA8FFD3E24ABB7D0008FD73C /* LaunchScreen.storyboard */,
191 | );
192 | path = "App Resources";
193 | sourceTree = "";
194 | };
195 | /* End PBXGroup section */
196 |
197 | /* Begin PBXNativeTarget section */
198 | FA8FFD2F24ABB7CB008FD73C /* ipatool */ = {
199 | isa = PBXNativeTarget;
200 | buildConfigurationList = FA8FFD4424ABB7D0008FD73C /* Build configuration list for PBXNativeTarget "ipatool" */;
201 | buildPhases = (
202 | FA8FFD2C24ABB7CB008FD73C /* Sources */,
203 | FA8FFD2D24ABB7CB008FD73C /* Frameworks */,
204 | FA8FFD2E24ABB7CB008FD73C /* Resources */,
205 | FA8FFD4824ABB974008FD73C /* Codesign */,
206 | );
207 | buildRules = (
208 | );
209 | dependencies = (
210 | );
211 | name = ipatool;
212 | packageProductDependencies = (
213 | FA8FFD4A24ABBDB0008FD73C /* ArgumentParser */,
214 | 65EECE7D2673C97500014AF4 /* ZIPFoundation */,
215 | );
216 | productName = flexdecrypt;
217 | productReference = FA8FFD3024ABB7CB008FD73C /* ipatool.app */;
218 | productType = "com.apple.product-type.application";
219 | };
220 | /* End PBXNativeTarget section */
221 |
222 | /* Begin PBXProject section */
223 | FA8FFD2824ABB7CB008FD73C /* Project object */ = {
224 | isa = PBXProject;
225 | attributes = {
226 | BuildIndependentTargetsInParallel = YES;
227 | LastSwiftUpdateCheck = 1150;
228 | LastUpgradeCheck = 2600;
229 | ORGANIZATIONNAME = "John Coates";
230 | TargetAttributes = {
231 | FA8FFD2F24ABB7CB008FD73C = {
232 | CreatedOnToolsVersion = 11.5;
233 | LastSwiftMigration = 1150;
234 | };
235 | };
236 | };
237 | buildConfigurationList = FA8FFD2B24ABB7CB008FD73C /* Build configuration list for PBXProject "ipatool" */;
238 | compatibilityVersion = "Xcode 9.3";
239 | developmentRegion = en;
240 | hasScannedForEncodings = 0;
241 | knownRegions = (
242 | en,
243 | Base,
244 | );
245 | mainGroup = FA8FFD2724ABB7CB008FD73C;
246 | packageReferences = (
247 | FA8FFD4924ABBDB0008FD73C /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
248 | 65EECE7C2673C97500014AF4 /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
249 | );
250 | productRefGroup = FA8FFD3124ABB7CB008FD73C /* Products */;
251 | projectDirPath = "";
252 | projectRoot = "";
253 | targets = (
254 | FA8FFD2F24ABB7CB008FD73C /* ipatool */,
255 | );
256 | };
257 | /* End PBXProject section */
258 |
259 | /* Begin PBXResourcesBuildPhase section */
260 | FA8FFD2E24ABB7CB008FD73C /* Resources */ = {
261 | isa = PBXResourcesBuildPhase;
262 | buildActionMask = 2147483647;
263 | files = (
264 | FA8FFD4024ABB7D0008FD73C /* LaunchScreen.storyboard in Resources */,
265 | FA8FFD3D24ABB7D0008FD73C /* Assets.xcassets in Resources */,
266 | );
267 | runOnlyForDeploymentPostprocessing = 0;
268 | };
269 | /* End PBXResourcesBuildPhase section */
270 |
271 | /* Begin PBXShellScriptBuildPhase section */
272 | FA8FFD4824ABB974008FD73C /* Codesign */ = {
273 | isa = PBXShellScriptBuildPhase;
274 | buildActionMask = 2147483647;
275 | files = (
276 | );
277 | inputFileListPaths = (
278 | );
279 | inputPaths = (
280 | );
281 | name = Codesign;
282 | outputFileListPaths = (
283 | );
284 | outputPaths = (
285 | );
286 | runOnlyForDeploymentPostprocessing = 0;
287 | shellPath = /bin/sh;
288 | shellScript = "set -x\nset -e\nexport appPath=$CODESIGNING_FOLDER_PATH\nexport binaryPath=$appPath/$PRODUCT_NAME\nexport entitlementsPath=$CODE_SIGN_ENTITLEMENTS\nexport plistBuddy=/usr/libexec/PlistBuddy\n\nif [ $PLATFORM_NAME = \"iphoneos\" ] && [ $CONFIGURATION = \"Debug\" ]; then\n echo \"warning: Entitling binary\"\n pushd ~/Library/MobileDevice/Provisioning\\ Profiles/\n FILES=./*\n for file in $FILES\n do\n echo \"Reading $file\"\n found=0\n security cms -D -i $file > /tmp/profile.plist \n wildcardAppId=\"Xcode: Wildcard AppID\"\n currentAppId=$($plistBuddy -c \"Print :AppIDName\" /tmp/profile.plist)\n if [[ \"$currentAppId\" == \"$wildcardAppId\" ]]; then\n currentAppId=$($plistBuddy -c \"Print :AppIDName\" /tmp/profile.plist) \n ApplicationIdentifierPrefix=$($plistBuddy -c \"Print :ApplicationIdentifierPrefix:0\" /tmp/profile.plist)\n TeamIdentifier=$($plistBuddy -c \"Print :TeamIdentifier:0\" /tmp/profile.plist)\n TeamName=$($plistBuddy -c \"Print :TeamName\" /tmp/profile.plist)\n echo \"Found Wildcard Provisoning Profile\"\n echo \"ApplicationIdentifierPrefix: $ApplicationIdentifierPrefix\"\n echo \"TeamIdentifier: $TeamIdentifier\"\n echo \"TeamName: $TeamName\"\n found=1\n rm /tmp/profile.plist\n break\n fi\n \n rm /tmp/profile.plist\n \n if [[ found == 0 ]]; then\n echo \"Error: Unable to find wildcard provisioning profile!\"\n exit 1\n fi\n done\n popd\n\n $plistBuddy -c \"Set :application-identifier $ApplicationIdentifierPrefix.$PRODUCT_BUNDLE_IDENTIFIER\" \"$entitlementsPath\"\n $plistBuddy -c \"Set :com.apple.developer.team-identifier $TeamIdentifier\" \"$entitlementsPath\"\n\n codesignIdentity=\"Apple Development: $TeamName\"\n FRAMEWORKS_DIR=\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\n find \"${FRAMEWORKS_DIR}\" -perm +111 -type f -exec codesign -fs \"$codesignIdentity\" \"{}\" \\;\n find \"${FRAMEWORKS_DIR}\" -perm +111 -type f -exec codesign -d \"$codesignIdentity\" \"{}\" \\;\n\n codesign --deep -fs \"$codesignIdentity\" --entitlements \"${entitlementsPath}\" \"${appPath}\"\n \n returnValue=$?\n \n codesign -d \"$appPath\"\n \n # clear\n $plistBuddy -c \"Set :application-identifier -\" \"$entitlementsPath\"\n $plistBuddy -c \"Set :com.apple.developer.team-identifier -\" \"$entitlementsPath\"\n \n if [[ $returnValue != 0 ]]; then\n exit $returnValue;\n fi\nelif [ $PLATFORM_NAME = \"iphoneos\" ] && [ $CONFIGURATION = \"Release\" ]; then\n echo \"warning: adhoc codesigning\"\n export entitlementsPath=$CODE_SIGN_ENTITLEMENTS\n export newEntitlementsPath=\"$CONFIGURATION_BUILD_DIR/modified.entitlements\"\n cp \"$entitlementsPath\" \"$newEntitlementsPath\"\n $plistBuddy -c \"Remove :application-identifier\" \"$newEntitlementsPath\"\n $plistBuddy -c \"Remove :com.apple.developer.team-identifier\" \"$newEntitlementsPath\"\n codesign --deep -f -s - --entitlements \"${newEntitlementsPath}\" \"${appPath}\"\n codesign -d \"$appPath\"\nfi\n";
289 | };
290 | /* End PBXShellScriptBuildPhase section */
291 |
292 | /* Begin PBXSourcesBuildPhase section */
293 | FA8FFD2C24ABB7CB008FD73C /* Sources */ = {
294 | isa = PBXSourcesBuildPhase;
295 | buildActionMask = 2147483647;
296 | files = (
297 | 65EECE2E2673C81200014AF4 /* IPATool.swift in Sources */,
298 | 65EECE6D2673C8B500014AF4 /* SignatureClient.swift in Sources */,
299 | 65EECE682673C8A800014AF4 /* HTTPClient.swift in Sources */,
300 | FA8FFD4D24ABBE14008FD73C /* main.swift in Sources */,
301 | 65EECE582673C89B00014AF4 /* LogLevel.swift in Sources */,
302 | F5BBBA3C2E9366CA00A929EE /* RedirectEndpoint.swift in Sources */,
303 | 65EECE512673C88F00014AF4 /* iTunesClient.swift in Sources */,
304 | 65EECE502673C88F00014AF4 /* iTunesEndpoint.swift in Sources */,
305 | 65EECE4F2673C88F00014AF4 /* iTunesResponse.swift in Sources */,
306 | 65EECE762673C8BE00014AF4 /* StoreResponse.swift in Sources */,
307 | 65EECE642673C8A800014AF4 /* HTTPPayload.swift in Sources */,
308 | 65EECE592673C89B00014AF4 /* ConsoleLogger.swift in Sources */,
309 | 65EECE572673C89B00014AF4 /* Logging.swift in Sources */,
310 | 65EECE652673C8A800014AF4 /* HTTPRequest.swift in Sources */,
311 | 65EECE672673C8A800014AF4 /* HTTPMethod.swift in Sources */,
312 | 65EECE692673C8A800014AF4 /* URLSession.swift in Sources */,
313 | 65EECE662673C8A800014AF4 /* HTTPResponse.swift in Sources */,
314 | 65EECE742673C8BE00014AF4 /* StoreRequest.swift in Sources */,
315 | 65EECE6A2673C8A800014AF4 /* HTTPDownloadClient.swift in Sources */,
316 | 65EECE632673C8A800014AF4 /* HTTPEndpoint.swift in Sources */,
317 | 65EECE732673C8BE00014AF4 /* StoreClient.swift in Sources */,
318 | 65EECE522673C88F00014AF4 /* iTunesRequest.swift in Sources */,
319 | 65EECE7B2673C8EA00014AF4 /* Search.swift in Sources */,
320 | 65EECE7A2673C8EA00014AF4 /* Download.swift in Sources */,
321 | 65EECE752673C8BE00014AF4 /* StoreEndpoint.swift in Sources */,
322 | );
323 | runOnlyForDeploymentPostprocessing = 0;
324 | };
325 | /* End PBXSourcesBuildPhase section */
326 |
327 | /* Begin PBXVariantGroup section */
328 | FA8FFD3E24ABB7D0008FD73C /* LaunchScreen.storyboard */ = {
329 | isa = PBXVariantGroup;
330 | children = (
331 | FA8FFD3F24ABB7D0008FD73C /* Base */,
332 | );
333 | name = LaunchScreen.storyboard;
334 | sourceTree = "";
335 | };
336 | /* End PBXVariantGroup section */
337 |
338 | /* Begin XCBuildConfiguration section */
339 | FA8FFD4224ABB7D0008FD73C /* Debug */ = {
340 | isa = XCBuildConfiguration;
341 | buildSettings = {
342 | ALWAYS_SEARCH_USER_PATHS = NO;
343 | CLANG_ANALYZER_NONNULL = YES;
344 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
346 | CLANG_CXX_LIBRARY = "libc++";
347 | CLANG_ENABLE_MODULES = YES;
348 | CLANG_ENABLE_OBJC_ARC = YES;
349 | CLANG_ENABLE_OBJC_WEAK = YES;
350 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
351 | CLANG_WARN_BOOL_CONVERSION = YES;
352 | CLANG_WARN_COMMA = YES;
353 | CLANG_WARN_CONSTANT_CONVERSION = YES;
354 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
355 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
356 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
357 | CLANG_WARN_EMPTY_BODY = YES;
358 | CLANG_WARN_ENUM_CONVERSION = YES;
359 | CLANG_WARN_INFINITE_RECURSION = YES;
360 | CLANG_WARN_INT_CONVERSION = YES;
361 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
362 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
363 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
364 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
365 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
366 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
367 | CLANG_WARN_STRICT_PROTOTYPES = YES;
368 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
369 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
370 | CLANG_WARN_UNREACHABLE_CODE = YES;
371 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
372 | COPY_PHASE_STRIP = NO;
373 | DEBUG_INFORMATION_FORMAT = dwarf;
374 | ENABLE_STRICT_OBJC_MSGSEND = YES;
375 | ENABLE_TESTABILITY = YES;
376 | GCC_C_LANGUAGE_STANDARD = gnu11;
377 | GCC_DYNAMIC_NO_PIC = NO;
378 | GCC_NO_COMMON_BLOCKS = YES;
379 | GCC_OPTIMIZATION_LEVEL = 0;
380 | GCC_PREPROCESSOR_DEFINITIONS = (
381 | "DEBUG=1",
382 | "$(inherited)",
383 | );
384 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
385 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
386 | GCC_WARN_UNDECLARED_SELECTOR = YES;
387 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
388 | GCC_WARN_UNUSED_FUNCTION = YES;
389 | GCC_WARN_UNUSED_VARIABLE = YES;
390 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
391 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
392 | MTL_FAST_MATH = YES;
393 | ONLY_ACTIVE_ARCH = YES;
394 | SDKROOT = iphoneos;
395 | STRING_CATALOG_GENERATE_SYMBOLS = YES;
396 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
397 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
398 | };
399 | name = Debug;
400 | };
401 | FA8FFD4324ABB7D0008FD73C /* Release */ = {
402 | isa = XCBuildConfiguration;
403 | buildSettings = {
404 | ALWAYS_SEARCH_USER_PATHS = NO;
405 | CLANG_ANALYZER_NONNULL = YES;
406 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
407 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
408 | CLANG_CXX_LIBRARY = "libc++";
409 | CLANG_ENABLE_MODULES = YES;
410 | CLANG_ENABLE_OBJC_ARC = YES;
411 | CLANG_ENABLE_OBJC_WEAK = YES;
412 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
413 | CLANG_WARN_BOOL_CONVERSION = YES;
414 | CLANG_WARN_COMMA = YES;
415 | CLANG_WARN_CONSTANT_CONVERSION = YES;
416 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
418 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
419 | CLANG_WARN_EMPTY_BODY = YES;
420 | CLANG_WARN_ENUM_CONVERSION = YES;
421 | CLANG_WARN_INFINITE_RECURSION = YES;
422 | CLANG_WARN_INT_CONVERSION = YES;
423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
424 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
425 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
426 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
427 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
428 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
429 | CLANG_WARN_STRICT_PROTOTYPES = YES;
430 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
431 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
432 | CLANG_WARN_UNREACHABLE_CODE = YES;
433 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
434 | COPY_PHASE_STRIP = NO;
435 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
436 | ENABLE_NS_ASSERTIONS = NO;
437 | ENABLE_STRICT_OBJC_MSGSEND = YES;
438 | GCC_C_LANGUAGE_STANDARD = gnu11;
439 | GCC_NO_COMMON_BLOCKS = YES;
440 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
441 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
442 | GCC_WARN_UNDECLARED_SELECTOR = YES;
443 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
444 | GCC_WARN_UNUSED_FUNCTION = YES;
445 | GCC_WARN_UNUSED_VARIABLE = YES;
446 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
447 | MTL_ENABLE_DEBUG_INFO = NO;
448 | MTL_FAST_MATH = YES;
449 | SDKROOT = iphoneos;
450 | STRING_CATALOG_GENERATE_SYMBOLS = YES;
451 | SWIFT_COMPILATION_MODE = wholemodule;
452 | SWIFT_OPTIMIZATION_LEVEL = "-O";
453 | VALIDATE_PRODUCT = YES;
454 | };
455 | name = Release;
456 | };
457 | FA8FFD4524ABB7D0008FD73C /* Debug */ = {
458 | isa = XCBuildConfiguration;
459 | buildSettings = {
460 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
461 | CLANG_ENABLE_MODULES = YES;
462 | CODE_SIGNING_ALLOWED = NO;
463 | CODE_SIGNING_REQUIRED = NO;
464 | CODE_SIGN_ENTITLEMENTS = $PROJECT_DIR/Source/ipatool.entitlements;
465 | CODE_SIGN_STYLE = Manual;
466 | DEVELOPMENT_TEAM = "";
467 | INFOPLIST_FILE = "App Resources/Info.plist";
468 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
469 | LD_RUNPATH_SEARCH_PATHS = (
470 | "$(inherited)",
471 | "@executable_path/Frameworks",
472 | /usr/lib/libswift/stable,
473 | );
474 | MARKETING_VERSION = 1.1;
475 | PRODUCT_BUNDLE_IDENTIFIER = daniel.ipatool;
476 | PRODUCT_NAME = "$(TARGET_NAME)";
477 | PROVISIONING_PROFILE_SPECIFIER = "";
478 | SWIFT_OBJC_BRIDGING_HEADER = $PROJECT_DIR/Source/Bridging.h;
479 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
480 | SWIFT_VERSION = 5.0;
481 | SYSTEM_HEADER_SEARCH_PATHS = $PROJECT_DIR/External/include;
482 | TARGETED_DEVICE_FAMILY = "1,2";
483 | };
484 | name = Debug;
485 | };
486 | FA8FFD4624ABB7D0008FD73C /* Release */ = {
487 | isa = XCBuildConfiguration;
488 | buildSettings = {
489 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
490 | CLANG_ENABLE_MODULES = YES;
491 | CODE_SIGNING_ALLOWED = NO;
492 | CODE_SIGNING_REQUIRED = NO;
493 | CODE_SIGN_ENTITLEMENTS = $PROJECT_DIR/Source/ipatool.entitlements;
494 | CODE_SIGN_STYLE = Manual;
495 | DEVELOPMENT_TEAM = "";
496 | INFOPLIST_FILE = "App Resources/Info.plist";
497 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
498 | LD_RUNPATH_SEARCH_PATHS = (
499 | "$(inherited)",
500 | "@executable_path/Frameworks",
501 | /usr/lib/libswift/stable,
502 | );
503 | MARKETING_VERSION = 1.1;
504 | PRODUCT_BUNDLE_IDENTIFIER = daniel.ipatool;
505 | PRODUCT_NAME = "$(TARGET_NAME)";
506 | PROVISIONING_PROFILE_SPECIFIER = "";
507 | SWIFT_OBJC_BRIDGING_HEADER = $PROJECT_DIR/Source/Bridging.h;
508 | SWIFT_VERSION = 5.0;
509 | SYSTEM_HEADER_SEARCH_PATHS = $PROJECT_DIR/External/include;
510 | TARGETED_DEVICE_FAMILY = "1,2";
511 | };
512 | name = Release;
513 | };
514 | /* End XCBuildConfiguration section */
515 |
516 | /* Begin XCConfigurationList section */
517 | FA8FFD2B24ABB7CB008FD73C /* Build configuration list for PBXProject "ipatool" */ = {
518 | isa = XCConfigurationList;
519 | buildConfigurations = (
520 | FA8FFD4224ABB7D0008FD73C /* Debug */,
521 | FA8FFD4324ABB7D0008FD73C /* Release */,
522 | );
523 | defaultConfigurationIsVisible = 0;
524 | defaultConfigurationName = Release;
525 | };
526 | FA8FFD4424ABB7D0008FD73C /* Build configuration list for PBXNativeTarget "ipatool" */ = {
527 | isa = XCConfigurationList;
528 | buildConfigurations = (
529 | FA8FFD4524ABB7D0008FD73C /* Debug */,
530 | FA8FFD4624ABB7D0008FD73C /* Release */,
531 | );
532 | defaultConfigurationIsVisible = 0;
533 | defaultConfigurationName = Release;
534 | };
535 | /* End XCConfigurationList section */
536 |
537 | /* Begin XCRemoteSwiftPackageReference section */
538 | 65EECE7C2673C97500014AF4 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
539 | isa = XCRemoteSwiftPackageReference;
540 | repositoryURL = "https://github.com/weichsel/ZIPFoundation.git";
541 | requirement = {
542 | kind = upToNextMajorVersion;
543 | minimumVersion = 0.9.12;
544 | };
545 | };
546 | FA8FFD4924ABBDB0008FD73C /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = {
547 | isa = XCRemoteSwiftPackageReference;
548 | repositoryURL = "https://github.com/apple/swift-argument-parser";
549 | requirement = {
550 | kind = upToNextMajorVersion;
551 | minimumVersion = 0.2.0;
552 | };
553 | };
554 | /* End XCRemoteSwiftPackageReference section */
555 |
556 | /* Begin XCSwiftPackageProductDependency section */
557 | 65EECE7D2673C97500014AF4 /* ZIPFoundation */ = {
558 | isa = XCSwiftPackageProductDependency;
559 | package = 65EECE7C2673C97500014AF4 /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
560 | productName = ZIPFoundation;
561 | };
562 | FA8FFD4A24ABBDB0008FD73C /* ArgumentParser */ = {
563 | isa = XCSwiftPackageProductDependency;
564 | package = FA8FFD4924ABBDB0008FD73C /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
565 | productName = ArgumentParser;
566 | };
567 | /* End XCSwiftPackageProductDependency section */
568 | };
569 | rootObject = FA8FFD2824ABB7CB008FD73C /* Project object */;
570 | }
571 |
--------------------------------------------------------------------------------