├── .github └── workflows │ └── build_app.yml ├── .gitignore ├── .gitmodules ├── AltServer.xcconfig ├── AltServer ├── AOSKit │ ├── AOSKit.h │ └── AOSKit.m ├── AltServer-Bridging-Header.h ├── AltServer.entitlements ├── AnisetteDataManager.swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-1024.png │ │ ├── Icon-128.png │ │ ├── Icon-16.png │ │ ├── Icon-256.png │ │ ├── Icon-32.png │ │ ├── Icon-512.png │ │ ├── Icon-64.png │ │ ├── Icon@1024.png │ │ ├── Icon@128.png │ │ ├── Icon@16.png │ │ ├── Icon@256-1.png │ │ ├── Icon@256.png │ │ ├── Icon@32-1.png │ │ ├── Icon@32.png │ │ ├── Icon@512-1.png │ │ ├── Icon@512.png │ │ └── Icon@64.png │ ├── Contents.json │ └── MenuBarIcon.imageset │ │ ├── Contents.json │ │ ├── MenuBar@19.png │ │ └── MenuBar@38.png ├── Base.lproj │ └── Main.storyboard ├── Categories │ ├── NSError+libimobiledevice.h │ └── NSError+libimobiledevice.mm ├── Connections │ ├── ALTDebugConnection+Private.h │ ├── ALTDebugConnection.h │ ├── ALTDebugConnection.mm │ ├── ALTNotificationConnection+Private.h │ ├── ALTNotificationConnection.h │ ├── ALTNotificationConnection.mm │ ├── ALTWiredConnection+Private.h │ ├── ALTWiredConnection.h │ ├── ALTWiredConnection.mm │ ├── RequestHandler.swift │ ├── WiredConnectionHandler.swift │ └── WirelessConnectionHandler.swift ├── DeveloperDiskManager.swift ├── Devices │ ├── ALTDeviceManager+Installation.swift │ ├── ALTDeviceManager.h │ └── ALTDeviceManager.mm ├── Extensions │ ├── FileManager+URLs.swift │ └── UserDefaults+AltServer.swift ├── Info.plist ├── InstalledApp.swift ├── MenuController.swift └── Views │ └── AppleIDAuthenticationAlert.swift ├── AltStore.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── AltServer.xcscheme ├── AltXPC.xcconfig ├── Build.xcconfig ├── CodeSigning.xcconfig.sample ├── LICENSE ├── README.md ├── Shared ├── ALTConstants.h ├── ALTConstants.m ├── Categories │ ├── CFNotificationName+AltStore.h │ ├── CFNotificationName+AltStore.m │ ├── NSError+ALTServerError.h │ └── NSError+ALTServerError.m ├── Components │ └── Keychain.swift ├── Connections │ ├── ALTConnection.h │ ├── Connection.swift │ ├── ConnectionManager.swift │ └── NetworkConnection.swift ├── Extensions │ ├── ALTApplication+AltStoreApp.swift │ ├── ALTServerError+Conveniences.swift │ ├── Bundle+AltStore.swift │ ├── NSError+AltStore.swift │ ├── NSXPCConnection+MachServices.swift │ └── Result+Conveniences.swift └── Server Protocol │ ├── CodableServerError.swift │ └── ServerProtocol.swift └── sparkle-macos.xml /.github/workflows/build_app.yml: -------------------------------------------------------------------------------- 1 | name: build_app 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | env: 6 | SCHEME: AltServer 7 | 8 | jobs: 9 | master_deploy: 10 | runs-on: macos-latest 11 | 12 | steps: 13 | - name: Checkout project 14 | uses: actions/checkout@v2 15 | with: 16 | submodules: recursive 17 | 18 | - name: Set environment variables from project settings 19 | run: | 20 | function set-env-from-proj { 21 | echo "$1=$(xcodebuild -scheme "AltServer" -showBuildSettings | grep " $1 " | sed "s/[ ]*$1 = //")" >> $GITHUB_ENV 22 | } 23 | set-env-from-proj FULL_PRODUCT_NAME 24 | set-env-from-proj INSTALL_PATH 25 | set-env-from-proj PRODUCT_BUNDLE_IDENTIFIER 26 | set-env-from-proj PRODUCT_MODULE_NAME 27 | set-env-from-proj PRODUCT_NAME 28 | set-env-from-proj PROJECT_NAME 29 | 30 | - name: Build and install app 31 | shell: bash 32 | run: | 33 | xcodebuild -scheme "$SCHEME" install DSTROOT=build/root | xcpretty 34 | 35 | - name: Package app 36 | run: | 37 | hdiutil create \ 38 | -fs HFS+ \ 39 | -srcfolder "build/root/$INSTALL_PATH/$FULL_PRODUCT_NAME" \ 40 | -volname "$PRODUCT_NAME" "build/$PRODUCT_MODULE_NAME.dmg" 41 | 42 | - name: Upload a Build Artifact 43 | uses: actions/upload-artifact@v3.1.1 44 | with: 45 | path: build/${{ env.PRODUCT_MODULE_NAME }}.dmg 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | # 3 | *.DS_Store 4 | 5 | # Xcode 6 | # 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | 23 | ## Other 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | /newrelic_agent.log 32 | /CodeSigning.xcconfig 33 | /.vscode 34 | AltServer/AltServer.entitlements 35 | AltStore.xcodeproj/project.pbxproj 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Dependencies/Roxas"] 2 | path = Dependencies/Roxas 3 | url = https://github.com/rileytestut/Roxas.git 4 | [submodule "Dependencies/AltSign"] 5 | path = Dependencies/AltSign 6 | url = https://github.com/SideStore/AltSign.git 7 | [submodule "Dependencies/libimobiledevice"] 8 | path = Dependencies/libimobiledevice 9 | url = https://github.com/SideStore/libimobiledevice.git 10 | [submodule "Dependencies/libusbmuxd"] 11 | path = Dependencies/libusbmuxd 12 | url = https://github.com/libimobiledevice/libusbmuxd.git 13 | [submodule "Dependencies/libplist"] 14 | path = Dependencies/libplist 15 | url = https://github.com/libimobiledevice/libplist.git 16 | [submodule "Dependencies/MarkdownAttributedString"] 17 | path = Dependencies/MarkdownAttributedString 18 | url = https://github.com/chockenberry/MarkdownAttributedString.git 19 | -------------------------------------------------------------------------------- /AltServer.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Build.xcconfig" 2 | -------------------------------------------------------------------------------- /AltServer/AOSKit/AOSKit.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AOSUtilities : NSObject 5 | + (id)currentComputerName; 6 | + (id)machineUDID; 7 | + (id)machineSerialNumber; 8 | + (id)retrieveOTPHeadersForDSID:(id)arg1; 9 | @end 10 | 11 | @interface AKDevice : NSObject 12 | + (id)currentDevice; 13 | - (id)localUserUUID; 14 | - (id)locale; 15 | - (id)serverFriendlyDescription; 16 | - (id)uniqueDeviceIdentifier; 17 | @end 18 | 19 | @interface AOSKit : NSObject 20 | + (ALTAnisetteData *)getAnisetteData; 21 | @end 22 | -------------------------------------------------------------------------------- /AltServer/AOSKit/AOSKit.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "AOSKit.h" 3 | 4 | @implementation AOSKit 5 | 6 | + (ALTAnisetteData *)getAnisetteData { 7 | 8 | AKDevice *device = [AKDevice currentDevice]; 9 | id headers = [AOSUtilities retrieveOTPHeadersForDSID:@"-2"]; 10 | 11 | NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init]; 12 | NSString *dateString = [formatter stringFromDate:[NSDate date]]; 13 | 14 | NSDictionary *dict = @{ 15 | @"date" : dateString, 16 | @"oneTimePassword" : [headers valueForKey:@"X-Apple-MD"], 17 | @"localUserID" : [device localUserUUID], 18 | @"machineID" : [headers valueForKey:@"X-Apple-MD-M"], 19 | @"routingInfo" : @"0", 20 | @"deviceSerialNumber" : [AOSUtilities machineSerialNumber], 21 | @"timeZone" : [[NSTimeZone systemTimeZone] abbreviation], 22 | @"locale" : [[device locale] localeIdentifier], 23 | @"deviceDescription" : [device serverFriendlyDescription], 24 | @"deviceUniqueIdentifier" : [device uniqueDeviceIdentifier] 25 | }; 26 | 27 | ALTAnisetteData *data = [[ALTAnisetteData alloc] initWithJSON:dict]; 28 | return data; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /AltServer/AltServer-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "ALTDeviceManager.h" 6 | #import "ALTWiredConnection.h" 7 | #import "ALTNotificationConnection.h" 8 | #import "ALTDebugConnection.h" 9 | 10 | // Shared 11 | #import "ALTConstants.h" 12 | #import "ALTConnection.h" 13 | #import "NSError+ALTServerError.h" 14 | #import "CFNotificationName+AltStore.h" 15 | 16 | #import "AOSKit.h" 17 | -------------------------------------------------------------------------------- /AltServer/AltServer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | $(TeamIdentifierPrefix)group.io.SideStore.SideServer 8 | 9 | keychain-access-groups 10 | 11 | $(AppIdentifierPrefix)group.io.SideStore.SideServer 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /AltServer/AnisetteDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnisetteDataManager.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 11/16/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | private extension Bundle 13 | { 14 | struct ID 15 | { 16 | static let mail = "com.apple.mail" } 17 | } 18 | 19 | enum AnisetteFetchError: LocalizedError 20 | { 21 | case unknownError 22 | } 23 | 24 | 25 | 26 | private extension ALTAnisetteData 27 | { 28 | func sanitize(byReplacingBundleID bundleID: String) 29 | { 30 | guard let range = self.deviceDescription.lowercased().range(of: "(" + bundleID.lowercased()) else { return } 31 | 32 | var adjustedDescription = self.deviceDescription[..) -> Void] = [:] 44 | private var anisetteDataTimers: [String: Timer] = [:] 45 | 46 | private override init() 47 | { 48 | super.init() 49 | 50 | DistributedNotificationCenter.default().addObserver(self, selector: #selector(AnisetteDataManager.handleAnisetteDataResponse(_:)), name: Notification.Name("com.rileytestut.AltServer.AnisetteDataResponse"), object: nil) 51 | } 52 | 53 | func requestAnisetteData(_ completion: @escaping (Result) -> Void) 54 | { 55 | guard let anisette = AOSKit.getAnisetteData() else { 56 | completion(.failure(.unknownError)) 57 | return 58 | } 59 | completion(.success(anisette)) 60 | } 61 | } 62 | 63 | private extension AnisetteDataManager 64 | { 65 | @objc func handleAnisetteDataResponse(_ notification: Notification) 66 | { 67 | guard let userInfo = notification.userInfo, let requestUUID = userInfo["requestUUID"] as? String else { return } 68 | 69 | if 70 | let archivedAnisetteData = userInfo["anisetteData"] as? Data, 71 | let anisetteData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ALTAnisetteData.self, from: archivedAnisetteData) 72 | { 73 | anisetteData.sanitize(byReplacingBundleID: Bundle.ID.mail) 74 | self.finishRequest(forUUID: requestUUID, result: .success(anisetteData)) 75 | } 76 | else 77 | { 78 | self.finishRequest(forUUID: requestUUID, result: .failure(ALTServerError(.invalidAnisetteData))) 79 | } 80 | } 81 | 82 | func finishRequest(forUUID requestUUID: String, result: Result) 83 | { 84 | let completionHandler = self.anisetteDataCompletionHandlers[requestUUID] 85 | self.anisetteDataCompletionHandlers[requestUUID] = nil 86 | 87 | let timer = self.anisetteDataTimers[requestUUID] 88 | self.anisetteDataTimers[requestUUID] = nil 89 | 90 | timer?.invalidate() 91 | completionHandler?(result) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /AltServer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 5/24/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import UserNotifications 11 | 12 | import AltSign 13 | 14 | import LaunchAtLogin 15 | import Sparkle 16 | 17 | private let altstoreAppURL = URL(string: "https://github.com/SideStore/SideStore/releases/latest/download/SideStore.ipa")! 18 | 19 | extension ALTDevice: MenuDisplayable {} 20 | 21 | @NSApplicationMain 22 | class AppDelegate: NSObject, NSApplicationDelegate { 23 | 24 | 25 | private var statusItem: NSStatusItem? 26 | 27 | private var connectedDevices = [ALTDevice]() 28 | 29 | private weak var authenticationAlert: NSAlert? 30 | 31 | @IBOutlet private var appMenu: NSMenu! 32 | @IBOutlet private var connectedDevicesMenu: NSMenu! 33 | @IBOutlet private var sideloadIPAConnectedDevicesMenu: NSMenu! 34 | @IBOutlet private var enableJITMenu: NSMenu! 35 | 36 | @IBOutlet private var launchAtLoginMenuItem: NSMenuItem! 37 | @IBOutlet private var sideloadAppMenuItem: NSMenuItem! 38 | @IBOutlet private var installAltStoreMenuItem: NSMenuItem! 39 | @IBOutlet private var logInMenuItem: NSMenuItem! 40 | 41 | private var connectedDevicesMenuController: MenuController! 42 | private var sideloadIPAConnectedDevicesMenuController: MenuController! 43 | private var enableJITMenuController: MenuController! 44 | 45 | private var _jitAppListMenuControllers = [AnyObject]() 46 | 47 | func applicationDidFinishLaunching(_ aNotification: Notification) 48 | { 49 | UserDefaults.standard.registerDefaults() 50 | 51 | UNUserNotificationCenter.current().delegate = self 52 | 53 | ServerConnectionManager.shared.start() 54 | ALTDeviceManager.shared.start() 55 | 56 | #if STAGING 57 | let feedURL: String = Bundle.main.infoDictionary!["SUFeedURL"]! as! String 58 | #else 59 | let feedURL: String = Bundle.main.infoDictionary!["SUFeedURL"]! as! String 60 | #endif 61 | 62 | SUUpdater.shared().feedURL = URL(string: feedURL) 63 | 64 | let item = NSStatusBar.system.statusItem(withLength: -1) 65 | item.menu = self.appMenu 66 | item.button?.image = NSImage(named: "MenuBarIcon") 67 | self.statusItem = item 68 | 69 | self.appMenu.delegate = self 70 | 71 | self.sideloadAppMenuItem.keyEquivalentModifierMask = .option 72 | self.sideloadAppMenuItem.isAlternate = true 73 | 74 | let placeholder = NSLocalizedString("No Connected Devices", comment: "") 75 | 76 | self.connectedDevicesMenuController = MenuController(menu: self.connectedDevicesMenu, items: []) 77 | self.connectedDevicesMenuController.placeholder = placeholder 78 | self.connectedDevicesMenuController.action = { [weak self] device in 79 | self?.installAltStore(to: device) 80 | } 81 | 82 | self.sideloadIPAConnectedDevicesMenuController = MenuController(menu: self.sideloadIPAConnectedDevicesMenu, items: []) 83 | self.sideloadIPAConnectedDevicesMenuController.placeholder = placeholder 84 | self.sideloadIPAConnectedDevicesMenuController.action = { [weak self] device in 85 | self?.sideloadIPA(to: device) 86 | } 87 | 88 | self.enableJITMenuController = MenuController(menu: self.enableJITMenu, items: []) 89 | self.enableJITMenuController.placeholder = placeholder 90 | 91 | UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { (success, error) in 92 | guard success else { return } 93 | 94 | if !UserDefaults.standard.didPresentInitialNotification 95 | { 96 | let content = UNMutableNotificationContent() 97 | content.title = NSLocalizedString("SideServer Running", comment: "") 98 | content.body = NSLocalizedString("SideServer runs in the background as a menu bar app listening for SideStore.", comment: "") 99 | 100 | let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) 101 | UNUserNotificationCenter.current().add(request) 102 | 103 | UserDefaults.standard.didPresentInitialNotification = true 104 | } 105 | } 106 | 107 | setupLoginMenuItem() 108 | } 109 | 110 | func applicationWillTerminate(_ aNotification: Notification) 111 | { 112 | // Insert code here to tear down your application 113 | } 114 | } 115 | 116 | private extension AppDelegate 117 | { 118 | @objc func installAltStore(to device: ALTDevice) 119 | { 120 | self.installApplication(at: altstoreAppURL, to: device) 121 | } 122 | 123 | @objc func sideloadIPA(to device: ALTDevice) 124 | { 125 | 126 | let openPanel = NSOpenPanel() 127 | openPanel.canChooseDirectories = false 128 | openPanel.allowsMultipleSelection = false 129 | openPanel.allowedFileTypes = ["ipa"] 130 | openPanel.begin { (response) in 131 | guard let fileURL = openPanel.url, response == .OK else { return } 132 | self.installApplication(at: fileURL, to: device) 133 | } 134 | NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) 135 | } 136 | 137 | func enableJIT(for app: InstalledApp, on device: ALTDevice) 138 | { 139 | func finish(_ result: Result) 140 | { 141 | DispatchQueue.main.async { 142 | switch result 143 | { 144 | case .failure(let error): 145 | self.showErrorAlert(error: error, localizedFailure: String(format: NSLocalizedString("JIT compilation could not be enabled for %@.", comment: ""), app.name)) 146 | 147 | case .success: 148 | let alert = NSAlert() 149 | alert.messageText = String(format: NSLocalizedString("Successfully enabled JIT for %@.", comment: ""), app.name) 150 | alert.informativeText = String(format: NSLocalizedString("JIT will remain enabled until you quit the app. You can now disconnect %@ from your computer.", comment: ""), device.name) 151 | alert.runModal() 152 | } 153 | } 154 | } 155 | 156 | ALTDeviceManager.shared.prepare(device) { (result) in 157 | switch result 158 | { 159 | case .failure(let error as NSError): return finish(.failure(error)) 160 | case .success: 161 | ALTDeviceManager.shared.startDebugConnection(to: device) { (connection, error) in 162 | guard let connection = connection else { 163 | return finish(.failure(error! as NSError)) 164 | } 165 | 166 | connection.enableUnsignedCodeExecutionForProcess(withName: app.executableName) { (success, error) in 167 | guard success else { 168 | return finish(.failure(error!)) 169 | } 170 | 171 | finish(.success(())) 172 | } 173 | } 174 | } 175 | } 176 | } 177 | 178 | func installApplication(at url: URL, to device: ALTDevice) 179 | { 180 | let username: String 181 | let password: String 182 | 183 | if let _username = try? Keychain.shared.getValue(for: .appleIDEmail), 184 | let _password = try? Keychain.shared.getValue(for: .appleIDPassword) { 185 | username = _username 186 | password = _password 187 | } else { 188 | let alert = AppleIDAuthenticationAlert() 189 | self.authenticationAlert = alert 190 | 191 | NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) 192 | 193 | let didTapContinue = alert.display() 194 | guard didTapContinue else { return } 195 | 196 | username = alert.appleIDValue.trimmingCharacters(in: .whitespacesAndNewlines) 197 | password = alert.passwordValue.trimmingCharacters(in: .whitespacesAndNewlines) 198 | 199 | do { 200 | try Keychain.shared.setValue(username.isEmpty ? nil : username, for: .appleIDEmail) 201 | try Keychain.shared.setValue(password.isEmpty ? nil : password, for: .appleIDPassword) 202 | } catch { 203 | print("AppleID Auth: Error saving credentials: \(error)") 204 | } 205 | } 206 | 207 | func finish(_ result: Result) 208 | { 209 | switch result 210 | { 211 | case .success(let application): 212 | let content = UNMutableNotificationContent() 213 | content.title = NSLocalizedString("Installation Succeeded", comment: "") 214 | content.body = String(format: NSLocalizedString("%@ was successfully installed on %@.", comment: ""), application.name, device.name) 215 | 216 | let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) 217 | UNUserNotificationCenter.current().add(request) 218 | 219 | case .failure(InstallError.cancelled), .failure(ALTAppleAPIError.requiresTwoFactorAuthentication): 220 | // Ignore 221 | break 222 | 223 | case .failure(let error): 224 | DispatchQueue.main.async { 225 | self.showErrorAlert(error: error, localizedFailure: String(format: NSLocalizedString("Could not install app to %@.", comment: ""), device.name)) 226 | } 227 | } 228 | } 229 | 230 | ALTDeviceManager.shared.installApplication(at: url, to: device, appleID: username, password: password, completion: finish(_:)) 231 | 232 | } 233 | 234 | func showErrorAlert(error: Error, localizedFailure: String) 235 | { 236 | let nsError = error as NSError 237 | 238 | let alert = NSAlert() 239 | alert.alertStyle = .critical 240 | alert.messageText = localizedFailure 241 | 242 | var messageComponents = [String]() 243 | 244 | let separator: String 245 | switch error 246 | { 247 | case ALTServerError.maximumFreeAppLimitReached: separator = "\n\n" 248 | default: separator = " " 249 | } 250 | 251 | if let errorFailure = nsError.localizedFailure 252 | { 253 | if let debugDescription = nsError.localizedDebugDescription 254 | { 255 | alert.messageText = errorFailure 256 | messageComponents.append(debugDescription) 257 | } 258 | else if let failureReason = nsError.localizedFailureReason 259 | { 260 | if nsError.localizedDescription.starts(with: errorFailure) 261 | { 262 | alert.messageText = errorFailure 263 | messageComponents.append(failureReason) 264 | } 265 | else 266 | { 267 | alert.messageText = errorFailure 268 | messageComponents.append(nsError.localizedDescription) 269 | } 270 | } 271 | else 272 | { 273 | // No failure reason given. 274 | 275 | if nsError.localizedDescription.starts(with: errorFailure) 276 | { 277 | // No need to duplicate errorFailure in both title and message. 278 | alert.messageText = localizedFailure 279 | messageComponents.append(nsError.localizedDescription) 280 | } 281 | else 282 | { 283 | alert.messageText = errorFailure 284 | messageComponents.append(nsError.localizedDescription) 285 | } 286 | } 287 | } 288 | else 289 | { 290 | alert.messageText = localizedFailure 291 | 292 | if let debugDescription = nsError.localizedDebugDescription 293 | { 294 | messageComponents.append(debugDescription) 295 | } 296 | else 297 | { 298 | messageComponents.append(nsError.localizedDescription) 299 | } 300 | } 301 | 302 | if let recoverySuggestion = nsError.localizedRecoverySuggestion 303 | { 304 | messageComponents.append(recoverySuggestion) 305 | } 306 | 307 | let informativeText = messageComponents.joined(separator: separator) 308 | alert.informativeText = informativeText 309 | 310 | NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) 311 | 312 | alert.runModal() 313 | } 314 | 315 | @objc func toggleLaunchAtLogin(_ item: NSMenuItem) 316 | { 317 | LaunchAtLogin.isEnabled.toggle() 318 | } 319 | 320 | } 321 | 322 | // MARK: - AppDelegate+loginMenuItem 323 | 324 | private extension AppDelegate { 325 | private func setupLoginMenuItem() { 326 | logInMenuItem.isEnabled = true 327 | logInMenuItem.title = "Apple ID Login..." 328 | do { 329 | let email = try Keychain.shared.getValue(for: .appleIDEmail) 330 | //logInMenuItem.isHidden = false 331 | logInMenuItem.title = "Log out (\(email))" 332 | logInMenuItem.action = #selector(logoutFromAppleID) 333 | } catch { 334 | //logInMenuItem.isHidden = false 335 | logInMenuItem.action = #selector(loginToAppleID) 336 | print("Error getting stored Apple ID credentials: \(error)") 337 | } 338 | } 339 | 340 | @objc private func loginToAppleID() { 341 | let alert = AppleIDAuthenticationAlert() 342 | 343 | NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) 344 | let didTapContinue = alert.display() 345 | guard didTapContinue else { return } 346 | 347 | let username = alert.appleIDValue.trimmingCharacters(in: .whitespacesAndNewlines) 348 | let password = alert.passwordValue.trimmingCharacters(in: .whitespacesAndNewlines) 349 | 350 | guard !username.isEmpty && !password.isEmpty else { 351 | print("AppleID Auth: Username and/or password was empty.") 352 | return 353 | } 354 | 355 | do { 356 | try Keychain.shared.setValue(username, for: .appleIDEmail) 357 | try Keychain.shared.setValue(password, for: .appleIDPassword) 358 | } catch { 359 | let errorAlert = NSAlert(error: error) 360 | NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) 361 | errorAlert.runModal() 362 | 363 | print("AppleID Auth: Error saving credentials: \(error)") 364 | } 365 | 366 | setupLoginMenuItem() 367 | } 368 | 369 | @objc private func logoutFromAppleID() { 370 | print("Removing AppleID credentials!") 371 | try? Keychain.shared.setValue(nil, for: .appleIDEmail) 372 | try? Keychain.shared.setValue(nil, for: .appleIDPassword) 373 | setupLoginMenuItem() 374 | } 375 | } 376 | 377 | extension AppDelegate: NSMenuDelegate 378 | { 379 | func menuWillOpen(_ menu: NSMenu) 380 | { 381 | guard menu == self.appMenu else { return } 382 | 383 | setupLoginMenuItem() 384 | 385 | // Clear any cached _jitAppListMenuControllers. 386 | self._jitAppListMenuControllers.removeAll() 387 | 388 | self.connectedDevices = ALTDeviceManager.shared.availableDevices 389 | 390 | self.connectedDevicesMenuController.items = self.connectedDevices 391 | self.sideloadIPAConnectedDevicesMenuController.items = self.connectedDevices 392 | self.enableJITMenuController.items = self.connectedDevices 393 | 394 | self.launchAtLoginMenuItem.target = self 395 | self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:)) 396 | self.launchAtLoginMenuItem.state = LaunchAtLogin.isEnabled ? .on : .off 397 | 398 | // Need to re-set this every time menu appears so we can refresh device app list. 399 | self.enableJITMenuController.submenuHandler = { [weak self] device in 400 | let submenu = NSMenu(title: NSLocalizedString("Sideloaded Apps", comment: "")) 401 | 402 | guard let `self` = self else { return submenu } 403 | 404 | let submenuController = MenuController(menu: submenu, items: []) 405 | submenuController.placeholder = NSLocalizedString("Loading...", comment: "") 406 | submenuController.action = { [weak self] (appInfo) in 407 | self?.enableJIT(for: appInfo, on: device) 408 | } 409 | 410 | // Keep strong reference 411 | self._jitAppListMenuControllers.append(submenuController) 412 | 413 | ALTDeviceManager.shared.fetchInstalledApps(on: device) { (installedApps, error) in 414 | DispatchQueue.main.async { 415 | guard let installedApps = installedApps else { 416 | print("Failed to fetch installed apps from \(device).", error!) 417 | submenuController.placeholder = error?.localizedDescription 418 | return 419 | } 420 | 421 | print("Fetched \(installedApps.count) apps for \(device).") 422 | 423 | let sortedApps = installedApps.sorted { (app1, app2) in 424 | if app1.name == app2.name 425 | { 426 | return app1.bundleIdentifier < app2.bundleIdentifier 427 | } 428 | else 429 | { 430 | return app1.name < app2.name 431 | } 432 | } 433 | 434 | submenuController.items = sortedApps 435 | 436 | if submenuController.items.isEmpty 437 | { 438 | submenuController.placeholder = NSLocalizedString("No Sideloaded Apps", comment: "") 439 | } 440 | } 441 | } 442 | 443 | return submenu 444 | } 445 | } 446 | 447 | func menuDidClose(_ menu: NSMenu) 448 | { 449 | NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) 450 | guard menu == self.appMenu else { return } 451 | 452 | // Clearing _jitAppListMenuControllers now prevents action handler from being called. 453 | // self._jitAppListMenuControllers = [] 454 | 455 | // Set `submenuHandler` to nil to prevent prematurely fetching installed apps in menuWillOpen(_:) 456 | // when assigning self.connectedDevices to `items` (which implicitly calls `submenuHandler`) 457 | self.enableJITMenuController.submenuHandler = nil 458 | } 459 | 460 | func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) 461 | { 462 | guard menu == self.appMenu else { return } 463 | 464 | // The submenu won't update correctly if the user holds/releases 465 | // the Option key while the submenu is visible. 466 | // Workaround: temporarily set submenu to nil to dismiss it, 467 | // which will then cause the correct submenu to appear. 468 | 469 | let previousItem: NSMenuItem 470 | switch item 471 | { 472 | case self.sideloadAppMenuItem: 473 | previousItem = self.installAltStoreMenuItem 474 | case self.installAltStoreMenuItem: 475 | previousItem = self.sideloadAppMenuItem 476 | default: return 477 | } 478 | 479 | let submenu = previousItem.submenu 480 | previousItem.submenu = nil 481 | previousItem.submenu = submenu 482 | } 483 | } 484 | 485 | extension AppDelegate: UNUserNotificationCenterDelegate 486 | { 487 | func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) 488 | { 489 | completionHandler([.alert, .sound, .badge]) 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "Icon-16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "Icon-32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "Icon-32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "Icon-64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "Icon-128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "Icon-256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "Icon-256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "Icon-512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "Icon-512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "Icon-1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon-1024.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon-128.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon-16.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon-256.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon-32.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon-512.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon-64.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@1024.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@128.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@16.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256-1.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32-1.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512-1.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/AppIcon.appiconset/Icon@64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@64.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/MenuBarIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "MenuBar@19.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "MenuBar@38.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "template" 24 | } 25 | } -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@19.png -------------------------------------------------------------------------------- /AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SideStore/SideServer-macOS/4ad482a106f39f779dd3c30d4772bc95aafab683/AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@38.png -------------------------------------------------------------------------------- /AltServer/Categories/NSError+libimobiledevice.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+libimobiledevice.h 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 3/23/21. 6 | // Copyright © 2021 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | #import 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface NSError (libimobiledevice) 18 | 19 | + (nullable instancetype)errorWithMobileImageMounterError:(mobile_image_mounter_error_t)error device:(nullable ALTDevice *)device; 20 | + (nullable instancetype)errorWithDebugServerError:(debugserver_error_t)error device:(nullable ALTDevice *)device; 21 | + (nullable instancetype)errorWithInstallationProxyError:(instproxy_error_t)error device:(nullable ALTDevice *)device; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /AltServer/Categories/NSError+libimobiledevice.mm: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+libimobiledevice.m 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 3/23/21. 6 | // Copyright © 2021 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "NSError+libimobiledevice.h" 10 | #import "NSError+ALTServerError.h" 11 | 12 | @implementation NSError (libimobiledevice) 13 | 14 | + (nullable instancetype)errorWithMobileImageMounterError:(mobile_image_mounter_error_t)error device:(nullable ALTDevice *)device 15 | { 16 | NSMutableDictionary *userInfo = [@{ 17 | ALTUnderlyingErrorDomainErrorKey: @"Mobile Image Mounter", 18 | ALTUnderlyingErrorCodeErrorKey: [@(error) description], 19 | } mutableCopy]; 20 | 21 | if (device) 22 | { 23 | userInfo[ALTDeviceNameErrorKey] = device.name; 24 | } 25 | 26 | switch (error) 27 | { 28 | case MOBILE_IMAGE_MOUNTER_E_SUCCESS: return nil; 29 | case MOBILE_IMAGE_MOUNTER_E_INVALID_ARG: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo]; 30 | case MOBILE_IMAGE_MOUNTER_E_PLIST_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidResponse userInfo:userInfo]; 31 | case MOBILE_IMAGE_MOUNTER_E_CONN_FAILED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUsbmuxd userInfo:userInfo]; 32 | case MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo]; 33 | case MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorDeviceLocked userInfo:userInfo]; 34 | case MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:userInfo]; 35 | } 36 | } 37 | 38 | + (nullable instancetype)errorWithDebugServerError:(debugserver_error_t)error device:(nullable ALTDevice *)device 39 | { 40 | NSMutableDictionary *userInfo = [@{ 41 | ALTUnderlyingErrorDomainErrorKey: @"Debug Server", 42 | ALTUnderlyingErrorCodeErrorKey: [@(error) description], 43 | } mutableCopy]; 44 | 45 | if (device) 46 | { 47 | userInfo[ALTDeviceNameErrorKey] = device.name; 48 | } 49 | 50 | switch (error) 51 | { 52 | case DEBUGSERVER_E_SUCCESS: return nil; 53 | case DEBUGSERVER_E_INVALID_ARG: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo]; 54 | case DEBUGSERVER_E_MUX_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUsbmuxd userInfo:userInfo]; 55 | case DEBUGSERVER_E_SSL_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorSSL userInfo:userInfo]; 56 | case DEBUGSERVER_E_RESPONSE_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidResponse userInfo:userInfo]; 57 | case DEBUGSERVER_E_TIMEOUT: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorTimedOut userInfo:userInfo]; 58 | case DEBUGSERVER_E_UNKNOWN_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:userInfo]; 59 | } 60 | } 61 | 62 | + (nullable instancetype)errorWithInstallationProxyError:(instproxy_error_t)error device:(nullable ALTDevice *)device 63 | { 64 | NSMutableDictionary *userInfo = [@{ 65 | ALTUnderlyingErrorDomainErrorKey: @"Installation Proxy", 66 | ALTUnderlyingErrorCodeErrorKey: [@(error) description], 67 | } mutableCopy]; 68 | 69 | if (device) 70 | { 71 | userInfo[ALTDeviceNameErrorKey] = device.name; 72 | } 73 | 74 | switch (error) 75 | { 76 | case INSTPROXY_E_SUCCESS: return nil; 77 | case INSTPROXY_E_INVALID_ARG: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo]; 78 | case INSTPROXY_E_PLIST_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidResponse userInfo:userInfo]; 79 | case INSTPROXY_E_CONN_FAILED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUsbmuxd userInfo:userInfo]; 80 | case INSTPROXY_E_RECEIVE_TIMEOUT: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorTimedOut userInfo:userInfo]; 81 | // case INSTPROXY_E_DEVICE_OS_VERSION_TOO_LOW: return [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnsupportediOSVersion userInfo:nil]; // Error message assumes we're installing AltStore 82 | default: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:userInfo]; 83 | } 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /AltServer/Connections/ALTDebugConnection+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // ALTDebugConnection+Private.h 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 2/19/21. 6 | // Copyright © 2021 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "ALTDebugConnection.h" 10 | 11 | #include 12 | #include 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface ALTDebugConnection () 17 | 18 | @property (nonatomic, readonly) dispatch_queue_t connectionQueue; 19 | 20 | @property (nonatomic, nullable) debugserver_client_t client; 21 | 22 | - (instancetype)initWithDevice:(ALTDevice *)device; 23 | 24 | - (void)connectWithCompletionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /AltServer/Connections/ALTDebugConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // ALTDebugConnection.h 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 2/19/21. 6 | // Copyright © 2021 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "AltSign.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | NS_SWIFT_NAME(DebugConnection) 14 | @interface ALTDebugConnection : NSObject 15 | 16 | @property (nonatomic, copy, readonly) ALTDevice *device; 17 | 18 | - (void)enableUnsignedCodeExecutionForProcessWithName:(NSString *)processName completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 19 | - (void)enableUnsignedCodeExecutionForProcessWithID:(NSInteger)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 20 | 21 | - (void)disconnect; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /AltServer/Connections/ALTDebugConnection.mm: -------------------------------------------------------------------------------- 1 | // 2 | // ALTDebugConnection.m 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 2/19/21. 6 | // Copyright © 2021 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "ALTDebugConnection+Private.h" 10 | 11 | #import "NSError+ALTServerError.h" 12 | #import "NSError+libimobiledevice.h" 13 | 14 | char *bin2hex(const unsigned char *bin, size_t length) 15 | { 16 | if (bin == NULL || length == 0) 17 | { 18 | return NULL; 19 | } 20 | 21 | char *hex = (char *)malloc(length * 2 + 1); 22 | for (size_t i = 0; i < length; i++) 23 | { 24 | hex[i * 2] = "0123456789ABCDEF"[bin[i] >> 4]; 25 | hex[i * 2 + 1] = "0123456789ABCDEF"[bin[i] & 0x0F]; 26 | } 27 | hex[length * 2] = '\0'; 28 | 29 | return hex; 30 | } 31 | 32 | @implementation ALTDebugConnection 33 | 34 | - (instancetype)initWithDevice:(ALTDevice *)device 35 | { 36 | self = [super init]; 37 | if (self) 38 | { 39 | _device = device; 40 | _connectionQueue = dispatch_queue_create_with_target("io.altstore.AltServer.DebugConnection", 41 | DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL, 42 | dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)); 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (void)dealloc 49 | { 50 | [self disconnect]; 51 | } 52 | 53 | - (void)disconnect 54 | { 55 | if (_client == nil) 56 | { 57 | return; 58 | } 59 | 60 | debugserver_client_free(_client); 61 | _client = nil; 62 | } 63 | 64 | - (void)connectWithCompletionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler 65 | { 66 | __block idevice_t device = NULL; 67 | 68 | void (^finish)(BOOL, NSError *) = ^(BOOL success, NSError *error) { 69 | if (device) 70 | { 71 | idevice_free(device); 72 | } 73 | 74 | completionHandler(success, error); 75 | }; 76 | 77 | dispatch_async(self.connectionQueue, ^{ 78 | /* Find Device */ 79 | if (idevice_new_with_options(&device, self.device.identifier.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) 80 | { 81 | return finish(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); 82 | } 83 | 84 | /* Connect to debugserver */ 85 | debugserver_client_t client = NULL; 86 | debugserver_error_t error = debugserver_client_start_service(device, &client, "AltServer"); 87 | if (error != DEBUGSERVER_E_SUCCESS) 88 | { 89 | return finish(NO, [NSError errorWithDebugServerError:error device:self.device]); 90 | } 91 | 92 | self.client = client; 93 | 94 | finish(YES, nil); 95 | }); 96 | } 97 | 98 | - (void)enableUnsignedCodeExecutionForProcessWithName:(NSString *)processName completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler 99 | { 100 | [self _enableUnsignedCodeExecutionForProcessWithName:processName pid:0 completionHandler:completionHandler]; 101 | } 102 | 103 | - (void)enableUnsignedCodeExecutionForProcessWithID:(NSInteger)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler 104 | { 105 | [self _enableUnsignedCodeExecutionForProcessWithName:nil pid:(int32_t)pid completionHandler:completionHandler]; 106 | } 107 | 108 | - (void)_enableUnsignedCodeExecutionForProcessWithName:(nullable NSString *)processName pid:(int32_t)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler 109 | { 110 | dispatch_async(self.connectionQueue, ^{ 111 | NSString *name = processName ?: NSLocalizedString(@"this app", @""); 112 | NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"JIT could not be enabled for %@.", comment: @""), name]; 113 | 114 | NSString *attachCommand = nil; 115 | 116 | if (processName) 117 | { 118 | NSString *encodedName = @(bin2hex((const unsigned char *)processName.UTF8String, (size_t)strlen(processName.UTF8String))); 119 | attachCommand = [NSString stringWithFormat:@"vAttachOrWait;%@", encodedName]; 120 | } 121 | else 122 | { 123 | // Convert to Big-endian. 124 | int32_t rawPID = CFSwapInt32HostToBig(pid); 125 | 126 | NSString *encodedName = @(bin2hex((const unsigned char *)&rawPID, 4)); 127 | attachCommand = [NSString stringWithFormat:@"vAttach;%@", encodedName]; 128 | } 129 | 130 | NSError *error = nil; 131 | if (![self sendCommand:attachCommand arguments:nil error:&error]) 132 | { 133 | NSMutableDictionary *userInfo = [error.userInfo mutableCopy]; 134 | userInfo[ALTAppNameErrorKey] = processName; 135 | userInfo[ALTDeviceNameErrorKey] = self.device.name; 136 | userInfo[NSLocalizedFailureErrorKey] = localizedFailure; 137 | 138 | NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; 139 | return completionHandler(NO, returnError); 140 | } 141 | 142 | NSString *detachCommand = @"D"; 143 | if (![self sendCommand:detachCommand arguments:nil error:&error]) 144 | { 145 | NSMutableDictionary *userInfo = [error.userInfo mutableCopy]; 146 | userInfo[ALTAppNameErrorKey] = processName; 147 | userInfo[ALTDeviceNameErrorKey] = self.device.name; 148 | userInfo[NSLocalizedFailureErrorKey] = localizedFailure; 149 | 150 | NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; 151 | return completionHandler(NO, returnError); 152 | } 153 | 154 | completionHandler(YES, nil); 155 | }); 156 | } 157 | 158 | #pragma mark - Private - 159 | 160 | - (BOOL)sendCommand:(NSString *)command arguments:(nullable NSArray *)arguments error:(NSError **)error 161 | { 162 | int argc = (int)arguments.count; 163 | char **argv = new char*[argc + 1]; 164 | 165 | for (int i = 0; i < argc; i++) 166 | { 167 | NSString *argument = arguments[i]; 168 | argv[i] = (char *)argument.UTF8String; 169 | } 170 | 171 | argv[argc] = NULL; 172 | 173 | debugserver_command_t debugCommand = NULL; 174 | debugserver_command_new(command.UTF8String, argc, argv, &debugCommand); 175 | 176 | delete[] argv; 177 | 178 | char *response = NULL; 179 | size_t responseSize = 0; 180 | debugserver_error_t debugServerError = debugserver_client_send_command(self.client, debugCommand, &response, &responseSize); 181 | debugserver_command_free(debugCommand); 182 | 183 | if (debugServerError != DEBUGSERVER_E_SUCCESS) 184 | { 185 | if (error) 186 | { 187 | *error = [NSError errorWithDebugServerError:debugServerError device:self.device]; 188 | } 189 | 190 | return NO; 191 | } 192 | 193 | NSString *rawResponse = (response != nil) ? @(response) : nil; 194 | if (![self processResponse:rawResponse error:error]) 195 | { 196 | return NO; 197 | } 198 | 199 | return YES; 200 | } 201 | 202 | - (BOOL)processResponse:(NSString *)rawResponse error:(NSError **)error 203 | { 204 | if (rawResponse == nil) 205 | { 206 | if (error) 207 | { 208 | *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil]; 209 | return NO; 210 | } 211 | } 212 | 213 | if (rawResponse.length == 0 || [rawResponse isEqualToString:@"OK"]) 214 | { 215 | return YES; 216 | } 217 | 218 | char type = [rawResponse characterAtIndex:0]; 219 | NSString *response = [rawResponse substringFromIndex:1]; 220 | 221 | switch (type) 222 | { 223 | case 'O': 224 | { 225 | // stdout/stderr 226 | 227 | char *decodedResponse = NULL; 228 | debugserver_decode_string(response.UTF8String, response.length, &decodedResponse); 229 | 230 | NSLog(@"Response: %@", @(decodedResponse)); 231 | 232 | if (decodedResponse) 233 | { 234 | free(decodedResponse); 235 | } 236 | 237 | return YES; 238 | } 239 | 240 | case 'T': 241 | { 242 | // Thread Information 243 | 244 | NSLog(@"Thread stopped. Details:\n%s", response.UTF8String + 1); 245 | 246 | // Parse thread state to determine if app is running. 247 | NSArray *components = [response componentsSeparatedByString:@";"]; 248 | if (components.count <= 1) 249 | { 250 | if (error) 251 | { 252 | *error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}]; 253 | } 254 | 255 | return NO; 256 | } 257 | 258 | NSString *mainThread = components[1]; 259 | NSString *threadState = [[mainThread componentsSeparatedByString:@":"] lastObject]; 260 | 261 | NSScanner *scanner = [NSScanner scannerWithString:threadState]; 262 | 263 | unsigned long long mainThreadState = 0; 264 | [scanner scanHexLongLong:&mainThreadState]; 265 | 266 | // If main thread state == 0, app is not running. 267 | if (mainThreadState == 0) 268 | { 269 | if (error) 270 | { 271 | *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil]; 272 | } 273 | 274 | return NO; 275 | } 276 | 277 | return YES; 278 | } 279 | 280 | case 'E': 281 | { 282 | // Error 283 | 284 | if (error) 285 | { 286 | NSInteger errorCode = [[[response componentsSeparatedByString:@";"] firstObject] integerValue]; 287 | 288 | switch (errorCode) 289 | { 290 | case 96: 291 | *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil]; 292 | break; 293 | 294 | default: 295 | *error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}]; 296 | break; 297 | } 298 | } 299 | 300 | return NO; 301 | } 302 | 303 | case 'W': 304 | { 305 | // Warning 306 | 307 | NSLog(@"WARNING: %@", response); 308 | return YES; 309 | } 310 | } 311 | 312 | return YES; 313 | } 314 | 315 | @end 316 | -------------------------------------------------------------------------------- /AltServer/Connections/ALTNotificationConnection+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // ALTNotificationConnection+Private.h 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 1/10/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "ALTNotificationConnection.h" 10 | 11 | #include 12 | #include 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface ALTNotificationConnection () 17 | 18 | @property (nonatomic, readonly) np_client_t client; 19 | 20 | - (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /AltServer/Connections/ALTNotificationConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // ALTNotificationConnection.h 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 1/10/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "AltSign.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | NS_SWIFT_NAME(NotificationConnection) 14 | @interface ALTNotificationConnection : NSObject 15 | 16 | @property (nonatomic, copy, readonly) ALTDevice *device; 17 | 18 | @property (nonatomic, copy, nullable) void (^receivedNotificationHandler)(CFNotificationName notification); 19 | 20 | - (void)startListeningForNotifications:(NSArray *)notifications 21 | completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 22 | 23 | - (void)sendNotification:(CFNotificationName)notification 24 | completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 25 | 26 | - (void)disconnect; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /AltServer/Connections/ALTNotificationConnection.mm: -------------------------------------------------------------------------------- 1 | // 2 | // ALTNotificationConnection.m 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 1/10/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "ALTNotificationConnection+Private.h" 10 | 11 | #import "NSError+ALTServerError.h" 12 | 13 | void ALTDeviceReceivedNotification(const char *notification, void *user_data); 14 | 15 | @implementation ALTNotificationConnection 16 | 17 | - (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client 18 | { 19 | self = [super init]; 20 | if (self) 21 | { 22 | _device = [device copy]; 23 | _client = client; 24 | } 25 | 26 | return self; 27 | } 28 | 29 | - (void)dealloc 30 | { 31 | [self disconnect]; 32 | } 33 | 34 | - (void)disconnect 35 | { 36 | np_client_free(self.client); 37 | _client = nil; 38 | } 39 | 40 | - (void)startListeningForNotifications:(NSArray *)notifications completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler 41 | { 42 | dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ 43 | const char **notificationNames = (const char **)malloc((notifications.count + 1) * sizeof(char *)); 44 | for (int i = 0; i < notifications.count; i++) 45 | { 46 | NSString *name = notifications[i]; 47 | notificationNames[i] = name.UTF8String; 48 | } 49 | notificationNames[notifications.count] = NULL; // Must have terminating NULL entry. 50 | 51 | np_error_t result = np_observe_notifications(self.client, notificationNames); 52 | if (result != NP_E_SUCCESS) 53 | { 54 | return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); 55 | } 56 | 57 | result = np_set_notify_callback(self.client, ALTDeviceReceivedNotification, (__bridge void *)self); 58 | if (result != NP_E_SUCCESS) 59 | { 60 | return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); 61 | } 62 | 63 | completionHandler(YES, nil); 64 | 65 | free(notificationNames); 66 | }); 67 | } 68 | 69 | - (void)sendNotification:(CFNotificationName)notification completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler 70 | { 71 | dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ 72 | np_error_t result = np_post_notification(self.client, [(__bridge NSString *)notification UTF8String]); 73 | if (result == NP_E_SUCCESS) 74 | { 75 | completionHandler(YES, nil); 76 | } 77 | else 78 | { 79 | completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); 80 | } 81 | }); 82 | } 83 | 84 | @end 85 | 86 | void ALTDeviceReceivedNotification(const char *notification, void *user_data) 87 | { 88 | ALTNotificationConnection *connection = (__bridge ALTNotificationConnection *)user_data; 89 | 90 | if (connection.receivedNotificationHandler) 91 | { 92 | connection.receivedNotificationHandler((__bridge CFNotificationName)@(notification)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /AltServer/Connections/ALTWiredConnection+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // ALTWiredConnection+Private.h 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 1/10/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "ALTWiredConnection.h" 10 | 11 | #include 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface ALTWiredConnection () 16 | 17 | @property (nonatomic, readwrite, getter=isConnected) BOOL connected; 18 | 19 | @property (nonatomic, readonly) idevice_connection_t connection; 20 | 21 | - (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /AltServer/Connections/ALTWiredConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // ALTWiredConnection.h 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 1/10/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "AltSign.h" 10 | 11 | #import "ALTConnection.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | NS_SWIFT_NAME(WiredConnection) 16 | @interface ALTWiredConnection : NSObject 17 | 18 | @property (nonatomic, readonly, getter=isConnected) BOOL connected; 19 | 20 | @property (nonatomic, copy, readonly) ALTDevice *device; 21 | 22 | - (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler; 23 | - (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler; 24 | 25 | - (void)disconnect; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /AltServer/Connections/ALTWiredConnection.mm: -------------------------------------------------------------------------------- 1 | // 2 | // ALTWiredConnection.m 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 1/10/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "ALTWiredConnection+Private.h" 10 | 11 | #import "ALTConnection.h" 12 | #import "NSError+ALTServerError.h" 13 | 14 | @implementation ALTWiredConnection 15 | 16 | - (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection 17 | { 18 | self = [super init]; 19 | if (self) 20 | { 21 | _device = [device copy]; 22 | _connection = connection; 23 | 24 | self.connected = YES; 25 | } 26 | 27 | return self; 28 | } 29 | 30 | - (void)dealloc 31 | { 32 | [self disconnect]; 33 | } 34 | 35 | - (void)disconnect 36 | { 37 | if (![self isConnected]) 38 | { 39 | return; 40 | } 41 | 42 | idevice_disconnect(self.connection); 43 | _connection = nil; 44 | 45 | self.connected = NO; 46 | } 47 | 48 | - (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler 49 | { 50 | void (^finish)(NSError *error) = ^(NSError *error) { 51 | if (error != nil) 52 | { 53 | NSLog(@"Send Error: %@", error); 54 | completionHandler(NO, error); 55 | } 56 | else 57 | { 58 | completionHandler(YES, nil); 59 | } 60 | }; 61 | 62 | dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ 63 | NSMutableData *mutableData = [data mutableCopy]; 64 | while (mutableData.length > 0) 65 | { 66 | uint32_t sentBytes = 0; 67 | if (idevice_connection_send(self.connection, (const char *)mutableData.bytes, (int32_t)mutableData.length, &sentBytes) != IDEVICE_E_SUCCESS) 68 | { 69 | return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); 70 | } 71 | 72 | [mutableData replaceBytesInRange:NSMakeRange(0, sentBytes) withBytes:NULL length:0]; 73 | } 74 | 75 | finish(nil); 76 | }); 77 | } 78 | 79 | - (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler 80 | { 81 | void (^finish)(NSData *data, NSError *error) = ^(NSData *data, NSError *error) { 82 | if (error != nil) 83 | { 84 | NSLog(@"Receive Data Error: %@", error); 85 | } 86 | 87 | completionHandler(data, error); 88 | }; 89 | 90 | dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ 91 | char bytes[4096]; 92 | NSMutableData *receivedData = [NSMutableData dataWithCapacity:expectedSize]; 93 | 94 | while (receivedData.length < expectedSize) 95 | { 96 | uint32_t size = MIN(4096, (uint32_t)expectedSize - (uint32_t)receivedData.length); 97 | 98 | uint32_t receivedBytes = 0; 99 | if (idevice_connection_receive_timeout(self.connection, bytes, size, &receivedBytes, 10000) != IDEVICE_E_SUCCESS) 100 | { 101 | return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); 102 | } 103 | 104 | NSData *data = [NSData dataWithBytesNoCopy:bytes length:receivedBytes freeWhenDone:NO]; 105 | [receivedData appendData:data]; 106 | } 107 | 108 | finish(receivedData, nil); 109 | }); 110 | } 111 | 112 | #pragma mark - NSObject - 113 | 114 | - (NSString *)description 115 | { 116 | return [NSString stringWithFormat:@"%@ (Wired)", self.device.name]; 117 | } 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /AltServer/Connections/RequestHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestHandler.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 5/23/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias ServerConnectionManager = ConnectionManager 12 | 13 | private let connectionManager = ConnectionManager(requestHandler: ServerRequestHandler(), 14 | connectionHandlers: [WirelessConnectionHandler(), WiredConnectionHandler()]) 15 | 16 | extension ServerConnectionManager 17 | { 18 | static var shared: ConnectionManager { 19 | return connectionManager 20 | } 21 | } 22 | 23 | struct ServerRequestHandler: RequestHandler 24 | { 25 | func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) 26 | { 27 | AnisetteDataManager.shared.requestAnisetteData { (result) in 28 | switch result 29 | { 30 | case .failure(let error): completionHandler(.failure(error)) 31 | case .success(let anisetteData): 32 | let response = AnisetteDataResponse(anisetteData: anisetteData) 33 | completionHandler(.success(response)) 34 | } 35 | } 36 | } 37 | 38 | func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) 39 | { 40 | var temporaryURL: URL? 41 | 42 | func finish(_ result: Result) 43 | { 44 | if let temporaryURL = temporaryURL 45 | { 46 | do { try FileManager.default.removeItem(at: temporaryURL) } 47 | catch { print("Failed to remove .ipa.", error) } 48 | } 49 | 50 | completionHandler(result) 51 | } 52 | 53 | self.receiveApp(for: request, from: connection) { (result) in 54 | print("Received app with result:", result) 55 | 56 | switch result 57 | { 58 | case .failure(let error): finish(.failure(error)) 59 | case .success(let fileURL): 60 | temporaryURL = fileURL 61 | 62 | print("Awaiting begin installation request...") 63 | 64 | connection.receiveRequest() { (result) in 65 | print("Received begin installation request with result:", result) 66 | 67 | switch result 68 | { 69 | case .failure(let error): finish(.failure(error)) 70 | case .success(.beginInstallation(let installRequest)): 71 | print("Installing app to device \(request.udid)...") 72 | 73 | self.installApp(at: fileURL, toDeviceWithUDID: request.udid, activeProvisioningProfiles: installRequest.activeProfiles, connection: connection) { (result) in 74 | print("Installed app to device with result:", result) 75 | switch result 76 | { 77 | case .failure(let error): finish(.failure(error)) 78 | case .success: 79 | let response = InstallationProgressResponse(progress: 1.0) 80 | finish(.success(response)) 81 | } 82 | } 83 | 84 | case .success: finish(.failure(ALTServerError(.unknownRequest))) 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection, 92 | completionHandler: @escaping (Result) -> Void) 93 | { 94 | ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles) { (success, error) in 95 | if let error = error, !success 96 | { 97 | print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error) 98 | completionHandler(.failure(ALTServerError(error))) 99 | } 100 | else 101 | { 102 | print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier }) 103 | 104 | let response = InstallProvisioningProfilesResponse() 105 | completionHandler(.success(response)) 106 | } 107 | } 108 | } 109 | 110 | func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection, 111 | completionHandler: @escaping (Result) -> Void) 112 | { 113 | ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (success, error) in 114 | if let error = error, !success 115 | { 116 | print("Failed to remove profiles \(request.bundleIdentifiers):", error) 117 | completionHandler(.failure(ALTServerError(error))) 118 | } 119 | else 120 | { 121 | print("Removed profiles:", request.bundleIdentifiers) 122 | 123 | let response = RemoveProvisioningProfilesResponse() 124 | completionHandler(.success(response)) 125 | } 126 | } 127 | } 128 | 129 | func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) 130 | { 131 | ALTDeviceManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier, fromDeviceWithUDID: request.udid) { (success, error) in 132 | if let error = error, !success 133 | { 134 | print("Failed to remove app \(request.bundleIdentifier):", error) 135 | completionHandler(.failure(ALTServerError(error))) 136 | } 137 | else 138 | { 139 | print("Removed app:", request.bundleIdentifier) 140 | 141 | let response = RemoveAppResponse() 142 | completionHandler(.success(response)) 143 | } 144 | } 145 | } 146 | 147 | func handleEnableUnsignedCodeExecutionRequest(_ request: EnableUnsignedCodeExecutionRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) 148 | { 149 | guard let device = ALTDeviceManager.shared.availableDevices.first(where: { $0.identifier == request.udid }) else { return completionHandler(.failure(ALTServerError(.deviceNotFound))) } 150 | 151 | ALTDeviceManager.shared.prepare(device) { result in 152 | switch result 153 | { 154 | case .failure(let error): completionHandler(.failure(error)) 155 | case .success: 156 | ALTDeviceManager.shared.startDebugConnection(to: device) { (connection, error) in 157 | guard let connection = connection else { return completionHandler(.failure(error!)) } 158 | 159 | func finish(success: Bool, error: Error?) 160 | { 161 | if let error = error, !success 162 | { 163 | print("Failed to enable unsigned code execution for process \(request.processID?.description ?? request.processName ?? "nil"):", error) 164 | completionHandler(.failure(ALTServerError(error))) 165 | } 166 | else 167 | { 168 | print("Enabled unsigned code execution for process:", request.processID ?? request.processName ?? "nil") 169 | 170 | let response = EnableUnsignedCodeExecutionResponse() 171 | completionHandler(.success(response)) 172 | } 173 | } 174 | 175 | if let processID = request.processID 176 | { 177 | connection.enableUnsignedCodeExecutionForProcess(withID: processID, completionHandler: finish) 178 | } 179 | else if let processName = request.processName 180 | { 181 | connection.enableUnsignedCodeExecutionForProcess(withName: processName, completionHandler: finish) 182 | } 183 | else 184 | { 185 | finish(success: false, error: ALTServerError(.invalidRequest)) 186 | } 187 | } 188 | } 189 | } 190 | } 191 | } 192 | 193 | private extension RequestHandler 194 | { 195 | func receiveApp(for request: PrepareAppRequest, from connection: Connection, completionHandler: @escaping (Result) -> Void) 196 | { 197 | connection.receiveData(expectedSize: request.contentSize) { (result) in 198 | do 199 | { 200 | print("Received app data!") 201 | 202 | let data = try result.get() 203 | 204 | guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) } 205 | 206 | print("Writing app data...") 207 | 208 | let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa") 209 | try data.write(to: temporaryURL, options: .atomic) 210 | 211 | print("Wrote app to URL:", temporaryURL) 212 | 213 | completionHandler(.success(temporaryURL)) 214 | } 215 | catch 216 | { 217 | print("Error processing app data:", error) 218 | 219 | completionHandler(.failure(ALTServerError(error))) 220 | } 221 | } 222 | } 223 | 224 | func installApp(at fileURL: URL, toDeviceWithUDID udid: String, activeProvisioningProfiles: Set?, connection: Connection, completionHandler: @escaping (Result) -> Void) 225 | { 226 | let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default) 227 | var isSending = false 228 | 229 | var observation: NSKeyValueObservation? 230 | 231 | let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in 232 | print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription) 233 | 234 | if let error = error.map({ ALTServerError($0) }) 235 | { 236 | completionHandler(.failure(error)) 237 | } 238 | else 239 | { 240 | completionHandler(.success(())) 241 | } 242 | 243 | observation?.invalidate() 244 | observation = nil 245 | } 246 | 247 | observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in 248 | serialQueue.async { 249 | guard !isSending else { return } 250 | isSending = true 251 | 252 | print("Progress:", progress.fractionCompleted) 253 | let response = InstallationProgressResponse(progress: progress.fractionCompleted) 254 | 255 | connection.send(response) { (result) in 256 | serialQueue.async { 257 | isSending = false 258 | } 259 | } 260 | } 261 | }) 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /AltServer/Connections/WiredConnectionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WiredConnectionHandler.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 6/1/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class WiredConnectionHandler: ConnectionHandler 12 | { 13 | var connectionHandler: ((Connection) -> Void)? 14 | var disconnectionHandler: ((Connection) -> Void)? 15 | 16 | private var notificationConnections = [ALTDevice: NotificationConnection]() 17 | private let queue = DispatchQueue(label: "WiredConnectionHandler", autoreleaseFrequency: .workItem, target: .global(qos: .utility)) 18 | 19 | func startListening() 20 | { 21 | NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil) 22 | NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil) 23 | } 24 | 25 | func stopListening() 26 | { 27 | NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidConnect, object: nil) 28 | NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidDisconnect, object: nil) 29 | } 30 | } 31 | 32 | private extension WiredConnectionHandler 33 | { 34 | func startNotificationConnection(to device: ALTDevice) 35 | { 36 | self.queue.async { 37 | ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in 38 | guard let connection = connection else { return } 39 | 40 | let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest] 41 | connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in 42 | guard success else { return } 43 | 44 | self.queue.async { 45 | connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in 46 | guard let self = self, let connection = connection else { return } 47 | self.handle(notification, for: connection) 48 | } 49 | 50 | self.notificationConnections[device] = connection 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | func stopNotificationConnection(to device: ALTDevice) 58 | { 59 | self.queue.async { 60 | guard let connection = self.notificationConnections[device] else { return } 61 | connection.disconnect() 62 | 63 | self.notificationConnections[device] = nil 64 | } 65 | } 66 | 67 | func handle(_ notification: CFNotificationName, for connection: NotificationConnection) 68 | { 69 | switch notification 70 | { 71 | case .wiredServerConnectionAvailableRequest: 72 | connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in 73 | if let error = error, !success 74 | { 75 | print("Error sending wired server connection response.", error) 76 | } 77 | else 78 | { 79 | print("Sent wired server connection available response!") 80 | } 81 | } 82 | 83 | case .wiredServerConnectionStartRequest: 84 | ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in 85 | if let wiredConnection = wiredConnection 86 | { 87 | print("Started wired server connection!") 88 | self.connectionHandler?(wiredConnection) 89 | 90 | var observation: NSKeyValueObservation? 91 | observation = wiredConnection.observe(\.isConnected) { [weak self] (connection, change) in 92 | guard !connection.isConnected else { return } 93 | self?.disconnectionHandler?(connection) 94 | 95 | observation?.invalidate() 96 | } 97 | } 98 | else if let error = error 99 | { 100 | print("Error starting wired server connection.", error) 101 | } 102 | } 103 | 104 | default: break 105 | } 106 | } 107 | } 108 | 109 | private extension WiredConnectionHandler 110 | { 111 | @objc func deviceDidConnect(_ notification: Notification) 112 | { 113 | guard let device = notification.object as? ALTDevice else { return } 114 | self.startNotificationConnection(to: device) 115 | } 116 | 117 | @objc func deviceDidDisconnect(_ notification: Notification) 118 | { 119 | guard let device = notification.object as? ALTDevice else { return } 120 | self.stopNotificationConnection(to: device) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /AltServer/Connections/WirelessConnectionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WirelessConnectionHandler.swift 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 6/1/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Network 11 | 12 | extension WirelessConnectionHandler 13 | { 14 | public enum State 15 | { 16 | case notRunning 17 | case connecting 18 | case running(NWListener.Service) 19 | case failed(Swift.Error) 20 | } 21 | } 22 | 23 | public class WirelessConnectionHandler: ConnectionHandler 24 | { 25 | public var connectionHandler: ((Connection) -> Void)? 26 | public var disconnectionHandler: ((Connection) -> Void)? 27 | 28 | public var stateUpdateHandler: ((State) -> Void)? 29 | 30 | public private(set) var state: State = .notRunning { 31 | didSet { 32 | self.stateUpdateHandler?(self.state) 33 | } 34 | } 35 | 36 | private lazy var listener = self.makeListener() 37 | private let dispatchQueue = DispatchQueue(label: "io.altstore.WirelessConnectionListener", qos: .utility) 38 | 39 | public func startListening() 40 | { 41 | switch self.state 42 | { 43 | case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue) 44 | default: break 45 | } 46 | } 47 | 48 | public func stopListening() 49 | { 50 | switch self.state 51 | { 52 | case .running: self.listener.cancel() 53 | default: break 54 | } 55 | } 56 | } 57 | 58 | private extension WirelessConnectionHandler 59 | { 60 | func makeListener() -> NWListener 61 | { 62 | let listener = try! NWListener(using: .tcp) 63 | 64 | let service: NWListener.Service 65 | 66 | if let serverID = UserDefaults.standard.serverID?.data(using: .utf8) 67 | { 68 | let txtDictionary = ["serverID": serverID] 69 | let txtData = NetService.data(fromTXTRecord: txtDictionary) 70 | 71 | service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData) 72 | } 73 | else 74 | { 75 | service = NWListener.Service(type: ALTServerServiceType) 76 | } 77 | 78 | listener.service = service 79 | 80 | listener.serviceRegistrationUpdateHandler = { (serviceChange) in 81 | switch serviceChange 82 | { 83 | case .add(.service(let name, let type, let domain, _)): 84 | let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil) 85 | self.state = .running(service) 86 | 87 | default: break 88 | } 89 | } 90 | 91 | listener.stateUpdateHandler = { (state) in 92 | switch state 93 | { 94 | case .ready: break 95 | case .waiting, .setup: self.state = .connecting 96 | case .cancelled: self.state = .notRunning 97 | case .failed(let error): self.state = .failed(error) 98 | @unknown default: break 99 | } 100 | } 101 | 102 | listener.newConnectionHandler = { [weak self] (connection) in 103 | self?.prepare(connection) 104 | } 105 | 106 | return listener 107 | } 108 | 109 | func prepare(_ nwConnection: NWConnection) 110 | { 111 | print("Preparing:", nwConnection) 112 | 113 | // Use same instance for all callbacks. 114 | let connection = NetworkConnection(nwConnection) 115 | 116 | nwConnection.stateUpdateHandler = { [weak self] (state) in 117 | switch state 118 | { 119 | case .setup, .preparing: break 120 | 121 | case .ready: 122 | print("Connected to client:", connection) 123 | self?.connectionHandler?(connection) 124 | 125 | case .waiting: 126 | print("Waiting for connection...") 127 | 128 | case .failed(let error): 129 | print("Failed to connect to service \(nwConnection.endpoint).", error) 130 | self?.disconnect(connection) 131 | 132 | case .cancelled: 133 | self?.disconnect(connection) 134 | 135 | @unknown default: break 136 | } 137 | } 138 | 139 | nwConnection.start(queue: self.dispatchQueue) 140 | } 141 | 142 | func disconnect(_ connection: Connection) 143 | { 144 | connection.disconnect() 145 | 146 | self.disconnectionHandler?(connection) 147 | 148 | if let networkConnection = connection as? NetworkConnection 149 | { 150 | networkConnection.nwConnection.stateUpdateHandler = nil 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /AltServer/DeveloperDiskManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeveloperDiskManager.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 2/19/21. 6 | // Copyright © 2021 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import AltSign 12 | 13 | enum DeveloperDiskError: LocalizedError 14 | { 15 | case unknownDownloadURL 16 | case unsupportedOperatingSystem 17 | case downloadedDiskNotFound 18 | 19 | var errorDescription: String? { 20 | switch self 21 | { 22 | case .unknownDownloadURL: return NSLocalizedString("The URL to download the Developer disk image could not be determined.", comment: "") 23 | case .unsupportedOperatingSystem: return NSLocalizedString("The device's operating system does not support installing Developer disk images.", comment: "") 24 | case .downloadedDiskNotFound: return NSLocalizedString("DeveloperDiskImage.dmg and its signature could not be found in the downloaded archive.", comment: "") 25 | } 26 | } 27 | } 28 | 29 | private extension URL 30 | { 31 | #if STAGING 32 | static let developerDiskDownloadURLs = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altserver/developerdisks.json")! 33 | #else 34 | static let developerDiskDownloadURLs = URL(string: "https://cdn.altstore.io/file/altstore/altserver/developerdisks.json")! 35 | #endif 36 | } 37 | 38 | private extension DeveloperDiskManager 39 | { 40 | struct FetchURLsResponse: Decodable 41 | { 42 | struct Disks: Decodable 43 | { 44 | var iOS: [String: DeveloperDiskURL]? 45 | var tvOS: [String: DeveloperDiskURL]? 46 | } 47 | 48 | var version: Int 49 | var disks: Disks 50 | } 51 | 52 | enum DeveloperDiskURL: Decodable 53 | { 54 | case archive(URL) 55 | case separate(diskURL: URL, signatureURL: URL) 56 | 57 | private enum CodingKeys: CodingKey 58 | { 59 | case archive 60 | case disk 61 | case signature 62 | } 63 | 64 | init(from decoder: Decoder) throws 65 | { 66 | let container = try decoder.container(keyedBy: CodingKeys.self) 67 | 68 | if container.contains(.archive) 69 | { 70 | let archiveURL = try container.decode(URL.self, forKey: .archive) 71 | self = .archive(archiveURL) 72 | } 73 | else 74 | { 75 | let diskURL = try container.decode(URL.self, forKey: .disk) 76 | let signatureURL = try container.decode(URL.self, forKey: .signature) 77 | self = .separate(diskURL: diskURL, signatureURL: signatureURL) 78 | } 79 | } 80 | } 81 | } 82 | 83 | class DeveloperDiskManager 84 | { 85 | private let session = URLSession(configuration: .ephemeral) 86 | 87 | func downloadDeveloperDisk(for device: ALTDevice, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void) 88 | { 89 | do 90 | { 91 | guard let osName = device.type.osName else { throw DeveloperDiskError.unsupportedOperatingSystem } 92 | 93 | let osKeyPath: KeyPath 94 | switch device.type 95 | { 96 | case .iphone, .ipad: osKeyPath = \FetchURLsResponse.Disks.iOS 97 | case .appletv: osKeyPath = \FetchURLsResponse.Disks.tvOS 98 | default: throw DeveloperDiskError.unsupportedOperatingSystem 99 | } 100 | 101 | var osVersion = device.osVersion 102 | osVersion.patchVersion = 0 // Patch is irrelevant for developer disks 103 | 104 | let osDirectoryURL = FileManager.default.developerDisksDirectory.appendingPathComponent(osName) 105 | let developerDiskDirectoryURL = osDirectoryURL.appendingPathComponent(osVersion.stringValue, isDirectory: true) 106 | try FileManager.default.createDirectory(at: developerDiskDirectoryURL, withIntermediateDirectories: true, attributes: nil) 107 | 108 | let developerDiskURL = developerDiskDirectoryURL.appendingPathComponent("DeveloperDiskImage.dmg") 109 | let developerDiskSignatureURL = developerDiskDirectoryURL.appendingPathComponent("DeveloperDiskImage.dmg.signature") 110 | 111 | let isCachedDiskCompatible = self.isDeveloperDiskCompatible(with: device) 112 | if isCachedDiskCompatible && FileManager.default.fileExists(atPath: developerDiskURL.path) && FileManager.default.fileExists(atPath: developerDiskSignatureURL.path) 113 | { 114 | // The developer disk is cached and we've confirmed it works, so re-use it. 115 | return completionHandler(.success((developerDiskURL, developerDiskSignatureURL))) 116 | } 117 | 118 | func finish(_ result: Result<(URL, URL), Error>) 119 | { 120 | do 121 | { 122 | let (diskFileURL, signatureFileURL) = try result.get() 123 | 124 | if FileManager.default.fileExists(atPath: developerDiskURL.path) 125 | { 126 | try FileManager.default.removeItem(at: developerDiskURL) 127 | } 128 | 129 | if FileManager.default.fileExists(atPath: developerDiskSignatureURL.path) 130 | { 131 | try FileManager.default.removeItem(at: developerDiskSignatureURL) 132 | } 133 | 134 | try FileManager.default.copyItem(at: diskFileURL, to: developerDiskURL) 135 | try FileManager.default.copyItem(at: signatureFileURL, to: developerDiskSignatureURL) 136 | 137 | completionHandler(.success((developerDiskURL, developerDiskSignatureURL))) 138 | } 139 | catch 140 | { 141 | completionHandler(.failure(error)) 142 | } 143 | } 144 | 145 | self.fetchDeveloperDiskURLs { (result) in 146 | do 147 | { 148 | let developerDiskURLs = try result.get() 149 | guard let diskURL = developerDiskURLs[keyPath: osKeyPath]?[osVersion.stringValue] else { throw DeveloperDiskError.unknownDownloadURL } 150 | 151 | switch diskURL 152 | { 153 | case .archive(let archiveURL): self.downloadDiskArchive(from: archiveURL, completionHandler: finish(_:)) 154 | case .separate(let diskURL, let signatureURL): self.downloadDisk(from: diskURL, signatureURL: signatureURL, completionHandler: finish(_:)) 155 | } 156 | } 157 | catch 158 | { 159 | finish(.failure(error)) 160 | } 161 | } 162 | } 163 | catch 164 | { 165 | completionHandler(.failure(error)) 166 | } 167 | } 168 | 169 | func setDeveloperDiskCompatible(_ isCompatible: Bool, with device: ALTDevice) 170 | { 171 | guard let id = self.developerDiskCompatibilityID(for: device) else { return } 172 | UserDefaults.standard.set(isCompatible, forKey: id) 173 | } 174 | 175 | func isDeveloperDiskCompatible(with device: ALTDevice) -> Bool 176 | { 177 | guard let id = self.developerDiskCompatibilityID(for: device) else { return false } 178 | 179 | let isCompatible = UserDefaults.standard.bool(forKey: id) 180 | return isCompatible 181 | } 182 | } 183 | 184 | private extension DeveloperDiskManager 185 | { 186 | func developerDiskCompatibilityID(for device: ALTDevice) -> String? 187 | { 188 | guard let osName = device.type.osName else { return nil } 189 | 190 | var osVersion = device.osVersion 191 | osVersion.patchVersion = 0 // Patch is irrelevant for developer disks 192 | 193 | let id = ["ALTDeveloperDiskCompatible", osName, device.osVersion.stringValue].joined(separator: "_") 194 | return id 195 | } 196 | 197 | func fetchDeveloperDiskURLs(completionHandler: @escaping (Result) -> Void) 198 | { 199 | let dataTask = self.session.dataTask(with: .developerDiskDownloadURLs) { (data, response, error) in 200 | do 201 | { 202 | guard let data = data else { throw error! } 203 | 204 | let response = try JSONDecoder().decode(FetchURLsResponse.self, from: data) 205 | completionHandler(.success(response.disks)) 206 | } 207 | catch 208 | { 209 | completionHandler(.failure(error)) 210 | } 211 | } 212 | 213 | dataTask.resume() 214 | } 215 | 216 | func downloadDiskArchive(from url: URL, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void) 217 | { 218 | let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in 219 | do 220 | { 221 | guard let fileURL = fileURL else { throw error! } 222 | defer { try? FileManager.default.removeItem(at: fileURL) } 223 | 224 | let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) 225 | try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil) 226 | defer { try? FileManager.default.removeItem(at: temporaryDirectory) } 227 | 228 | try FileManager.default.unzipArchive(at: fileURL, toDirectory: temporaryDirectory) 229 | 230 | guard let enumerator = FileManager.default.enumerator(at: temporaryDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) else { 231 | throw CocoaError(.fileNoSuchFile, userInfo: [NSURLErrorKey: temporaryDirectory]) 232 | } 233 | 234 | var tempDiskFileURL: URL? 235 | var tempSignatureFileURL: URL? 236 | 237 | for case let fileURL as URL in enumerator 238 | { 239 | switch fileURL.pathExtension.lowercased() 240 | { 241 | case "dmg": tempDiskFileURL = fileURL 242 | case "signature": tempSignatureFileURL = fileURL 243 | default: break 244 | } 245 | } 246 | 247 | guard let diskFileURL = tempDiskFileURL, let signatureFileURL = tempSignatureFileURL else { throw DeveloperDiskError.downloadedDiskNotFound } 248 | 249 | completionHandler(.success((diskFileURL, signatureFileURL))) 250 | } 251 | catch 252 | { 253 | completionHandler(.failure(error)) 254 | } 255 | } 256 | 257 | downloadTask.resume() 258 | } 259 | 260 | func downloadDisk(from diskURL: URL, signatureURL: URL, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void) 261 | { 262 | let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) 263 | 264 | do { try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil) } 265 | catch { return completionHandler(.failure(error)) } 266 | 267 | var diskFileURL: URL? 268 | var signatureFileURL: URL? 269 | 270 | var downloadError: Error? 271 | 272 | let dispatchGroup = DispatchGroup() 273 | dispatchGroup.enter() 274 | dispatchGroup.enter() 275 | 276 | let diskDownloadTask = URLSession.shared.downloadTask(with: diskURL) { (fileURL, response, error) in 277 | do 278 | { 279 | guard let fileURL = fileURL else { throw error! } 280 | 281 | let destinationURL = temporaryDirectory.appendingPathComponent("DeveloperDiskImage.dmg") 282 | try FileManager.default.copyItem(at: fileURL, to: destinationURL) 283 | 284 | diskFileURL = destinationURL 285 | } 286 | catch 287 | { 288 | downloadError = error 289 | } 290 | 291 | dispatchGroup.leave() 292 | } 293 | 294 | let signatureDownloadTask = URLSession.shared.downloadTask(with: signatureURL) { (fileURL, response, error) in 295 | do 296 | { 297 | guard let fileURL = fileURL else { throw error! } 298 | 299 | let destinationURL = temporaryDirectory.appendingPathComponent("DeveloperDiskImage.dmg.signature") 300 | try FileManager.default.copyItem(at: fileURL, to: destinationURL) 301 | 302 | signatureFileURL = destinationURL 303 | } 304 | catch 305 | { 306 | downloadError = error 307 | } 308 | 309 | dispatchGroup.leave() 310 | } 311 | 312 | diskDownloadTask.resume() 313 | signatureDownloadTask.resume() 314 | 315 | dispatchGroup.notify(queue: .global(qos: .userInitiated)) { 316 | defer { 317 | try? FileManager.default.removeItem(at: temporaryDirectory) 318 | } 319 | 320 | guard let diskFileURL = diskFileURL, let signatureFileURL = signatureFileURL else { 321 | return completionHandler(.failure(downloadError ?? DeveloperDiskError.downloadedDiskNotFound)) 322 | } 323 | 324 | completionHandler(.success((diskFileURL, signatureFileURL))) 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /AltServer/Devices/ALTDeviceManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // ALTDeviceManager.h 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 5/24/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AltSign.h" 11 | 12 | @class ALTWiredConnection; 13 | @class ALTNotificationConnection; 14 | @class ALTDebugConnection; 15 | 16 | @class ALTInstalledApp; 17 | 18 | NS_ASSUME_NONNULL_BEGIN 19 | 20 | extern NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidConnect); 21 | extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidDisconnect); 22 | 23 | @interface ALTDeviceManager : NSObject 24 | 25 | @property (class, nonatomic, readonly) ALTDeviceManager *sharedManager; 26 | 27 | @property (nonatomic, readonly) NSArray *connectedDevices; 28 | @property (nonatomic, readonly) NSArray *availableDevices; 29 | 30 | - (void)start; 31 | 32 | /* App Installation */ 33 | - (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 34 | - (void)removeAppForBundleIdentifier:(NSString *)bundleIdentifier fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 35 | 36 | /* Provisioning Profiles */ 37 | - (void)installProvisioningProfiles:(NSSet *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 38 | - (void)removeProvisioningProfilesForBundleIdentifiers:(NSSet *)bundleIdentifiers fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 39 | 40 | /* Developer Disk Image */ 41 | - (void)isDeveloperDiskImageMountedForDevice:(ALTDevice *)device 42 | completionHandler:(void (^)(BOOL isMounted, NSError *_Nullable error))completionHandler; 43 | - (void)installDeveloperDiskImageAtURL:(NSURL *)diskURL signatureURL:(NSURL *)signatureURL toDevice:(ALTDevice *)device 44 | completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; 45 | 46 | /* Apps */ 47 | - (void)fetchInstalledAppsOnDevice:(ALTDevice *)altDevice completionHandler:(void (^)(NSSet *_Nullable installedApps, NSError *_Nullable error))completionHandler; 48 | 49 | /* Connections */ 50 | - (void)startWiredConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTWiredConnection *_Nullable connection, NSError *_Nullable error))completionHandler; 51 | - (void)startNotificationConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTNotificationConnection *_Nullable connection, NSError *_Nullable error))completionHandler; 52 | - (void)startDebugConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTDebugConnection *_Nullable connection, NSError * _Nullable error))completionHandler; 53 | - (NSString *)getPairingPlistString:(NSString *)udid; 54 | 55 | @end 56 | 57 | NS_ASSUME_NONNULL_END 58 | -------------------------------------------------------------------------------- /AltServer/Extensions/FileManager+URLs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+URLs.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 2/23/21. 6 | // Copyright © 2021 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension FileManager 12 | { 13 | var altserverDirectory: URL { 14 | let applicationSupportDirectoryURL = self.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] 15 | 16 | let altserverDirectoryURL = applicationSupportDirectoryURL.appendingPathComponent("com.rileytestut.AltServer") 17 | return altserverDirectoryURL 18 | } 19 | 20 | var certificatesDirectory: URL { 21 | let certificatesDirectoryURL = self.altserverDirectory.appendingPathComponent("Certificates") 22 | return certificatesDirectoryURL 23 | } 24 | 25 | var developerDisksDirectory: URL { 26 | let developerDisksDirectoryURL = self.altserverDirectory.appendingPathComponent("DeveloperDiskImages") 27 | return developerDisksDirectoryURL 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AltServer/Extensions/UserDefaults+AltServer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults+AltServer.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 7/31/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension UserDefaults 12 | { 13 | var serverID: String? { 14 | get { 15 | return self.string(forKey: "serverID") 16 | } 17 | set { 18 | self.set(newValue, forKey: "serverID") 19 | } 20 | } 21 | 22 | var didPresentInitialNotification: Bool { 23 | get { 24 | return self.bool(forKey: "didPresentInitialNotification") 25 | } 26 | set { 27 | self.set(newValue, forKey: "didPresentInitialNotification") 28 | } 29 | } 30 | 31 | func registerDefaults() 32 | { 33 | if self.serverID == nil 34 | { 35 | self.serverID = UUID().uuidString 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AltServer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AppIdentifierPrefix 6 | $(AppIdentifierPrefix) 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(MARKETING_VERSION) 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | LSUIElement 28 | 29 | NSHumanReadableCopyright 30 | 2023 SideStore Team 31 | NSMainStoryboardFile 32 | Main 33 | NSPrincipalClass 34 | NSApplication 35 | SUFeedURL 36 | https://github.com/SideStore/SideServer-macOS/raw/develop/sparkle-macos.xml 37 | SUFeedURL-Staging 38 | https://github.com/SideStore/SideServer-macOS/raw/develop/sparkle-macos.xml 39 | SUPublicEDKey 40 | DEf5AVVLBx5biDINRZL5aArGLi9vh37lOzWPTvVuXW0= 41 | 42 | 43 | -------------------------------------------------------------------------------- /AltServer/InstalledApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InstalledApp.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 5/25/21. 6 | // Copyright © 2021 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc(ALTInstalledApp) @objcMembers 12 | class InstalledApp: NSObject, MenuDisplayable 13 | { 14 | let name: String 15 | let bundleIdentifier: String 16 | let executableName: String 17 | 18 | init?(dictionary: [String: Any]) 19 | { 20 | guard let name = dictionary[kCFBundleNameKey as String] as? String, 21 | let bundleIdentifier = dictionary[kCFBundleIdentifierKey as String] as? String, 22 | let executableName = dictionary[kCFBundleExecutableKey as String] as? String 23 | else { return nil } 24 | 25 | self.name = name 26 | self.bundleIdentifier = bundleIdentifier 27 | self.executableName = executableName 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AltServer/MenuController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuController.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 3/3/21. 6 | // Copyright © 2021 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | protocol MenuDisplayable 13 | { 14 | var name: String { get } 15 | } 16 | 17 | class MenuController: NSObject, NSMenuDelegate 18 | { 19 | let menu: NSMenu 20 | 21 | var items: [T] { 22 | didSet { 23 | self.submenus.removeAll() 24 | self.updateMenu() 25 | } 26 | } 27 | 28 | var placeholder: String? { 29 | didSet { 30 | self.updateMenu() 31 | } 32 | } 33 | 34 | var action: ((T) -> Void)? 35 | 36 | var submenuHandler: ((T) -> NSMenu)? 37 | private var submenus = [T: NSMenu]() 38 | 39 | init(menu: NSMenu, items: [T]) 40 | { 41 | self.menu = menu 42 | self.items = items 43 | 44 | super.init() 45 | 46 | self.menu.delegate = self 47 | } 48 | 49 | @objc 50 | private func performAction(_ menuItem: NSMenuItem) 51 | { 52 | guard case let index = self.menu.index(of: menuItem), index != -1 else { return } 53 | 54 | let item = self.items[index] 55 | self.action?(item) 56 | } 57 | 58 | @objc 59 | func numberOfItems(in menu: NSMenu) -> Int 60 | { 61 | let numberOfItems = (self.items.isEmpty && self.placeholder != nil) ? 1 : self.items.count 62 | return numberOfItems 63 | } 64 | 65 | @objc 66 | func menu(_ menu: NSMenu, update menuItem: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool 67 | { 68 | if let text = self.placeholder, self.items.isEmpty 69 | { 70 | menuItem.title = text 71 | menuItem.isEnabled = false 72 | menuItem.target = nil 73 | menuItem.action = nil 74 | } 75 | else 76 | { 77 | let item = self.items[index] 78 | 79 | menuItem.title = item.name 80 | menuItem.isEnabled = true 81 | menuItem.target = self 82 | menuItem.action = #selector(MenuController.performAction(_:)) 83 | menuItem.tag = index 84 | 85 | if let submenu = self.submenus[item] ?? self.submenuHandler?(item) 86 | { 87 | menuItem.submenu = submenu 88 | 89 | // Cache submenu to prevent duplicate calls to submenuHandler. 90 | self.submenus[item] = submenu 91 | } 92 | } 93 | 94 | return true 95 | } 96 | } 97 | 98 | private extension MenuController 99 | { 100 | func updateMenu() 101 | { 102 | self.menu.removeAllItems() 103 | 104 | let numberOfItems = self.numberOfItems(in: self.menu) 105 | for index in 0 ..< numberOfItems 106 | { 107 | let menuItem = NSMenuItem(title: "", action: nil, keyEquivalent: "") 108 | guard self.menu(self.menu, update: menuItem, at: index, shouldCancel: false) else { break } 109 | 110 | self.menu.addItem(menuItem) 111 | } 112 | 113 | self.menu.update() 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /AltServer/Views/AppleIDAuthenticationAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleIDAuthenticationAlert.swift 3 | // AltServer 4 | // 5 | // Created by royal on 17/12/2022. 6 | // 7 | 8 | import AppKit 9 | 10 | // MARK: - AppleIDAuthenticationAlert 11 | 12 | final class AppleIDAuthenticationAlert: NSAlert { 13 | 14 | private let appleIDTextField: NSTextField 15 | private let passwordTextField: NSSecureTextField 16 | 17 | var appleIDValue: String { 18 | get { appleIDTextField.stringValue } 19 | } 20 | 21 | var passwordValue: String { 22 | get { passwordTextField.stringValue } 23 | } 24 | 25 | override init() { 26 | let textFieldSize = NSSize(width: 300, height: 22) 27 | 28 | let stackView = NSStackView(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height * 2)) 29 | stackView.orientation = .vertical 30 | stackView.distribution = .equalSpacing 31 | stackView.spacing = 0 32 | 33 | appleIDTextField = NSTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height)) 34 | appleIDTextField.translatesAutoresizingMaskIntoConstraints = false 35 | appleIDTextField.placeholderString = NSLocalizedString("Apple ID", comment: "") 36 | stackView.addArrangedSubview(appleIDTextField) 37 | 38 | passwordTextField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height)) 39 | passwordTextField.translatesAutoresizingMaskIntoConstraints = false 40 | passwordTextField.placeholderString = NSLocalizedString("Password", comment: "") 41 | stackView.addArrangedSubview(passwordTextField) 42 | 43 | appleIDTextField.nextKeyView = passwordTextField 44 | 45 | super.init() 46 | 47 | appleIDTextField.delegate = self 48 | passwordTextField.delegate = self 49 | 50 | messageText = NSLocalizedString("Please enter your Apple ID and password.", comment: "") 51 | informativeText = NSLocalizedString("Your Apple ID and password will be saved in the Keychain and will be sent only to Apple servers.", comment: "") 52 | accessoryView = stackView 53 | 54 | window.initialFirstResponder = appleIDTextField 55 | 56 | addButton(withTitle: NSLocalizedString("Continue", comment: "")) 57 | addButton(withTitle: NSLocalizedString("Cancel", comment: "")) 58 | 59 | validateTextFields() 60 | } 61 | 62 | /// Displays the alert modally, and returns a `Bool` saying whether the user did press "Continue". 63 | func display() -> Bool { 64 | let result = runModal() 65 | NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) 66 | return result == .alertFirstButtonReturn 67 | } 68 | } 69 | 70 | // MARK: - AppleIDAuthenticationAlert+NSTextFieldDelegate 71 | 72 | extension AppleIDAuthenticationAlert: NSTextFieldDelegate { 73 | func controlTextDidChange(_ obj: Notification) { 74 | validateTextFields() 75 | } 76 | } 77 | 78 | // MARK: - AppleIDAuthenticationAlert+Private 79 | 80 | private extension AppleIDAuthenticationAlert { 81 | func validateTextFields() { 82 | let isAppleIDTextFieldValid = !appleIDValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty 83 | let isPasswordTextFieldValid = !passwordValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty 84 | buttons.first?.isEnabled = isAppleIDTextFieldValid && isPasswordTextFieldValid 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /AltStore.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AltStore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AppCenter", 6 | "repositoryURL": "https://github.com/microsoft/appcenter-sdk-apple.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "8354a50fe01a7e54e196d3b5493b5ab53dd5866a", 10 | "version": "4.4.2" 11 | } 12 | }, 13 | { 14 | "package": "KeychainAccess", 15 | "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "84e546727d66f1adc5439debad16270d0fdd04e7", 19 | "version": "4.2.2" 20 | } 21 | }, 22 | { 23 | "package": "LaunchAtLogin", 24 | "repositoryURL": "https://github.com/sindresorhus/LaunchAtLogin.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "e8171b3e38a2816f579f58f3dac1522aa39efe41", 28 | "version": "4.2.0" 29 | } 30 | }, 31 | { 32 | "package": "Nuke", 33 | "repositoryURL": "https://github.com/kean/Nuke.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "9318d02a8a6d20af56505c9673261c1fd3b3aebe", 37 | "version": "7.6.3" 38 | } 39 | }, 40 | { 41 | "package": "PLCrashReporter", 42 | "repositoryURL": "https://github.com/microsoft/PLCrashReporter.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "6b27393cad517c067dceea85fadf050e70c4ceaa", 46 | "version": "1.10.1" 47 | } 48 | }, 49 | { 50 | "package": "Sparkle", 51 | "repositoryURL": "https://github.com/sparkle-project/Sparkle.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "286edd1fa22505a9e54d170e9fd07d775ea233f2", 55 | "version": "2.1.0" 56 | } 57 | }, 58 | { 59 | "package": "STPrivilegedTask", 60 | "repositoryURL": "https://github.com/JoeMatt/STPrivilegedTask.git", 61 | "state": { 62 | "branch": "master", 63 | "revision": "10a9150ef32d444af326beba76356ae9af95a3e7", 64 | "version": null 65 | } 66 | } 67 | ] 68 | }, 69 | "version": 1 70 | } 71 | -------------------------------------------------------------------------------- /AltStore.xcodeproj/xcshareddata/xcschemes/AltServer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /AltXPC.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Build.xcconfig" 2 | -------------------------------------------------------------------------------- /Build.xcconfig: -------------------------------------------------------------------------------- 1 | // Configuration settings file format documentation can be found at: 2 | // https://help.apple.com/xcode/#/dev745c5c974 3 | 4 | MARKETING_VERSION = 1.0.4 5 | CURRENT_PROJECT_VERSION = 104 6 | 7 | // Vars to be overwritten by `CodeSigning.xcconfig` if exists 8 | DEVELOPMENT_TEAM = S32Z3HMYVQ 9 | ORG_IDENTIFIER = com.joemattiello 10 | 11 | // Codesigning settings defined optionally, see `CodeSigning.xcconfig.example` 12 | #include? "CodeSigning.xcconfig" 13 | 14 | ORG_PREFIX = $(ORG_IDENTIFIER) 15 | 16 | PRODUCT_NAME = AltStore 17 | //PRODUCT_NAME[configuration=Debug] = Prov Debug 18 | 19 | PRODUCT_BUNDLE_IDENTIFIER = $(ORG_PREFIX).$(PROJECT_NAME) 20 | //PRODUCT_BUNDLE_IDENTIFIER[configuration=Debug] = $(ORG_PREFIX).$(PROJECT_NAME:lower)-debug 21 | 22 | APP_GROUP_IDENTIFIER = $(ORG_PREFIX).$(PROJECT_NAME) 23 | ICLOUD_CONTAINER_IDENTIFIER = iCloud.$(ORG_PREFIX).$(PROJECT_NAME) 24 | -------------------------------------------------------------------------------- /CodeSigning.xcconfig.sample: -------------------------------------------------------------------------------- 1 | // Your Team ID. 2 | // If you have a paid Apple Developer account, you can find your Team ID at 3 | // https://developer.apple.com/account/#/membership 4 | DEVELOPMENT_TEAM = XYZ0123456 5 | 6 | // Prefix of unique bundle IDs registered to you in Apple Developer Portal. 7 | // You need to register: 8 | // - com.myuniquename.provenance 9 | // - com.myuniquename.provenance.spotlight 10 | // - com.myuniquename.provenance.topshelf 11 | ORG_IDENTIFIER = com.myuniquename 12 | 13 | // Set to YES if you have a valid paid Apple Developer account 14 | DEVELOPER_ACCOUNT_PAID = NO 15 | 16 | // Name of the iOS development signing certificate, you probably do not need 17 | // to change this. 18 | CODE_SIGN_IDENTITY_IOS = Apple Development 19 | 20 | // Name of the iOS development signing certificate, you probably do not need 21 | // to change this. 22 | CODE_SIGN_IDENTITY_TVOS = Apple Development 23 | 24 | // The values below are specific to macOS development. If you do not define 25 | // these keys, the build will default to ad-hoc signing. You will need to 26 | // follow `Documentation/MacDevelopment.md` to disable library verification and 27 | // remove unsupported entitlements. 28 | 29 | // Name of the macOS development signing certificate. Comment out this line to 30 | // use ad-hoc signing. 31 | CODE_SIGN_IDENTITY_MAC = Apple Development 32 | 33 | // Create a Mac provisioning profile for com.myuniquename.UTM with the 34 | // Hypervisor entitlements and get its UUID. If you do not have access to these 35 | // entitlements, comment out the line and delete the following entitlements 36 | // - com.apple.vm.device-access 37 | // from the following file 38 | // - Provenance/macOS.entitlements 39 | PROVISIONING_PROFILE_SPECIFIER_MAC = 00000000-1111-2222-3333-444444444444 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SideServer-macOS 2 | 3 | _Glorified SideStore Installer in server-form to use as an optional backup anisette server, and optional auto-refresher of [SideStore](https://github.com/sidestore/sidestore)_ 4 | 5 | [![build_app](https://github.com/SideStore/SideServer-macOS/actions/workflows/build_app.yml/badge.svg)](https://github.com/SideStore/SideServer-macOS/actions/workflows/build_app.yml) 6 | 7 | ![Alt](https://repobeats.axiom.co/api/embed/eff2778e5a6cc801e308ac5a6ca0e675c4ce58a5.svg "Repobeats analytics image") 8 | -------------------------------------------------------------------------------- /Shared/ALTConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // ALTConstants.h 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 5/30/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern uint16_t ALTDeviceListeningSocket; 12 | -------------------------------------------------------------------------------- /Shared/ALTConstants.m: -------------------------------------------------------------------------------- 1 | // 2 | // ALTConstants.m 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 1/10/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | uint16_t ALTDeviceListeningSocket = 28151; 12 | -------------------------------------------------------------------------------- /Shared/Categories/CFNotificationName+AltStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // CFNotificationName+AltStore.h 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 1/10/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | extern CFNotificationName const ALTWiredServerConnectionAvailableRequest NS_SWIFT_NAME(wiredServerConnectionAvailableRequest); 14 | extern CFNotificationName const ALTWiredServerConnectionAvailableResponse NS_SWIFT_NAME(wiredServerConnectionAvailableResponse); 15 | extern CFNotificationName const ALTWiredServerConnectionStartRequest NS_SWIFT_NAME(wiredServerConnectionStartRequest); 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Shared/Categories/CFNotificationName+AltStore.m: -------------------------------------------------------------------------------- 1 | // 2 | // CFNotificationName+AltStore.m 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 1/10/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "CFNotificationName+AltStore.h" 10 | 11 | CFNotificationName const ALTWiredServerConnectionAvailableRequest = CFSTR("io.altstore.Request.WiredServerConnectionAvailable"); 12 | CFNotificationName const ALTWiredServerConnectionAvailableResponse = CFSTR("io.altstore.Response.WiredServerConnectionAvailable"); 13 | CFNotificationName const ALTWiredServerConnectionStartRequest = CFSTR("io.altstore.Request.WiredServerConnectionStart"); 14 | -------------------------------------------------------------------------------- /Shared/Categories/NSError+ALTServerError.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+ALTServerError.h 3 | // AltStore 4 | // 5 | // Created by Riley Testut on 5/30/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NSErrorDomain const AltServerErrorDomain; 12 | extern NSErrorDomain const AltServerInstallationErrorDomain; 13 | extern NSErrorDomain const AltServerConnectionErrorDomain; 14 | 15 | extern NSErrorUserInfoKey const ALTUnderlyingErrorDomainErrorKey; 16 | extern NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey; 17 | extern NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey; 18 | extern NSErrorUserInfoKey const ALTAppNameErrorKey; 19 | extern NSErrorUserInfoKey const ALTDeviceNameErrorKey; 20 | extern NSErrorUserInfoKey const ALTOperatingSystemNameErrorKey; 21 | extern NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey; 22 | 23 | typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError) 24 | { 25 | ALTServerErrorUnderlyingError = -1, 26 | 27 | ALTServerErrorUnknown = 0, 28 | ALTServerErrorConnectionFailed = 1, 29 | ALTServerErrorLostConnection = 2, 30 | 31 | ALTServerErrorDeviceNotFound = 3, 32 | ALTServerErrorDeviceWriteFailed = 4, 33 | 34 | ALTServerErrorInvalidRequest = 5, 35 | ALTServerErrorInvalidResponse = 6, 36 | 37 | ALTServerErrorInvalidApp = 7, 38 | ALTServerErrorInstallationFailed = 8, 39 | ALTServerErrorMaximumFreeAppLimitReached = 9, 40 | ALTServerErrorUnsupportediOSVersion = 10, 41 | 42 | ALTServerErrorUnknownRequest = 11, 43 | ALTServerErrorUnknownResponse = 12, 44 | 45 | ALTServerErrorInvalidAnisetteData = 13, 46 | 47 | ALTServerErrorProfileNotFound = 15, 48 | 49 | ALTServerErrorAppDeletionFailed = 16, 50 | 51 | ALTServerErrorRequestedAppNotRunning = 100, 52 | ALTServerErrorIncompatibleDeveloperDisk = 101 53 | }; 54 | 55 | typedef NS_ERROR_ENUM(AltServerConnectionErrorDomain, ALTServerConnectionError) 56 | { 57 | ALTServerConnectionErrorUnknown, 58 | ALTServerConnectionErrorDeviceLocked, 59 | ALTServerConnectionErrorInvalidRequest, 60 | ALTServerConnectionErrorInvalidResponse, 61 | ALTServerConnectionErrorUsbmuxd, 62 | ALTServerConnectionErrorSSL, 63 | ALTServerConnectionErrorTimedOut, 64 | }; 65 | 66 | NS_ASSUME_NONNULL_BEGIN 67 | 68 | @interface NSError (ALTServerError) 69 | @end 70 | 71 | NS_ASSUME_NONNULL_END 72 | -------------------------------------------------------------------------------- /Shared/Categories/NSError+ALTServerError.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+ALTServerError.m 3 | // AltStore 4 | // 5 | // Created by Riley Testut on 5/30/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "NSError+ALTServerError.h" 10 | 11 | NSErrorDomain const AltServerErrorDomain = @"com.rileytestut.AltServer"; 12 | NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServer.Installation"; 13 | NSErrorDomain const AltServerConnectionErrorDomain = @"com.rileytestut.AltServer.Connection"; 14 | 15 | NSErrorUserInfoKey const ALTUnderlyingErrorDomainErrorKey = @"underlyingErrorDomain"; 16 | NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey = @"underlyingErrorCode"; 17 | NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey = @"bundleIdentifier"; 18 | NSErrorUserInfoKey const ALTAppNameErrorKey = @"appName"; 19 | NSErrorUserInfoKey const ALTDeviceNameErrorKey = @"deviceName"; 20 | NSErrorUserInfoKey const ALTOperatingSystemNameErrorKey = @"ALTOperatingSystemName"; 21 | NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSystemVersion"; 22 | 23 | @implementation NSError (ALTServerError) 24 | 25 | + (void)load 26 | { 27 | [NSError setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) { 28 | if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey]) 29 | { 30 | return [error altserver_localizedFailureReason]; 31 | } 32 | else if ([userInfoKey isEqualToString:NSLocalizedRecoverySuggestionErrorKey]) 33 | { 34 | return [error altserver_localizedRecoverySuggestion]; 35 | } 36 | else if ([userInfoKey isEqualToString:NSDebugDescriptionErrorKey]) 37 | { 38 | return [error altserver_localizedDebugDescription]; 39 | } 40 | 41 | return nil; 42 | }]; 43 | 44 | [NSError setUserInfoValueProviderForDomain:AltServerConnectionErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) { 45 | if ([userInfoKey isEqualToString:NSLocalizedDescriptionKey]) 46 | { 47 | return [error altserver_connection_localizedDescription]; 48 | } 49 | else if ([userInfoKey isEqualToString:NSLocalizedRecoverySuggestionErrorKey]) 50 | { 51 | return [error altserver_connection_localizedRecoverySuggestion]; 52 | } 53 | 54 | return nil; 55 | }]; 56 | } 57 | 58 | - (nullable NSString *)altserver_localizedFailureReason 59 | { 60 | switch ((ALTServerError)self.code) 61 | { 62 | case ALTServerErrorUnderlyingError: 63 | { 64 | NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey]; 65 | if (underlyingError.localizedFailureReason != nil) 66 | { 67 | return underlyingError.localizedFailureReason; 68 | } 69 | 70 | NSString *underlyingErrorCode = self.userInfo[ALTUnderlyingErrorCodeErrorKey]; 71 | if (underlyingErrorCode != nil) 72 | { 73 | return [NSString stringWithFormat:NSLocalizedString(@"Error code: %@", @""), underlyingErrorCode]; 74 | } 75 | 76 | return nil; 77 | } 78 | 79 | case ALTServerErrorUnknown: 80 | return NSLocalizedString(@"An unknown error occured.", @""); 81 | 82 | case ALTServerErrorConnectionFailed: 83 | #if TARGET_OS_OSX 84 | return NSLocalizedString(@"There was an error connecting to the device.", @""); 85 | #else 86 | return NSLocalizedString(@"Could not connect to AltServer.", @""); 87 | #endif 88 | 89 | case ALTServerErrorLostConnection: 90 | return NSLocalizedString(@"Lost connection to AltServer.", @""); 91 | 92 | case ALTServerErrorDeviceNotFound: 93 | return NSLocalizedString(@"AltServer could not find this device.", @""); 94 | 95 | case ALTServerErrorDeviceWriteFailed: 96 | return NSLocalizedString(@"Failed to write app data to device.", @""); 97 | 98 | case ALTServerErrorInvalidRequest: 99 | return NSLocalizedString(@"AltServer received an invalid request.", @""); 100 | 101 | case ALTServerErrorInvalidResponse: 102 | return NSLocalizedString(@"AltServer sent an invalid response.", @""); 103 | 104 | case ALTServerErrorInvalidApp: 105 | return NSLocalizedString(@"The app is invalid.", @""); 106 | 107 | case ALTServerErrorInstallationFailed: 108 | return NSLocalizedString(@"An error occured while installing the app.", @""); 109 | 110 | case ALTServerErrorMaximumFreeAppLimitReached: 111 | return NSLocalizedString(@"Cannot activate more than 3 apps with a non-developer Apple ID.", @""); 112 | 113 | case ALTServerErrorUnsupportediOSVersion: 114 | return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install AltStore.", @""); 115 | 116 | case ALTServerErrorUnknownRequest: 117 | return NSLocalizedString(@"AltServer does not support this request.", @""); 118 | 119 | case ALTServerErrorUnknownResponse: 120 | return NSLocalizedString(@"Received an unknown response from AltServer.", @""); 121 | 122 | case ALTServerErrorInvalidAnisetteData: 123 | return NSLocalizedString(@"The provided anisette data is invalid.", @""); 124 | 125 | case ALTServerErrorProfileNotFound: 126 | return [self profileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not find profile", "")]; 127 | 128 | case ALTServerErrorAppDeletionFailed: 129 | return NSLocalizedString(@"An error occured while removing the app.", @""); 130 | 131 | case ALTServerErrorRequestedAppNotRunning: 132 | { 133 | NSString *appName = self.userInfo[ALTAppNameErrorKey] ?: NSLocalizedString(@"The requested app", @""); 134 | NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"the device", @""); 135 | return [NSString stringWithFormat:NSLocalizedString(@"%@ is not currently running on %@.", ""), appName, deviceName]; 136 | } 137 | 138 | case ALTServerErrorIncompatibleDeveloperDisk: 139 | { 140 | NSString *osVersion = [self altserver_osVersion] ?: NSLocalizedString(@"this device's OS version", @""); 141 | NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"The disk is incompatible with %@.", @""), osVersion]; 142 | return failureReason; 143 | } 144 | } 145 | 146 | return nil; 147 | } 148 | 149 | - (nullable NSString *)altserver_localizedRecoverySuggestion 150 | { 151 | switch ((ALTServerError)self.code) 152 | { 153 | case ALTServerErrorConnectionFailed: 154 | case ALTServerErrorDeviceNotFound: 155 | return NSLocalizedString(@"Make sure you have trusted this device with your computer and Wi-Fi sync is enabled.", @""); 156 | 157 | case ALTServerErrorMaximumFreeAppLimitReached: 158 | #if TARGET_OS_OSX 159 | return NSLocalizedString(@"Please deactivate a sideloaded app with AltStore in order to install another app.\n\nIf you're running iOS 13.5 or later, make sure 'Offload Unused Apps' is disabled in Settings > iTunes & App Stores, then install or delete all offloaded apps to prevent them from erroneously counting towards this limit.", @""); 160 | #else 161 | return NSLocalizedString(@"Please deactivate a sideloaded app in order to install another one.\n\nIf you're running iOS 13.5 or later, make sure “Offload Unused Apps” is disabled in Settings > iTunes & App Stores, then install or delete all offloaded apps.", @""); 162 | #endif 163 | 164 | case ALTServerErrorRequestedAppNotRunning: 165 | { 166 | NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"your device", @""); 167 | return [NSString stringWithFormat:NSLocalizedString(@"Make sure the app is running in the foreground on %@ then try again.", @""), deviceName]; 168 | } 169 | 170 | default: 171 | return nil; 172 | } 173 | } 174 | 175 | - (nullable NSString *)altserver_localizedDebugDescription 176 | { 177 | switch ((ALTServerError)self.code) 178 | { 179 | case ALTServerErrorIncompatibleDeveloperDisk: 180 | { 181 | NSString *path = self.userInfo[NSFilePathErrorKey]; 182 | if (path == nil) 183 | { 184 | return nil; 185 | } 186 | 187 | NSString *osVersion = [self altserver_osVersion] ?: NSLocalizedString(@"this device's OS version", @""); 188 | NSString *debugDescription = [NSString stringWithFormat:NSLocalizedString(@"The Developer disk located at\n\n%@\n\nis incompatible with %@.", @""), path, osVersion]; 189 | return debugDescription; 190 | } 191 | 192 | default: 193 | return nil; 194 | } 195 | } 196 | 197 | - (NSString *)profileErrorLocalizedDescriptionWithBaseDescription:(NSString *)baseDescription 198 | { 199 | NSString *localizedDescription = nil; 200 | 201 | NSString *bundleID = self.userInfo[ALTProvisioningProfileBundleIDErrorKey]; 202 | if (bundleID) 203 | { 204 | localizedDescription = [NSString stringWithFormat:@"%@ “%@”", baseDescription, bundleID]; 205 | } 206 | else 207 | { 208 | localizedDescription = [NSString stringWithFormat:@"%@.", baseDescription]; 209 | } 210 | 211 | return localizedDescription; 212 | } 213 | 214 | - (nullable NSString *)altserver_osVersion 215 | { 216 | NSString *osName = self.userInfo[ALTOperatingSystemNameErrorKey]; 217 | NSString *versionString = self.userInfo[ALTOperatingSystemVersionErrorKey]; 218 | if (osName == nil || versionString == nil) 219 | { 220 | return nil; 221 | } 222 | 223 | NSString *osVersion = [NSString stringWithFormat:@"%@ %@", osName, versionString]; 224 | return osVersion; 225 | } 226 | 227 | #pragma mark - AltServerConnectionErrorDomain - 228 | 229 | - (nullable NSString *)altserver_connection_localizedDescription 230 | { 231 | switch ((ALTServerConnectionError)self.code) 232 | { 233 | case ALTServerConnectionErrorUnknown: 234 | { 235 | NSString *underlyingErrorDomain = self.userInfo[ALTUnderlyingErrorDomainErrorKey]; 236 | NSString *underlyingErrorCode = self.userInfo[ALTUnderlyingErrorCodeErrorKey]; 237 | 238 | if (underlyingErrorDomain != nil && underlyingErrorCode != nil) 239 | { 240 | return [NSString stringWithFormat:NSLocalizedString(@"%@ error %@.", @""), underlyingErrorDomain, underlyingErrorCode]; 241 | } 242 | else if (underlyingErrorCode != nil) 243 | { 244 | return [NSString stringWithFormat:NSLocalizedString(@"Connection error code: %@", @""), underlyingErrorCode]; 245 | } 246 | 247 | return nil; 248 | } 249 | 250 | case ALTServerConnectionErrorDeviceLocked: 251 | { 252 | NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"The device", @""); 253 | return [NSString stringWithFormat:NSLocalizedString(@"%@ is currently locked.", @""), deviceName]; 254 | } 255 | 256 | case ALTServerConnectionErrorInvalidRequest: 257 | { 258 | NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"The device", @""); 259 | return [NSString stringWithFormat:NSLocalizedString(@"%@ received an invalid request from AltServer.", @""), deviceName]; 260 | } 261 | 262 | case ALTServerConnectionErrorInvalidResponse: 263 | { 264 | NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"the device", @""); 265 | return [NSString stringWithFormat:NSLocalizedString(@"AltServer received an invalid response from %@.", @""), deviceName]; 266 | } 267 | 268 | case ALTServerConnectionErrorUsbmuxd: 269 | { 270 | return NSLocalizedString(@"There was an issue communicating with the usbmuxd daemon.", @""); 271 | } 272 | 273 | case ALTServerConnectionErrorSSL: 274 | { 275 | NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"the device", @""); 276 | return [NSString stringWithFormat:NSLocalizedString(@"AltServer could not establish a secure connection to %@.", @""), deviceName]; 277 | } 278 | 279 | case ALTServerConnectionErrorTimedOut: 280 | { 281 | NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"the device", @""); 282 | return [NSString stringWithFormat:NSLocalizedString(@"AltServer's connection to %@ timed out.", @""), deviceName]; 283 | } 284 | } 285 | 286 | return nil; 287 | } 288 | 289 | - (nullable NSString *)altserver_connection_localizedRecoverySuggestion 290 | { 291 | switch ((ALTServerConnectionError)self.code) 292 | { 293 | case ALTServerConnectionErrorDeviceLocked: 294 | { 295 | return NSLocalizedString(@"Please unlock the device with your passcode and try again.", @""); 296 | } 297 | 298 | case ALTServerConnectionErrorUnknown: 299 | case ALTServerConnectionErrorInvalidRequest: 300 | case ALTServerConnectionErrorInvalidResponse: 301 | case ALTServerConnectionErrorUsbmuxd: 302 | case ALTServerConnectionErrorSSL: 303 | case ALTServerConnectionErrorTimedOut: 304 | { 305 | return nil; 306 | } 307 | } 308 | } 309 | 310 | @end 311 | -------------------------------------------------------------------------------- /Shared/Components/Keychain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keychain.swift 3 | // AltServer 4 | // 5 | // Created by royal on 17/12/2022. 6 | // 7 | 8 | import Foundation 9 | import KeychainAccess 10 | 11 | // MARK: - Keychain 12 | 13 | struct Keychain { 14 | static let shared = Keychain() 15 | 16 | private let keychain = KeychainAccess.Keychain(accessGroup: "\(Bundle.main.appIdentifierPrefix ?? "")group.\(Bundle.main.bundleIdentifier ?? "")").synchronizable(true) 17 | 18 | private init() {} 19 | 20 | public func setValue(_ value: String?, for key: Keychain.Key) throws { 21 | if let value { 22 | try keychain.set(value, key: key.rawValue) 23 | } else { 24 | try keychain.remove(key.rawValue) 25 | } 26 | } 27 | 28 | public func getValue(for key: Keychain.Key) throws -> String { 29 | let value = try keychain.getString(key.rawValue) 30 | guard let value else { 31 | throw KeychainError.noValueForKey 32 | } 33 | return value 34 | } 35 | } 36 | 37 | // MARK: - Keychain+Key 38 | 39 | extension Keychain { 40 | enum Key: String { 41 | case appleIDEmail = "AppleIDEmail" 42 | case appleIDPassword = "AppleIDPassword" 43 | } 44 | } 45 | 46 | // MARK: - Keychain+Errors 47 | 48 | extension Keychain { 49 | enum KeychainError: Error { 50 | case noValueForKey 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Shared/Connections/ALTConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // ALTConnection.h 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 6/1/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | NS_SWIFT_NAME(Connection) 14 | @protocol ALTConnection 15 | 16 | - (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler NS_REFINED_FOR_SWIFT; 17 | - (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler NS_SWIFT_NAME(__receiveData(expectedSize:completionHandler:)); 18 | 19 | - (void)disconnect; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /Shared/Connections/Connection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Connection.swift 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 6/1/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Network 11 | 12 | public extension Connection 13 | { 14 | func send(_ data: Data, completionHandler: @escaping (Result) -> Void) 15 | { 16 | self.__send(data) { (success, error) in 17 | let result = Result(success, error).mapError { (error) -> ALTServerError in 18 | guard let nwError = error as? NWError else { return ALTServerError(error) } 19 | return ALTServerError(.lostConnection, underlyingError: nwError) 20 | } 21 | 22 | completionHandler(result) 23 | } 24 | } 25 | 26 | func receiveData(expectedSize: Int, completionHandler: @escaping (Result) -> Void) 27 | { 28 | self.__receiveData(expectedSize: expectedSize) { (data, error) in 29 | let result = Result(data, error).mapError { (error) -> ALTServerError in 30 | guard let nwError = error as? NWError else { return ALTServerError(error) } 31 | return ALTServerError(.lostConnection, underlyingError: nwError) 32 | } 33 | 34 | completionHandler(result) 35 | } 36 | } 37 | 38 | func send(_ response: T, shouldDisconnect: Bool = false, completionHandler: @escaping (Result) -> Void) 39 | { 40 | func finish(_ result: Result) 41 | { 42 | completionHandler(result) 43 | 44 | if shouldDisconnect 45 | { 46 | // Add short delay to prevent us from dropping connection too quickly. 47 | DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) { 48 | self.disconnect() 49 | } 50 | } 51 | } 52 | 53 | do 54 | { 55 | let data = try JSONEncoder().encode(response) 56 | let responseSize = withUnsafeBytes(of: Int32(data.count)) { Data($0) } 57 | 58 | self.send(responseSize) { (result) in 59 | switch result 60 | { 61 | case .failure(let error): finish(.failure(error)) 62 | case .success: 63 | self.send(data) { (result) in 64 | switch result 65 | { 66 | case .failure(let error): finish(.failure(error)) 67 | case .success: finish(.success(())) 68 | } 69 | } 70 | } 71 | } 72 | } 73 | catch 74 | { 75 | finish(.failure(.init(.invalidResponse, underlyingError: error))) 76 | } 77 | } 78 | 79 | func receiveRequest(completionHandler: @escaping (Result) -> Void) 80 | { 81 | let size = MemoryLayout.size 82 | 83 | print("Receiving request size from connection:", self) 84 | self.receiveData(expectedSize: size) { (result) in 85 | do 86 | { 87 | let data = try result.get() 88 | 89 | let expectedSize = Int(data.withUnsafeBytes { $0.load(as: Int32.self) }) 90 | print("Receiving request from connection: \(self)... (\(expectedSize) bytes)") 91 | 92 | self.receiveData(expectedSize: expectedSize) { (result) in 93 | do 94 | { 95 | let data = try result.get() 96 | let request = try JSONDecoder().decode(ServerRequest.self, from: data) 97 | 98 | print("Received request:", request) 99 | completionHandler(.success(request)) 100 | } 101 | catch 102 | { 103 | completionHandler(.failure(ALTServerError(error))) 104 | } 105 | } 106 | } 107 | catch 108 | { 109 | completionHandler(.failure(ALTServerError(error))) 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Shared/Connections/ConnectionManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectionManager.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 5/23/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Network 11 | 12 | public protocol RequestHandler 13 | { 14 | func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) 15 | func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) 16 | 17 | func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection, 18 | completionHandler: @escaping (Result) -> Void) 19 | func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection, 20 | completionHandler: @escaping (Result) -> Void) 21 | 22 | func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) 23 | 24 | func handleEnableUnsignedCodeExecutionRequest(_ request: EnableUnsignedCodeExecutionRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) 25 | } 26 | 27 | public protocol ConnectionHandler: AnyObject 28 | { 29 | var connectionHandler: ((Connection) -> Void)? { get set } 30 | var disconnectionHandler: ((Connection) -> Void)? { get set } 31 | 32 | func startListening() 33 | func stopListening() 34 | } 35 | 36 | public class ConnectionManager 37 | { 38 | public let requestHandler: RequestHandlerType 39 | public let connectionHandlers: [ConnectionHandler] 40 | 41 | public var isStarted = false 42 | 43 | private var connections = [Connection]() 44 | 45 | public init(requestHandler: RequestHandlerType, connectionHandlers: [ConnectionHandler]) 46 | { 47 | self.requestHandler = requestHandler 48 | self.connectionHandlers = connectionHandlers 49 | 50 | for handler in connectionHandlers 51 | { 52 | handler.connectionHandler = { [weak self] (connection) in 53 | self?.prepare(connection) 54 | } 55 | 56 | handler.disconnectionHandler = { [weak self] (connection) in 57 | self?.disconnect(connection) 58 | } 59 | } 60 | } 61 | 62 | public func start() 63 | { 64 | guard !self.isStarted else { return } 65 | 66 | for connectionHandler in self.connectionHandlers 67 | { 68 | connectionHandler.startListening() 69 | } 70 | 71 | self.isStarted = true 72 | } 73 | 74 | public func stop() 75 | { 76 | guard self.isStarted else { return } 77 | 78 | for connectionHandler in self.connectionHandlers 79 | { 80 | connectionHandler.stopListening() 81 | } 82 | 83 | self.isStarted = false 84 | } 85 | } 86 | 87 | private extension ConnectionManager 88 | { 89 | func prepare(_ connection: Connection) 90 | { 91 | guard !self.connections.contains(where: { $0 === connection }) else { return } 92 | self.connections.append(connection) 93 | 94 | self.handleRequest(for: connection) 95 | } 96 | 97 | func disconnect(_ connection: Connection) 98 | { 99 | guard let index = self.connections.firstIndex(where: { $0 === connection }) else { return } 100 | self.connections.remove(at: index) 101 | } 102 | 103 | func handleRequest(for connection: Connection) 104 | { 105 | func finish(_ result: Result) 106 | { 107 | do 108 | { 109 | let response = try result.get() 110 | connection.send(response, shouldDisconnect: true) { (result) in 111 | print("Sent response \(response) with result:", result) 112 | } 113 | } 114 | catch 115 | { 116 | let response = ErrorResponse(error: ALTServerError(error)) 117 | connection.send(response, shouldDisconnect: true) { (result) in 118 | print("Sent error response \(response) with result:", result) 119 | } 120 | } 121 | } 122 | 123 | connection.receiveRequest() { (result) in 124 | print("Received request with result:", result) 125 | 126 | switch result 127 | { 128 | case .failure(let error): finish(Result.failure(error)) 129 | 130 | case .success(.anisetteData(let request)): 131 | self.requestHandler.handleAnisetteDataRequest(request, for: connection) { (result) in 132 | finish(result) 133 | } 134 | 135 | case .success(.prepareApp(let request)): 136 | self.requestHandler.handlePrepareAppRequest(request, for: connection) { (result) in 137 | finish(result) 138 | } 139 | 140 | case .success(.beginInstallation): break 141 | 142 | case .success(.installProvisioningProfiles(let request)): 143 | self.requestHandler.handleInstallProvisioningProfilesRequest(request, for: connection) { (result) in 144 | finish(result) 145 | } 146 | 147 | case .success(.removeProvisioningProfiles(let request)): 148 | self.requestHandler.handleRemoveProvisioningProfilesRequest(request, for: connection) { (result) in 149 | finish(result) 150 | } 151 | 152 | case .success(.removeApp(let request)): 153 | self.requestHandler.handleRemoveAppRequest(request, for: connection) { (result) in 154 | finish(result) 155 | } 156 | 157 | case .success(.enableUnsignedCodeExecution(let request)): 158 | self.requestHandler.handleEnableUnsignedCodeExecutionRequest(request, for: connection) { (result) in 159 | finish(result) 160 | } 161 | 162 | case .success(.unknown): 163 | finish(Result.failure(ALTServerError(.unknownRequest))) 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Shared/Connections/NetworkConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkConnection.swift 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 6/1/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Network 11 | 12 | public class NetworkConnection: NSObject, Connection 13 | { 14 | public let nwConnection: NWConnection 15 | 16 | public init(_ nwConnection: NWConnection) 17 | { 18 | self.nwConnection = nwConnection 19 | } 20 | 21 | public func __send(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void) 22 | { 23 | self.nwConnection.send(content: data, completion: .contentProcessed { (error) in 24 | completionHandler(error == nil, error) 25 | }) 26 | } 27 | 28 | public func __receiveData(expectedSize: Int, completionHandler: @escaping (Data?, Error?) -> Void) 29 | { 30 | self.nwConnection.receive(minimumIncompleteLength: expectedSize, maximumLength: expectedSize) { (data, context, isComplete, error) in 31 | guard data != nil || error != nil else { 32 | return completionHandler(nil, ALTServerError(.lostConnection)) 33 | } 34 | 35 | completionHandler(data, error) 36 | } 37 | } 38 | 39 | public func disconnect() 40 | { 41 | switch self.nwConnection.state 42 | { 43 | case .cancelled, .failed: break 44 | default: self.nwConnection.cancel() 45 | } 46 | } 47 | } 48 | 49 | extension NetworkConnection 50 | { 51 | override public var description: String { 52 | return "\(self.nwConnection.endpoint) (Network)" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Shared/Extensions/ALTApplication+AltStoreApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ALTApplication+AltStoreApp.swift 3 | // AltStore 4 | // 5 | // Created by Riley Testut on 11/11/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | import AltSign 10 | 11 | extension ALTApplication 12 | { 13 | static let altstoreBundleID = "com.rileytestut.AltStore" 14 | 15 | var isAltStoreApp: Bool { 16 | let isAltStoreApp = self.bundleIdentifier.contains(ALTApplication.altstoreBundleID) 17 | return isAltStoreApp 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Shared/Extensions/ALTServerError+Conveniences.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ALTServerError+Conveniences.swift 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 6/4/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AltSign 11 | 12 | public extension ALTServerError 13 | { 14 | init(_ error: E) 15 | { 16 | switch error 17 | { 18 | case let error as ALTServerError: self = error 19 | case let error as ALTServerConnectionError: self = ALTServerError(.connectionFailed, underlyingError: error) 20 | case let error as ALTAppleAPIError where error.code == .invalidAnisetteData: self = ALTServerError(.invalidAnisetteData, underlyingError: error) 21 | case is DecodingError: self = ALTServerError(.invalidRequest, underlyingError: error) 22 | case is EncodingError: self = ALTServerError(.invalidResponse, underlyingError: error) 23 | case let error as NSError: 24 | var userInfo = error.userInfo 25 | if !userInfo.keys.contains(NSUnderlyingErrorKey) 26 | { 27 | // Assign underlying error (if there isn't already one). 28 | userInfo[NSUnderlyingErrorKey] = error 29 | } 30 | 31 | self = ALTServerError(.underlyingError, userInfo: userInfo) 32 | } 33 | } 34 | 35 | init(_ code: ALTServerError.Code, underlyingError: E) 36 | { 37 | self = ALTServerError(code, userInfo: [NSUnderlyingErrorKey: underlyingError]) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Shared/Extensions/Bundle+AltStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+AltStore.swift 3 | // AltStore 4 | // 5 | // Created by Riley Testut on 5/30/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Bundle 12 | { 13 | struct Info 14 | { 15 | public static let deviceID = "ALTDeviceID" 16 | public static let serverID = "ALTServerID" 17 | public static let devicePairingString = "ALTPairingFile" 18 | public static let certificateID = "ALTCertificateID" 19 | public static let appGroups = "ALTAppGroups" 20 | public static let altBundleID = "ALTBundleIdentifier" 21 | 22 | public static let urlTypes = "CFBundleURLTypes" 23 | public static let exportedUTIs = "UTExportedTypeDeclarations" 24 | 25 | public static let untetherURL = "ALTFugu14UntetherURL" 26 | public static let untetherRequired = "ALTFugu14UntetherRequired" 27 | public static let untetherMinimumiOSVersion = "ALTFugu14UntetherMinimumVersion" 28 | public static let untetherMaximumiOSVersion = "ALTFugu14UntetherMaximumVersion" 29 | } 30 | } 31 | 32 | public extension Bundle 33 | { 34 | var infoPlistURL: URL { 35 | let infoPlistURL = self.bundleURL.appendingPathComponent("Info.plist") 36 | return infoPlistURL 37 | } 38 | 39 | var provisioningProfileURL: URL { 40 | let provisioningProfileURL = self.bundleURL.appendingPathComponent("embedded.mobileprovision") 41 | return provisioningProfileURL 42 | } 43 | 44 | var certificateURL: URL { 45 | let certificateURL = self.bundleURL.appendingPathComponent("ALTCertificate.p12") 46 | return certificateURL 47 | } 48 | 49 | var altstorePlistURL: URL { 50 | let altstorePlistURL = self.bundleURL.appendingPathComponent("AltStore.plist") 51 | return altstorePlistURL 52 | } 53 | } 54 | 55 | public extension Bundle 56 | { 57 | static var baseAltStoreAppGroupID = "group.com.SideStore.SideStore" 58 | 59 | var appGroups: [String] { 60 | return self.infoDictionary?[Bundle.Info.appGroups] as? [String] ?? [] 61 | } 62 | 63 | var altstoreAppGroup: String? { 64 | let appGroup = self.appGroups.first { $0.contains(Bundle.baseAltStoreAppGroupID) } 65 | return appGroup 66 | } 67 | 68 | var completeInfoDictionary: [String : Any]? { 69 | let infoPlistURL = self.infoPlistURL 70 | return NSDictionary(contentsOf: infoPlistURL) as? [String : Any] 71 | } 72 | 73 | var appIdentifierPrefix: String? { 74 | infoDictionary?["AppIdentifierPrefix"] as? String 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Shared/Extensions/NSError+AltStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+AltStore.swift 3 | // AltStore 4 | // 5 | // Created by Riley Testut on 3/11/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSError 12 | { 13 | @objc(alt_localizedFailure) 14 | var localizedFailure: String? { 15 | let localizedFailure = (self.userInfo[NSLocalizedFailureErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedFailureErrorKey) as? String) 16 | return localizedFailure 17 | } 18 | 19 | @objc(alt_localizedDebugDescription) 20 | var localizedDebugDescription: String? { 21 | let debugDescription = (self.userInfo[NSDebugDescriptionErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSDebugDescriptionErrorKey) as? String) 22 | return debugDescription 23 | } 24 | 25 | @objc(alt_errorWithLocalizedFailure:) 26 | func withLocalizedFailure(_ failure: String) -> NSError 27 | { 28 | var userInfo = self.userInfo 29 | userInfo[NSLocalizedFailureErrorKey] = failure 30 | 31 | if let failureReason = self.localizedFailureReason 32 | { 33 | userInfo[NSLocalizedFailureReasonErrorKey] = failureReason 34 | } 35 | else if self.localizedFailure == nil && self.localizedFailureReason == nil && self.localizedDescription.contains(self.localizedErrorCode) 36 | { 37 | // Default localizedDescription, so replace with just the localized error code portion. 38 | userInfo[NSLocalizedFailureReasonErrorKey] = "(\(self.localizedErrorCode).)" 39 | } 40 | else 41 | { 42 | userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedDescription 43 | } 44 | 45 | if let localizedDescription = NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedDescriptionKey) as? String 46 | { 47 | userInfo[NSLocalizedDescriptionKey] = localizedDescription 48 | } 49 | 50 | // Don't accidentally remove localizedDescription from dictionary 51 | // userInfo[NSLocalizedDescriptionKey] = NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedDescriptionKey) as? String 52 | 53 | if let recoverySuggestion = self.localizedRecoverySuggestion 54 | { 55 | userInfo[NSLocalizedRecoverySuggestionErrorKey] = recoverySuggestion 56 | } 57 | 58 | let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo) 59 | return error 60 | } 61 | 62 | func sanitizedForCoreData() -> NSError 63 | { 64 | var userInfo = self.userInfo 65 | userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure 66 | userInfo[NSLocalizedDescriptionKey] = self.localizedDescription 67 | userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason 68 | userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion 69 | 70 | // Remove userInfo values that don't conform to NSSecureEncoding. 71 | userInfo = userInfo.filter { (key, value) in 72 | return (value as AnyObject) is NSSecureCoding 73 | } 74 | 75 | // Sanitize underlying errors. 76 | if let underlyingError = userInfo[NSUnderlyingErrorKey] as? Error 77 | { 78 | let sanitizedError = (underlyingError as NSError).sanitizedForCoreData() 79 | userInfo[NSUnderlyingErrorKey] = sanitizedError 80 | } 81 | 82 | if #available(iOS 14.5, macOS 11.3, *), let underlyingErrors = userInfo[NSMultipleUnderlyingErrorsKey] as? [Error] 83 | { 84 | let sanitizedErrors = underlyingErrors.map { ($0 as NSError).sanitizedForCoreData() } 85 | userInfo[NSMultipleUnderlyingErrorsKey] = sanitizedErrors 86 | } 87 | 88 | let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo) 89 | return error 90 | } 91 | } 92 | 93 | extension Error 94 | { 95 | var underlyingError: Error? { 96 | let underlyingError = (self as NSError).userInfo[NSUnderlyingErrorKey] as? Error 97 | return underlyingError 98 | } 99 | 100 | var localizedErrorCode: String { 101 | let localizedErrorCode = String(format: NSLocalizedString("%@ error %@", comment: ""), (self as NSError).domain, (self as NSError).code as NSNumber) 102 | return localizedErrorCode 103 | } 104 | } 105 | 106 | protocol ALTLocalizedError: LocalizedError, CustomNSError 107 | { 108 | var failure: String? { get } 109 | 110 | var underlyingError: Error? { get } 111 | } 112 | 113 | extension ALTLocalizedError 114 | { 115 | var errorUserInfo: [String : Any] { 116 | let userInfo = ([ 117 | NSLocalizedDescriptionKey: self.errorDescription, 118 | NSLocalizedFailureReasonErrorKey: self.failureReason, 119 | NSLocalizedFailureErrorKey: self.failure, 120 | NSUnderlyingErrorKey: self.underlyingError 121 | ] as [String: Any?]).compactMapValues { $0 } 122 | return userInfo 123 | } 124 | 125 | var underlyingError: Error? { 126 | // Error's default implementation calls errorUserInfo, 127 | // but ALTLocalizedError.errorUserInfo calls underlyingError. 128 | // Return nil to prevent infinite recursion. 129 | return nil 130 | } 131 | 132 | var errorDescription: String? { 133 | guard let errorFailure = self.failure else { return (self.underlyingError as NSError?)?.localizedDescription } 134 | guard let failureReason = self.failureReason else { return errorFailure } 135 | 136 | let errorDescription = errorFailure + " " + failureReason 137 | return errorDescription 138 | } 139 | 140 | var failureReason: String? { (self.underlyingError as NSError?)?.localizedDescription } 141 | var recoverySuggestion: String? { (self.underlyingError as NSError?)?.localizedRecoverySuggestion } 142 | var helpAnchor: String? { (self.underlyingError as NSError?)?.helpAnchor } 143 | } 144 | -------------------------------------------------------------------------------- /Shared/Extensions/NSXPCConnection+MachServices.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSXPCConnection+MachServices.swift 3 | // AltStore 4 | // 5 | // Created by Riley Testut on 9/22/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc private protocol XPCPrivateAPI 12 | { 13 | init(machServiceName: String) 14 | init(machServiceName: String, options: NSXPCConnection.Options) 15 | } 16 | 17 | public extension NSXPCConnection 18 | { 19 | class func makeConnection(machServiceName: String) -> NSXPCConnection 20 | { 21 | let connection = unsafeBitCast(self, to: XPCPrivateAPI.Type.self).init(machServiceName: machServiceName, options: .privileged) 22 | return unsafeBitCast(connection, to: NSXPCConnection.self) 23 | } 24 | } 25 | 26 | public extension NSXPCListener 27 | { 28 | class func makeListener(machServiceName: String) -> NSXPCListener 29 | { 30 | let listener = unsafeBitCast(self, to: XPCPrivateAPI.Type.self).init(machServiceName: machServiceName) 31 | return unsafeBitCast(listener, to: NSXPCListener.self) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Shared/Extensions/Result+Conveniences.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result+Conveniences.swift 3 | // AltStore 4 | // 5 | // Created by Riley Testut on 5/22/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Result 12 | { 13 | var value: Success? { 14 | switch self 15 | { 16 | case .success(let value): return value 17 | case .failure: return nil 18 | } 19 | } 20 | 21 | var error: Failure? { 22 | switch self 23 | { 24 | case .success: return nil 25 | case .failure(let error): return error 26 | } 27 | } 28 | 29 | init(_ value: Success?, _ error: Failure?) 30 | { 31 | switch (value, error) 32 | { 33 | case (let value?, _): self = .success(value) 34 | case (_, let error?): self = .failure(error) 35 | case (nil, nil): preconditionFailure("Either value or error must be non-nil") 36 | } 37 | } 38 | } 39 | 40 | public extension Result where Success == Void 41 | { 42 | init(_ success: Bool, _ error: Failure?) 43 | { 44 | if success 45 | { 46 | self = .success(()) 47 | } 48 | else if let error = error 49 | { 50 | self = .failure(error) 51 | } 52 | else 53 | { 54 | preconditionFailure("Error must be non-nil if success is false") 55 | } 56 | } 57 | } 58 | 59 | public extension Result 60 | { 61 | init(_ values: (T?, U?), _ error: Failure?) where Success == (T, U) 62 | { 63 | if let value1 = values.0, let value2 = values.1 64 | { 65 | self = .success((value1, value2)) 66 | } 67 | else if let error = error 68 | { 69 | self = .failure(error) 70 | } 71 | else 72 | { 73 | preconditionFailure("Error must be non-nil if either provided values are nil") 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Shared/Server Protocol/CodableServerError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodableServerError.swift 3 | // AltKit 4 | // 5 | // Created by Riley Testut on 3/5/20. 6 | // Copyright © 2020 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself 12 | extension ALTServerError.Code: Codable {} 13 | 14 | extension CodableServerError 15 | { 16 | enum UserInfoValue: Codable 17 | { 18 | case string(String) 19 | case error(NSError) 20 | 21 | public init(from decoder: Decoder) throws 22 | { 23 | let container = try decoder.singleValueContainer() 24 | 25 | if 26 | let data = try? container.decode(Data.self), 27 | let error = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSError.self, from: data) 28 | { 29 | self = .error(error) 30 | } 31 | else if let string = try? container.decode(String.self) 32 | { 33 | self = .string(string) 34 | } 35 | else 36 | { 37 | throw DecodingError.dataCorruptedError(in: container, debugDescription: "UserInfoValue value cannot be decoded") 38 | } 39 | } 40 | 41 | func encode(to encoder: Encoder) throws 42 | { 43 | var container = encoder.singleValueContainer() 44 | 45 | switch self 46 | { 47 | case .string(let string): try container.encode(string) 48 | case .error(let error): 49 | guard let data = try? NSKeyedArchiver.archivedData(withRootObject: error, requiringSecureCoding: true) else { 50 | let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "UserInfoValue value \(self) cannot be encoded") 51 | throw EncodingError.invalidValue(self, context) 52 | } 53 | 54 | try container.encode(data) 55 | } 56 | } 57 | } 58 | } 59 | 60 | struct CodableServerError: Codable 61 | { 62 | var error: ALTServerError { 63 | return ALTServerError(self.errorCode, userInfo: self.userInfo ?? [:]) 64 | } 65 | 66 | private var errorCode: ALTServerError.Code 67 | private var userInfo: [String: Any]? 68 | 69 | private enum CodingKeys: String, CodingKey 70 | { 71 | case errorCode 72 | case userInfo 73 | } 74 | 75 | init(error: ALTServerError) 76 | { 77 | self.errorCode = error.code 78 | 79 | var userInfo = error.userInfo 80 | if let localizedRecoverySuggestion = (error as NSError).localizedRecoverySuggestion 81 | { 82 | userInfo[NSLocalizedRecoverySuggestionErrorKey] = localizedRecoverySuggestion 83 | } 84 | 85 | if !userInfo.isEmpty 86 | { 87 | self.userInfo = userInfo 88 | } 89 | } 90 | 91 | init(from decoder: Decoder) throws 92 | { 93 | let container = try decoder.container(keyedBy: CodingKeys.self) 94 | 95 | let errorCode = try container.decode(Int.self, forKey: .errorCode) 96 | self.errorCode = ALTServerError.Code(rawValue: errorCode) ?? .unknown 97 | 98 | let rawUserInfo = try container.decodeIfPresent([String: UserInfoValue].self, forKey: .userInfo) 99 | 100 | let userInfo = rawUserInfo?.mapValues { (value) -> Any in 101 | switch value 102 | { 103 | case .string(let string): return string 104 | case .error(let error): return error 105 | } 106 | } 107 | self.userInfo = userInfo 108 | } 109 | 110 | func encode(to encoder: Encoder) throws 111 | { 112 | var container = encoder.container(keyedBy: CodingKeys.self) 113 | try container.encode(self.error.code.rawValue, forKey: .errorCode) 114 | 115 | let rawUserInfo = self.userInfo?.compactMapValues { (value) -> UserInfoValue? in 116 | switch value 117 | { 118 | case let string as String: return .string(string) 119 | case let error as NSError: return .error(error) 120 | default: return nil 121 | } 122 | } 123 | try container.encodeIfPresent(rawUserInfo, forKey: .userInfo) 124 | } 125 | } 126 | 127 | -------------------------------------------------------------------------------- /Shared/Server Protocol/ServerProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerProtocol.swift 3 | // AltServer 4 | // 5 | // Created by Riley Testut on 5/24/19. 6 | // Copyright © 2019 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AltSign 11 | 12 | public let ALTServerServiceType = "_altserver._tcp" 13 | 14 | protocol ServerMessageProtocol: Codable 15 | { 16 | var version: Int { get } 17 | var identifier: String { get } 18 | } 19 | 20 | public enum ServerRequest: Decodable 21 | { 22 | case anisetteData(AnisetteDataRequest) 23 | case prepareApp(PrepareAppRequest) 24 | case beginInstallation(BeginInstallationRequest) 25 | case installProvisioningProfiles(InstallProvisioningProfilesRequest) 26 | case removeProvisioningProfiles(RemoveProvisioningProfilesRequest) 27 | case removeApp(RemoveAppRequest) 28 | case enableUnsignedCodeExecution(EnableUnsignedCodeExecutionRequest) 29 | case unknown(identifier: String, version: Int) 30 | 31 | var identifier: String { 32 | switch self 33 | { 34 | case .anisetteData(let request): return request.identifier 35 | case .prepareApp(let request): return request.identifier 36 | case .beginInstallation(let request): return request.identifier 37 | case .installProvisioningProfiles(let request): return request.identifier 38 | case .removeProvisioningProfiles(let request): return request.identifier 39 | case .removeApp(let request): return request.identifier 40 | case .enableUnsignedCodeExecution(let request): return request.identifier 41 | case .unknown(let identifier, _): return identifier 42 | } 43 | } 44 | 45 | var version: Int { 46 | switch self 47 | { 48 | case .anisetteData(let request): return request.version 49 | case .prepareApp(let request): return request.version 50 | case .beginInstallation(let request): return request.version 51 | case .installProvisioningProfiles(let request): return request.version 52 | case .removeProvisioningProfiles(let request): return request.version 53 | case .removeApp(let request): return request.version 54 | case .enableUnsignedCodeExecution(let request): return request.version 55 | case .unknown(_, let version): return version 56 | } 57 | } 58 | 59 | private enum CodingKeys: String, CodingKey 60 | { 61 | case identifier 62 | case version 63 | } 64 | 65 | public init(from decoder: Decoder) throws 66 | { 67 | let container = try decoder.container(keyedBy: CodingKeys.self) 68 | 69 | let version = try container.decode(Int.self, forKey: .version) 70 | 71 | let identifier = try container.decode(String.self, forKey: .identifier) 72 | switch identifier 73 | { 74 | case "AnisetteDataRequest": 75 | let request = try AnisetteDataRequest(from: decoder) 76 | self = .anisetteData(request) 77 | 78 | case "PrepareAppRequest": 79 | let request = try PrepareAppRequest(from: decoder) 80 | self = .prepareApp(request) 81 | 82 | case "BeginInstallationRequest": 83 | let request = try BeginInstallationRequest(from: decoder) 84 | self = .beginInstallation(request) 85 | 86 | case "InstallProvisioningProfilesRequest": 87 | let request = try InstallProvisioningProfilesRequest(from: decoder) 88 | self = .installProvisioningProfiles(request) 89 | 90 | case "RemoveProvisioningProfilesRequest": 91 | let request = try RemoveProvisioningProfilesRequest(from: decoder) 92 | self = .removeProvisioningProfiles(request) 93 | 94 | case "RemoveAppRequest": 95 | let request = try RemoveAppRequest(from: decoder) 96 | self = .removeApp(request) 97 | 98 | case "EnableUnsignedCodeExecutionRequest": 99 | let request = try EnableUnsignedCodeExecutionRequest(from: decoder) 100 | self = .enableUnsignedCodeExecution(request) 101 | 102 | default: 103 | self = .unknown(identifier: identifier, version: version) 104 | } 105 | } 106 | } 107 | 108 | public enum ServerResponse: Decodable 109 | { 110 | case anisetteData(AnisetteDataResponse) 111 | case installationProgress(InstallationProgressResponse) 112 | case installProvisioningProfiles(InstallProvisioningProfilesResponse) 113 | case removeProvisioningProfiles(RemoveProvisioningProfilesResponse) 114 | case removeApp(RemoveAppResponse) 115 | case enableUnsignedCodeExecution(EnableUnsignedCodeExecutionResponse) 116 | case error(ErrorResponse) 117 | case unknown(identifier: String, version: Int) 118 | 119 | var identifier: String { 120 | switch self 121 | { 122 | case .anisetteData(let response): return response.identifier 123 | case .installationProgress(let response): return response.identifier 124 | case .installProvisioningProfiles(let response): return response.identifier 125 | case .removeProvisioningProfiles(let response): return response.identifier 126 | case .removeApp(let response): return response.identifier 127 | case .enableUnsignedCodeExecution(let response): return response.identifier 128 | case .error(let response): return response.identifier 129 | case .unknown(let identifier, _): return identifier 130 | } 131 | } 132 | 133 | var version: Int { 134 | switch self 135 | { 136 | case .anisetteData(let response): return response.version 137 | case .installationProgress(let response): return response.version 138 | case .installProvisioningProfiles(let response): return response.version 139 | case .removeProvisioningProfiles(let response): return response.version 140 | case .removeApp(let response): return response.version 141 | case .enableUnsignedCodeExecution(let response): return response.version 142 | case .error(let response): return response.version 143 | case .unknown(_, let version): return version 144 | } 145 | } 146 | 147 | private enum CodingKeys: String, CodingKey 148 | { 149 | case identifier 150 | case version 151 | } 152 | 153 | public init(from decoder: Decoder) throws 154 | { 155 | let container = try decoder.container(keyedBy: CodingKeys.self) 156 | 157 | let version = try container.decode(Int.self, forKey: .version) 158 | 159 | let identifier = try container.decode(String.self, forKey: .identifier) 160 | switch identifier 161 | { 162 | case "AnisetteDataResponse": 163 | let response = try AnisetteDataResponse(from: decoder) 164 | self = .anisetteData(response) 165 | 166 | case "InstallationProgressResponse": 167 | let response = try InstallationProgressResponse(from: decoder) 168 | self = .installationProgress(response) 169 | 170 | case "InstallProvisioningProfilesResponse": 171 | let response = try InstallProvisioningProfilesResponse(from: decoder) 172 | self = .installProvisioningProfiles(response) 173 | 174 | case "RemoveProvisioningProfilesResponse": 175 | let response = try RemoveProvisioningProfilesResponse(from: decoder) 176 | self = .removeProvisioningProfiles(response) 177 | 178 | case "RemoveAppResponse": 179 | let response = try RemoveAppResponse(from: decoder) 180 | self = .removeApp(response) 181 | 182 | case "EnableUnsignedCodeExecutionResponse": 183 | let response = try EnableUnsignedCodeExecutionResponse(from: decoder) 184 | self = .enableUnsignedCodeExecution(response) 185 | 186 | case "ErrorResponse": 187 | let response = try ErrorResponse(from: decoder) 188 | self = .error(response) 189 | 190 | default: 191 | self = .unknown(identifier: identifier, version: version) 192 | } 193 | } 194 | } 195 | 196 | // _Don't_ provide generic SuccessResponse, as that would prevent us 197 | // from easily changing response format for a request in the future. 198 | public struct ErrorResponse: ServerMessageProtocol 199 | { 200 | public var version = 2 201 | public var identifier = "ErrorResponse" 202 | 203 | public var error: ALTServerError { 204 | return self.serverError?.error ?? ALTServerError(self.errorCode) 205 | } 206 | private var serverError: CodableServerError? 207 | 208 | // Legacy (v1) 209 | private var errorCode: ALTServerError.Code 210 | 211 | public init(error: ALTServerError) 212 | { 213 | self.serverError = CodableServerError(error: error) 214 | self.errorCode = error.code 215 | } 216 | } 217 | 218 | public struct AnisetteDataRequest: ServerMessageProtocol 219 | { 220 | public var version = 1 221 | public var identifier = "AnisetteDataRequest" 222 | 223 | public init() 224 | { 225 | } 226 | } 227 | 228 | public struct AnisetteDataResponse: ServerMessageProtocol 229 | { 230 | public var version = 1 231 | public var identifier = "AnisetteDataResponse" 232 | 233 | public var anisetteData: ALTAnisetteData 234 | 235 | private enum CodingKeys: String, CodingKey 236 | { 237 | case identifier 238 | case version 239 | case anisetteData 240 | } 241 | 242 | public init(anisetteData: ALTAnisetteData) 243 | { 244 | self.anisetteData = anisetteData 245 | } 246 | 247 | public init(from decoder: Decoder) throws 248 | { 249 | let container = try decoder.container(keyedBy: CodingKeys.self) 250 | self.version = try container.decode(Int.self, forKey: .version) 251 | self.identifier = try container.decode(String.self, forKey: .identifier) 252 | 253 | let json = try container.decode([String: String].self, forKey: .anisetteData) 254 | 255 | if let anisetteData = ALTAnisetteData(json: json) 256 | { 257 | self.anisetteData = anisetteData 258 | } 259 | else 260 | { 261 | throw DecodingError.dataCorruptedError(forKey: CodingKeys.anisetteData, in: container, debugDescription: "Couuld not parse anisette data from JSON") 262 | } 263 | } 264 | 265 | public func encode(to encoder: Encoder) throws 266 | { 267 | var container = encoder.container(keyedBy: CodingKeys.self) 268 | try container.encode(self.version, forKey: .version) 269 | try container.encode(self.identifier, forKey: .identifier) 270 | 271 | let json = self.anisetteData.json() 272 | try container.encode(json, forKey: .anisetteData) 273 | } 274 | } 275 | 276 | public struct PrepareAppRequest: ServerMessageProtocol 277 | { 278 | public var version = 1 279 | public var identifier = "PrepareAppRequest" 280 | 281 | public var udid: String 282 | public var contentSize: Int 283 | 284 | public var fileURL: URL? 285 | 286 | public init(udid: String, contentSize: Int, fileURL: URL? = nil) 287 | { 288 | self.udid = udid 289 | self.contentSize = contentSize 290 | self.fileURL = fileURL 291 | } 292 | } 293 | 294 | public struct BeginInstallationRequest: ServerMessageProtocol 295 | { 296 | public var version = 3 297 | public var identifier = "BeginInstallationRequest" 298 | 299 | // If activeProfiles is non-nil, then AltServer should remove all profiles except active ones. 300 | public var activeProfiles: Set? 301 | 302 | public var bundleIdentifier: String? 303 | 304 | public init(activeProfiles: Set?, bundleIdentifier: String?) 305 | { 306 | self.activeProfiles = activeProfiles 307 | self.bundleIdentifier = bundleIdentifier 308 | } 309 | } 310 | 311 | public struct InstallationProgressResponse: ServerMessageProtocol 312 | { 313 | public var version = 1 314 | public var identifier = "InstallationProgressResponse" 315 | 316 | public var progress: Double 317 | 318 | public init(progress: Double) 319 | { 320 | self.progress = progress 321 | } 322 | } 323 | 324 | public struct InstallProvisioningProfilesRequest: ServerMessageProtocol 325 | { 326 | public var version = 1 327 | public var identifier = "InstallProvisioningProfilesRequest" 328 | 329 | public var udid: String 330 | public var provisioningProfiles: Set 331 | 332 | // If activeProfiles is non-nil, then AltServer should remove all profiles except active ones. 333 | public var activeProfiles: Set? 334 | 335 | private enum CodingKeys: String, CodingKey 336 | { 337 | case identifier 338 | case version 339 | case udid 340 | case provisioningProfiles 341 | case activeProfiles 342 | } 343 | 344 | public init(udid: String, provisioningProfiles: Set, activeProfiles: Set?) 345 | { 346 | self.udid = udid 347 | self.provisioningProfiles = provisioningProfiles 348 | self.activeProfiles = activeProfiles 349 | } 350 | 351 | public init(from decoder: Decoder) throws 352 | { 353 | let container = try decoder.container(keyedBy: CodingKeys.self) 354 | self.version = try container.decode(Int.self, forKey: .version) 355 | self.identifier = try container.decode(String.self, forKey: .identifier) 356 | self.udid = try container.decode(String.self, forKey: .udid) 357 | 358 | let rawProvisioningProfiles = try container.decode([Data].self, forKey: .provisioningProfiles) 359 | let provisioningProfiles = try rawProvisioningProfiles.map { (data) -> ALTProvisioningProfile in 360 | guard let profile = ALTProvisioningProfile(data: data) else { 361 | throw DecodingError.dataCorruptedError(forKey: CodingKeys.provisioningProfiles, in: container, debugDescription: "Could not parse provisioning profile from data.") 362 | } 363 | return profile 364 | } 365 | 366 | self.provisioningProfiles = Set(provisioningProfiles) 367 | self.activeProfiles = try container.decodeIfPresent(Set.self, forKey: .activeProfiles) 368 | } 369 | 370 | public func encode(to encoder: Encoder) throws 371 | { 372 | var container = encoder.container(keyedBy: CodingKeys.self) 373 | try container.encode(self.version, forKey: .version) 374 | try container.encode(self.identifier, forKey: .identifier) 375 | try container.encode(self.udid, forKey: .udid) 376 | 377 | try container.encode(self.provisioningProfiles.map { $0.data }, forKey: .provisioningProfiles) 378 | try container.encodeIfPresent(self.activeProfiles, forKey: .activeProfiles) 379 | } 380 | } 381 | 382 | public struct InstallProvisioningProfilesResponse: ServerMessageProtocol 383 | { 384 | public var version = 1 385 | public var identifier = "InstallProvisioningProfilesResponse" 386 | 387 | public init() 388 | { 389 | } 390 | } 391 | 392 | public struct RemoveProvisioningProfilesRequest: ServerMessageProtocol 393 | { 394 | public var version = 1 395 | public var identifier = "RemoveProvisioningProfilesRequest" 396 | 397 | public var udid: String 398 | public var bundleIdentifiers: Set 399 | 400 | public init(udid: String, bundleIdentifiers: Set) 401 | { 402 | self.udid = udid 403 | self.bundleIdentifiers = bundleIdentifiers 404 | } 405 | } 406 | 407 | public struct RemoveProvisioningProfilesResponse: ServerMessageProtocol 408 | { 409 | public var version = 1 410 | public var identifier = "RemoveProvisioningProfilesResponse" 411 | 412 | public init() 413 | { 414 | } 415 | } 416 | 417 | public struct RemoveAppRequest: ServerMessageProtocol 418 | { 419 | public var version = 1 420 | public var identifier = "RemoveAppRequest" 421 | 422 | public var udid: String 423 | public var bundleIdentifier: String 424 | 425 | public init(udid: String, bundleIdentifier: String) 426 | { 427 | self.udid = udid 428 | self.bundleIdentifier = bundleIdentifier 429 | } 430 | } 431 | 432 | public struct RemoveAppResponse: ServerMessageProtocol 433 | { 434 | public var version = 1 435 | public var identifier = "RemoveAppResponse" 436 | 437 | public init() 438 | { 439 | } 440 | } 441 | 442 | public struct EnableUnsignedCodeExecutionRequest: ServerMessageProtocol 443 | { 444 | public var version = 1 445 | public var identifier = "EnableUnsignedCodeExecutionRequest" 446 | 447 | public var udid: String 448 | public var processID: Int? 449 | public var processName: String? 450 | 451 | public init(udid: String, processID: Int? = nil, processName: String? = nil) 452 | { 453 | self.udid = udid 454 | self.processID = processID 455 | self.processName = processName 456 | } 457 | } 458 | 459 | public struct EnableUnsignedCodeExecutionResponse: ServerMessageProtocol 460 | { 461 | public var version = 1 462 | public var identifier = "EnableUnsignedCodeExecutionResponse" 463 | 464 | public init() 465 | { 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /sparkle-macos.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SideServer Changelog 5 | Most recent changes with links to updates. 6 | en 7 | 8 | 9 | Version 1.0.6 10 | 13 |
  • Added a manual "Check for update..." menu bar item for users to check themselves if there is a new update
  • 14 |
  • Fixed "updating SideServer" bug. Now checks if it can be updated properly
  • 15 |
  • Notarized app
  • 16 | 17 | ]]> 18 |
    19 | Thu, 27 Mar 2023 12:10:00 -0005 20 | 21 | 10.14.4 22 |
    23 | 24 | 25 | Version 1.0.5 26 | 29 |
  • Added a manual "Check for update..." menu bar item for users to check themselves if there is a new update
  • 30 |
  • Fixed "updating SideServer" bug. Now checks if it can be updated properly
  • 31 |
  • Notarized app
  • 32 | 33 | ]]> 34 |
    35 | Thu, 27 Mar 2023 12:10:00 -0005 36 | 37 | 10.14.4 38 |
    39 | 40 | 41 | Version 1.0.4 42 | 45 |
  • Added a manual "Check for update..." menu bar item for users to check themselves if there is a new update
  • 46 |
  • Fixed "updating SideServer" bug. Now checks if it can be updated properly
  • 47 |
  • Notarized app
  • 48 | 49 | ]]> 50 |
    51 | Thu, 27 Mar 2023 12:05:00 -0005 52 | 53 | 10.14.4 54 |
    55 | 56 | 57 | Version 1.0.3 58 | 61 |
  • Added a manual "Check for update..." menu bar item for users to check themselves if there is a new update
  • 62 |
  • Fixed "updating SideServer" bug. Now checks if it can be updated properly
  • 63 |
  • Notarized app
  • 64 | 65 | ]]> 66 |
    67 | Thu, 27 Mar 2023 12:00:00 -0005 68 | 69 | 10.14.4 70 |
    71 | 72 | 73 | Version 1.0.2 74 | 77 |
  • Added a manual "Check for update..." menu bar item for users to check themselves if there is a new update
  • 78 |
  • Fixed "updating SideServer" bug. Now checks if it can be updated properly
  • 79 | 80 | ]]> 81 |
    82 | Thu, 14 Jul 2022 12:00:00 -0005 83 | 84 | 10.14.4 85 |
    86 | 87 | 88 | Version 1.0.1 89 | 92 |
  • Fixes Notarization issues with SideServer itself
  • 93 |
  • Using Apple IDs that contain capital letters
  • 94 |
  • Using Apple IDs that have 2FA enabled without any trusted devices
  • 95 | 96 | ]]> 97 |
    98 | Thu, 14 Jul 2022 12:00:00 -0005 99 | 100 | 10.14.4 101 |
    102 | 103 | 104 | Version 1.0 105 | 107 | New 108 |
  • Apple ID Log in / Log Out (saves credentials in keychain)
  • 109 |
  • Remove depency on Mail plugin
  • 110 |
  • Enable Just-in-Time (JIT) compilation for sideloaded apps with "Enable JIT" menu option
  • 111 |
  • Sideload apps (.ipa's) directly to iOS devices without installing SideStore first*
  • 112 |
  • Install SideStore onto multiple iOS devices using the same Apple ID
  • 113 | 114 |
      115 | Improved 116 |
    • Added “…” to menu items that require additional user input to match macOS HIG
    • 117 |
    • Prefers paid teams (organization or individual) over free teams if there are multiple Apple Developer teams associated with your Apple ID
    • 118 |
    • Revokes the previous iOS Development certificate created by AltStore (if one exists) instead of revoking a random one
    • 119 |
    • Improved various error messages
    • 120 |
    121 |
      122 | Fixed 123 |
    • Fix installing with correct app group.
    • 124 |
    125 | *Hold Option to make the "Sideload .ipa…" menu option appear 126 | ]]> 127 |
    128 | Tue, 03 May 2022 10:30:00 -0007 129 | 130 | 10.14.4 131 |
    132 |
    133 |
    134 | --------------------------------------------------------------------------------