Merge pull request #45 from SideStore/feature/noserver

Remove SideServer from monorepo
This commit is contained in:
Joe Mattiello
2022-11-22 22:06:30 -05:00
committed by GitHub
57 changed files with 2 additions and 7561 deletions

View File

@@ -1,23 +0,0 @@
//
// ALTPluginService.h
// AltPlugin
//
// Created by Riley Testut on 11/14/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
@class ALTAnisetteData;
NS_ASSUME_NONNULL_BEGIN
@interface ALTPluginService : NSObject
@property (class, nonatomic, readonly) ALTPluginService *sharedService;
- (ALTAnisetteData *)requestAnisetteData;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,105 +0,0 @@
//
// ALTPluginService.m
// AltPlugin
//
// Created by Riley Testut on 11/14/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import "ALTPluginService.h"
#import <dlfcn.h>
#import "ALTAnisetteData.h"
@import AppKit;
@interface AKAppleIDSession : NSObject
- (id)appleIDHeadersForRequest:(id)arg1;
@end
@interface AKDevice
+ (AKDevice *)currentDevice;
- (NSString *)uniqueDeviceIdentifier;
- (nullable NSString *)serialNumber;
- (NSString *)serverFriendlyDescription;
@end
@interface ALTPluginService ()
@property (nonatomic, readonly) NSISO8601DateFormatter *dateFormatter;
@end
@implementation ALTPluginService
+ (instancetype)sharedService
{
static ALTPluginService *_service = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_service = [[self alloc] init];
});
return _service;
}
- (instancetype)init
{
self = [super init];
if (self)
{
_dateFormatter = [[NSISO8601DateFormatter alloc] init];
}
return self;
}
+ (void)initialize
{
[[ALTPluginService sharedService] start];
}
- (void)start
{
dlopen("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit", RTLD_NOW);
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"com.rileytestut.AltServer.FetchAnisetteData" object:nil];
}
- (ALTAnisetteData *)requestAnisetteData
{
NSMutableURLRequest* req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"https://developerservices2.apple.com/services/QH65B2/listTeams.action?clientId=XABBG36SBA"]];
[req setHTTPMethod:@"POST"];
AKAppleIDSession *session = [[NSClassFromString(@"AKAppleIDSession") alloc] initWithIdentifier:@"com.apple.gs.xcode.auth"];
NSDictionary *headers = [session appleIDHeadersForRequest:req];
AKDevice *device = [NSClassFromString(@"AKDevice") currentDevice];
NSDate *date = [self.dateFormatter dateFromString:headers[@"X-Apple-I-Client-Time"]];
ALTAnisetteData *anisetteData = [[NSClassFromString(@"ALTAnisetteData") alloc] initWithMachineID:headers[@"X-Apple-I-MD-M"]
oneTimePassword:headers[@"X-Apple-I-MD"]
localUserID:headers[@"X-Apple-I-MD-LU"]
routingInfo:[headers[@"X-Apple-I-MD-RINFO"] longLongValue]
deviceUniqueIdentifier:device.uniqueDeviceIdentifier
deviceSerialNumber:device.serialNumber ?: @"C02LKHBBFD57" // serialNumber can be nil, so provide valid fallback serial number.
deviceDescription:device.serverFriendlyDescription
date:date
locale:[NSLocale currentLocale]
timeZone:[NSTimeZone localTimeZone]];
return anisetteData;
}
- (void)receiveNotification:(NSNotification *)notification
{
NSString *requestUUID = notification.userInfo[@"requestUUID"];
ALTAnisetteData *anisetteData = [self requestAnisetteData];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:anisetteData requiringSecureCoding:YES error:nil];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.rileytestut.AltServer.AnisetteDataResponse" object:nil userInfo:@{@"requestUUID": requestUUID, @"anisetteData": data} deliverImmediately:YES];
}
@end

View File

@@ -1,171 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Riley Testut. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>ALTPluginService</string>
<key>Supported10.14PluginCompatibilityUUIDs</key>
<array>
<string># UUIDs for versions from 10.12 to 99.99.99</string>
<string># For mail version 10.0 (3226) on OS X Version 10.12 (build 16A319)</string>
<string>36CCB8BB-2207-455E-89BC-B9D6E47ABB5B</string>
<string># For mail version 10.1 (3251) on OS X Version 10.12.1 (build 16B2553a)</string>
<string>9054AFD9-2607-489E-8E63-8B09A749BC61</string>
<string># For mail version 10.2 (3259) on OS X Version 10.12.2 (build 16D12b)</string>
<string>1CD3B36A-0E3B-4A26-8F7E-5BDF96AAC97E</string>
<string># For mail version 10.3 (3273) on OS X Version 10.12.4 (build 16G1036)</string>
<string>21560BD9-A3CC-482E-9B99-95B7BF61EDC1</string>
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
<string># For mail version 13.0 (3594.4.2) on OS X Version 10.15 (build 19A558d)</string>
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
</array>
<key>Supported10.15PluginCompatibilityUUIDs</key>
<array>
<string># UUIDs for versions from 10.12 to 99.99.99</string>
<string># For mail version 10.0 (3226) on OS X Version 10.12 (build 16A319)</string>
<string>36CCB8BB-2207-455E-89BC-B9D6E47ABB5B</string>
<string># For mail version 10.1 (3251) on OS X Version 10.12.1 (build 16B2553a)</string>
<string>9054AFD9-2607-489E-8E63-8B09A749BC61</string>
<string># For mail version 10.2 (3259) on OS X Version 10.12.2 (build 16D12b)</string>
<string>1CD3B36A-0E3B-4A26-8F7E-5BDF96AAC97E</string>
<string># For mail version 10.3 (3273) on OS X Version 10.12.4 (build 16G1036)</string>
<string>21560BD9-A3CC-482E-9B99-95B7BF61EDC1</string>
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
<string># For mail version 13.0 (3594.4.2) on OS X Version 10.15 (build 19A558d)</string>
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
</array>
<key>Supported11.0PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.10PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.1PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.2PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.3PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.4PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.5PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.6PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.7PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.8PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.9PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported12.0PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
</array>
<key>Supported12.1PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
</array>
<key>Supported12.2PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
</array>
<key>Supported12.3PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
</array>
<key>Supported12.4PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
</array>
<key>Supported12.5PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
</array>
<key>Supported12.6PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
</array>
<key>Supported12.7PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
</array>
<key>Supported12.8PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
</array>
<key>Supported12.9PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
</array>
<key>Supported13.0PluginCompatibilityUUIDs</key>
<array>
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
</array>
</dict>
</plist>

View File

@@ -1 +0,0 @@
#include "Build.xcconfig"

Binary file not shown.

View File

@@ -1,15 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "ALTDeviceManager.h"
#import "ALTWiredConnection.h"
#import "ALTNotificationConnection.h"
#import "ALTDebugConnection.h"
// Shared
#import "ALTConstants.h"
#import "ALTConnection.h"
#import "AltXPCProtocol.h"
#import "NSError+ALTServerError.h"
#import "CFNotificationName+AltStore.h"

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>
</array>
</dict>
</plist>

View File

@@ -1,163 +0,0 @@
//
// AnisetteDataManager.swift
// AltServer
//
// Created by Riley Testut on 11/16/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
import AppKit
private extension Bundle
{
struct ID
{
static let mail = "com.apple.mail"
static let altXPC = "com.rileytestut.AltXPC"
}
}
private extension ALTAnisetteData
{
func sanitize(byReplacingBundleID bundleID: String)
{
guard let range = self.deviceDescription.lowercased().range(of: "(" + bundleID.lowercased()) else { return }
var adjustedDescription = self.deviceDescription[..<range.lowerBound]
adjustedDescription += "(com.apple.dt.Xcode/3594.4.19)>"
self.deviceDescription = String(adjustedDescription)
}
}
class AnisetteDataManager: NSObject
{
static let shared = AnisetteDataManager()
private var anisetteDataCompletionHandlers: [String: (Result<ALTAnisetteData, Error>) -> Void] = [:]
private var anisetteDataTimers: [String: Timer] = [:]
private lazy var xpcConnection: NSXPCConnection = {
let connection = NSXPCConnection(serviceName: Bundle.ID.altXPC)
connection.remoteObjectInterface = NSXPCInterface(with: AltXPCProtocol.self)
connection.resume()
return connection
}()
private override init()
{
super.init()
DistributedNotificationCenter.default().addObserver(self, selector: #selector(AnisetteDataManager.handleAnisetteDataResponse(_:)), name: Notification.Name("com.rileytestut.AltServer.AnisetteDataResponse"), object: nil)
}
func requestAnisetteData(_ completion: @escaping (Result<ALTAnisetteData, Error>) -> Void)
{
if #available(macOS 10.15, *)
{
self.requestAnisetteDataFromXPCService { (result) in
do
{
let anisetteData = try result.get()
completion(.success(anisetteData))
}
catch CocoaError.xpcConnectionInterrupted
{
// SIP and/or AMFI are not disabled, so fall back to Mail plug-in.
self.requestAnisetteDataFromPlugin { (result) in
completion(result)
}
}
catch
{
completion(.failure(error))
}
}
}
else
{
self.requestAnisetteDataFromPlugin { (result) in
completion(result)
}
}
}
func isXPCAvailable(completion: @escaping (Bool) -> Void)
{
guard let proxy = self.xpcConnection.remoteObjectProxyWithErrorHandler({ (error) in
completion(false)
}) as? AltXPCProtocol else { return }
proxy.ping {
completion(true)
}
}
}
private extension AnisetteDataManager
{
@available(macOS 10.15, *)
func requestAnisetteDataFromXPCService(completion: @escaping (Result<ALTAnisetteData, Error>) -> Void)
{
guard let proxy = self.xpcConnection.remoteObjectProxyWithErrorHandler({ (error) in
print("Anisette XPC Error:", error)
completion(.failure(error))
}) as? AltXPCProtocol else { return }
proxy.requestAnisetteData { (anisetteData, error) in
anisetteData?.sanitize(byReplacingBundleID: Bundle.ID.altXPC)
completion(Result(anisetteData, error))
}
}
func requestAnisetteDataFromPlugin(completion: @escaping (Result<ALTAnisetteData, Error>) -> Void)
{
let requestUUID = UUID().uuidString
self.anisetteDataCompletionHandlers[requestUUID] = completion
let isMailRunning = NSWorkspace.shared.runningApplications.map { $0.bundleIdentifier }.contains { $0 == "com.apple.mail" }
if !isMailRunning, let mailApp = FileManager.default.urls(for: .applicationDirectory,in: .systemDomainMask).first?.appendingPathComponent("Mail.app") {
NSWorkspace.shared.open(mailApp)
}
let timer = Timer(timeInterval: 5.0, repeats: false) { (timer) in
self.finishRequest(forUUID: requestUUID, result: .failure(ALTServerError(.pluginNotFound)))
}
self.anisetteDataTimers[requestUUID] = timer
RunLoop.main.add(timer, forMode: .default)
DistributedNotificationCenter.default().postNotificationName(Notification.Name("com.rileytestut.AltServer.FetchAnisetteData"), object: nil, userInfo: ["requestUUID": requestUUID], options: .deliverImmediately)
}
@objc func handleAnisetteDataResponse(_ notification: Notification)
{
guard let userInfo = notification.userInfo, let requestUUID = userInfo["requestUUID"] as? String else { return }
if
let archivedAnisetteData = userInfo["anisetteData"] as? Data,
let anisetteData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ALTAnisetteData.self, from: archivedAnisetteData)
{
anisetteData.sanitize(byReplacingBundleID: Bundle.ID.mail)
self.finishRequest(forUUID: requestUUID, result: .success(anisetteData))
}
else
{
self.finishRequest(forUUID: requestUUID, result: .failure(ALTServerError(.invalidAnisetteData)))
}
}
func finishRequest(forUUID requestUUID: String, result: Result<ALTAnisetteData, Error>)
{
let completionHandler = self.anisetteDataCompletionHandlers[requestUUID]
self.anisetteDataCompletionHandlers[requestUUID] = nil
let timer = self.anisetteDataTimers[requestUUID]
self.anisetteDataTimers[requestUUID] = nil
timer?.invalidate()
completionHandler?(result)
}
}

View File

@@ -1,617 +0,0 @@
//
// AppDelegate.swift
// AltServer
//
// Created by Riley Testut on 5/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Cocoa
import UserNotifications
import AltSign
import LaunchAtLogin
import Sparkle
#if STAGING
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altstore.ipa")!
#elseif BETA
private let altstoreAppURL = URL(string: "https://cdn.altstore.io/file/altstore/altstore-beta.ipa")!
#else
private let altstoreAppURL = URL(string: "https://cdn.altstore.io/file/altstore/altstore.ipa")!
#endif
extension ALTDevice: MenuDisplayable {}
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
private let pluginManager = PluginManager()
private var statusItem: NSStatusItem?
private var connectedDevices = [ALTDevice]()
private weak var authenticationAlert: NSAlert?
@IBOutlet private var appMenu: NSMenu!
@IBOutlet private var connectedDevicesMenu: NSMenu!
@IBOutlet private var sideloadIPAConnectedDevicesMenu: NSMenu!
@IBOutlet private var enableJITMenu: NSMenu!
@IBOutlet private var launchAtLoginMenuItem: NSMenuItem!
@IBOutlet private var installMailPluginMenuItem: NSMenuItem!
@IBOutlet private var installAltStoreMenuItem: NSMenuItem!
@IBOutlet private var sideloadAppMenuItem: NSMenuItem!
@IBOutlet private var checkForUpdatesMenuItem: NSMenuItem!
private weak var authenticationAppleIDTextField: NSTextField?
private weak var authenticationPasswordTextField: NSSecureTextField?
private var connectedDevicesMenuController: MenuController<ALTDevice>!
private var sideloadIPAConnectedDevicesMenuController: MenuController<ALTDevice>!
private var enableJITMenuController: MenuController<ALTDevice>!
private var _jitAppListMenuControllers = [AnyObject]()
private var isAltPluginUpdateAvailable = false
@IBOutlet private var updaterController: SPUStandardUpdaterController! = {
return SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
}()
func applicationDidFinishLaunching(_ aNotification: Notification) {
UserDefaults.standard.registerDefaults()
UNUserNotificationCenter.current().delegate = self
ServerConnectionManager.shared.start()
ALTDeviceManager.shared.start()
let item = NSStatusBar.system.statusItem(withLength: -1)
item.menu = self.appMenu
item.button?.image = NSImage(named: "MenuBarIcon")
self.statusItem = item
self.appMenu.delegate = self
self.sideloadAppMenuItem.keyEquivalentModifierMask = .option
self.sideloadAppMenuItem.isAlternate = true
let placeholder = NSLocalizedString("No Connected Devices", comment: "")
self.connectedDevicesMenuController = MenuController<ALTDevice>(menu: self.connectedDevicesMenu, items: [])
self.connectedDevicesMenuController.placeholder = placeholder
self.connectedDevicesMenuController.action = { [weak self] device in
self?.installAltStore(to: device)
}
self.sideloadIPAConnectedDevicesMenuController = MenuController<ALTDevice>(menu: self.sideloadIPAConnectedDevicesMenu, items: [])
self.sideloadIPAConnectedDevicesMenuController.placeholder = placeholder
self.sideloadIPAConnectedDevicesMenuController.action = { [weak self] device in
self?.sideloadIPA(to: device)
}
self.enableJITMenuController = MenuController<ALTDevice>(menu: self.enableJITMenu, items: [])
self.enableJITMenuController.placeholder = placeholder
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { (success, error) in
guard success else { return }
if !UserDefaults.standard.didPresentInitialNotification
{
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("AltServer Running", comment: "")
content.body = NSLocalizedString("AltServer runs in the background as a menu bar app listening for AltStore.", comment: "")
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
UserDefaults.standard.didPresentInitialNotification = true
}
}
self.pluginManager.isUpdateAvailable { result in
guard let isUpdateAvailable = try? result.get() else { return }
self.isAltPluginUpdateAvailable = isUpdateAvailable
if isUpdateAvailable
{
self.installMailPlugin()
}
}
}
func applicationWillTerminate(_ aNotification: Notification)
{
// Insert code here to tear down your application
}
}
private extension AppDelegate
{
@objc func installAltStore(to device: ALTDevice)
{
self.installApplication(at: altstoreAppURL, to: device)
}
@objc func sideloadIPA(to device: ALTDevice)
{
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let openPanel = NSOpenPanel()
openPanel.canChooseDirectories = false
openPanel.allowsMultipleSelection = false
openPanel.allowedFileTypes = ["ipa"]
openPanel.begin { (response) in
guard let fileURL = openPanel.url, response == .OK else { return }
self.installApplication(at: fileURL, to: device)
}
}
func enableJIT(for app: InstalledApp, on device: ALTDevice)
{
func finish(_ result: Result<Void, Error>)
{
DispatchQueue.main.async {
switch result
{
case .failure(let error):
self.showErrorAlert(error: error, localizedFailure: String(format: NSLocalizedString("JIT compilation could not be enabled for %@.", comment: ""), app.name))
case .success:
let alert = NSAlert()
alert.messageText = String(format: NSLocalizedString("Successfully enabled JIT for %@.", comment: ""), app.name)
alert.informativeText = String(format: NSLocalizedString("JIT will remain enabled until you quit the app. You can now disconnect %@ from your computer.", comment: ""), device.name)
alert.runModal()
}
}
}
ALTDeviceManager.shared.prepare(device) { (result) in
switch result
{
case .failure(let error as NSError): return finish(.failure(error))
case .success:
ALTDeviceManager.shared.startDebugConnection(to: device) { (connection, error) in
guard let connection = connection else {
return finish(.failure(error! as NSError))
}
connection.enableUnsignedCodeExecutionForProcess(withName: app.executableName) { (success, error) in
guard success else {
return finish(.failure(error!))
}
finish(.success(()))
}
}
}
}
}
func installApplication(at url: URL, to device: ALTDevice)
{
let alert = NSAlert()
alert.messageText = NSLocalizedString("Please enter your Apple ID and password.", comment: "")
alert.informativeText = NSLocalizedString("Your Apple ID and password are not saved and are only sent to Apple for authentication.", comment: "")
let textFieldSize = NSSize(width: 300, height: 22)
let appleIDTextField = NSTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
appleIDTextField.delegate = self
appleIDTextField.translatesAutoresizingMaskIntoConstraints = false
appleIDTextField.placeholderString = NSLocalizedString("Apple ID", comment: "")
alert.window.initialFirstResponder = appleIDTextField
self.authenticationAppleIDTextField = appleIDTextField
let passwordTextField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
passwordTextField.delegate = self
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.placeholderString = NSLocalizedString("Password", comment: "")
self.authenticationPasswordTextField = passwordTextField
appleIDTextField.nextKeyView = passwordTextField
let stackView = NSStackView(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height * 2))
stackView.orientation = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 0
stackView.addArrangedSubview(appleIDTextField)
stackView.addArrangedSubview(passwordTextField)
alert.accessoryView = stackView
alert.addButton(withTitle: NSLocalizedString("Install", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
self.authenticationAlert = alert
self.validate()
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let response = alert.runModal()
guard response == .alertFirstButtonReturn else { return }
let username = appleIDTextField.stringValue
let password = passwordTextField.stringValue
func finish(_ result: Result<ALTApplication, Error>)
{
switch result
{
case .success(let application):
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Installation Succeeded", comment: "")
content.body = String(format: NSLocalizedString("%@ was successfully installed on %@.", comment: ""), application.name, device.name)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
case .failure(InstallError.cancelled), .failure(ALTAppleAPIError.requiresTwoFactorAuthentication):
// Ignore
break
case .failure(let error):
DispatchQueue.main.async {
self.showErrorAlert(error: error, localizedFailure: String(format: NSLocalizedString("Could not install app to %@.", comment: ""), device.name))
}
}
}
func install()
{
ALTDeviceManager.shared.installApplication(at: url, to: device, appleID: username, password: password, completion: finish(_:))
}
AnisetteDataManager.shared.isXPCAvailable { isAvailable in
if isAvailable
{
// XPC service is available, so we don't need to install/update Mail plug-in.
// Users can still manually do so from the AltServer menu.
install()
}
else
{
self.pluginManager.isUpdateAvailable { result in
switch result
{
case .failure(let error): finish(.failure(error))
case .success(let isUpdateAvailable):
self.isAltPluginUpdateAvailable = isUpdateAvailable
if !self.pluginManager.isMailPluginInstalled || isUpdateAvailable
{
self.installMailPlugin { result in
switch result
{
case .failure: break
case .success: install()
}
}
}
else
{
install()
}
}
}
}
}
}
func showErrorAlert(error: Error, localizedFailure: String)
{
let nsError = error as NSError
let alert = NSAlert()
alert.alertStyle = .critical
alert.messageText = localizedFailure
var messageComponents = [String]()
let separator: String
switch error
{
case ALTServerError.maximumFreeAppLimitReached: separator = "\n\n"
default: separator = " "
}
if let errorFailure = nsError.localizedFailure
{
if let debugDescription = nsError.localizedDebugDescription
{
alert.messageText = errorFailure
messageComponents.append(debugDescription)
}
else if let failureReason = nsError.localizedFailureReason
{
if nsError.localizedDescription.starts(with: errorFailure)
{
alert.messageText = errorFailure
messageComponents.append(failureReason)
}
else
{
alert.messageText = errorFailure
messageComponents.append(nsError.localizedDescription)
}
}
else
{
// No failure reason given.
if nsError.localizedDescription.starts(with: errorFailure)
{
// No need to duplicate errorFailure in both title and message.
alert.messageText = localizedFailure
messageComponents.append(nsError.localizedDescription)
}
else
{
alert.messageText = errorFailure
messageComponents.append(nsError.localizedDescription)
}
}
}
else
{
alert.messageText = localizedFailure
if let debugDescription = nsError.localizedDebugDescription
{
messageComponents.append(debugDescription)
}
else
{
messageComponents.append(nsError.localizedDescription)
}
}
if let recoverySuggestion = nsError.localizedRecoverySuggestion
{
messageComponents.append(recoverySuggestion)
}
let informativeText = messageComponents.joined(separator: separator)
alert.informativeText = informativeText
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
alert.runModal()
}
@objc func toggleLaunchAtLogin(_ item: NSMenuItem)
{
LaunchAtLogin.isEnabled.toggle()
}
@objc func handleInstallMailPluginMenuItem(_ item: NSMenuItem)
{
if !self.pluginManager.isMailPluginInstalled || self.isAltPluginUpdateAvailable
{
self.installMailPlugin()
}
else
{
self.uninstallMailPlugin()
}
}
private func installMailPlugin(completion: ((Result<Void, Error>) -> Void)? = nil)
{
self.pluginManager.installMailPlugin { (result) in
DispatchQueue.main.async {
switch result
{
case .failure(PluginError.cancelled): break
case .failure(let error):
let alert = NSAlert()
alert.messageText = NSLocalizedString("Failed to Install Mail Plug-in", comment: "")
alert.informativeText = error.localizedDescription
alert.runModal()
case .success:
let alert = NSAlert()
alert.messageText = NSLocalizedString("Mail Plug-in Installed", comment: "")
alert.informativeText = NSLocalizedString("Please restart Mail and enable AltPlugin in Mail's Preferences. Mail must be running when installing or refreshing apps with AltServer.", comment: "")
alert.runModal()
self.isAltPluginUpdateAvailable = false
}
completion?(result)
}
}
}
private func uninstallMailPlugin()
{
self.pluginManager.uninstallMailPlugin { (result) in
DispatchQueue.main.async {
switch result
{
case .failure(PluginError.cancelled): break
case .failure(let error):
let alert = NSAlert()
alert.messageText = NSLocalizedString("Failed to Uninstall Mail Plug-in", comment: "")
alert.informativeText = error.localizedDescription
alert.runModal()
case .success:
let alert = NSAlert()
alert.messageText = NSLocalizedString("Mail Plug-in Uninstalled", comment: "")
alert.informativeText = NSLocalizedString("Please restart Mail for changes to take effect. You will not be able to use AltServer until the plug-in is reinstalled.", comment: "")
alert.runModal()
}
}
}
}
}
extension AppDelegate: NSMenuDelegate
{
func menuWillOpen(_ menu: NSMenu)
{
guard menu == self.appMenu else { return }
// Clear any cached _jitAppListMenuControllers.
self._jitAppListMenuControllers.removeAll()
self.connectedDevices = ALTDeviceManager.shared.availableDevices
self.connectedDevicesMenuController.items = self.connectedDevices
self.sideloadIPAConnectedDevicesMenuController.items = self.connectedDevices
self.enableJITMenuController.items = self.connectedDevices
self.launchAtLoginMenuItem.target = self
self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:))
self.launchAtLoginMenuItem.state = LaunchAtLogin.isEnabled ? .on : .off
if self.isAltPluginUpdateAvailable
{
self.installMailPluginMenuItem.title = NSLocalizedString("Update Mail Plug-in…", comment: "")
}
else if self.pluginManager.isMailPluginInstalled
{
self.installMailPluginMenuItem.title = NSLocalizedString("Uninstall Mail Plug-in…", comment: "")
}
else
{
self.installMailPluginMenuItem.title = NSLocalizedString("Install Mail Plug-in…", comment: "")
}
self.installMailPluginMenuItem.target = self
self.installMailPluginMenuItem.action = #selector(AppDelegate.handleInstallMailPluginMenuItem(_:))
// Need to re-set this every time menu appears so we can refresh device app list.
self.enableJITMenuController.submenuHandler = { [weak self] device in
let submenu = NSMenu(title: NSLocalizedString("Sideloaded Apps", comment: ""))
guard let `self` = self else { return submenu }
let submenuController = MenuController<InstalledApp>(menu: submenu, items: [])
submenuController.placeholder = NSLocalizedString("Loading...", comment: "")
submenuController.action = { [weak self] (appInfo) in
self?.enableJIT(for: appInfo, on: device)
}
// Keep strong reference
self._jitAppListMenuControllers.append(submenuController)
ALTDeviceManager.shared.fetchInstalledApps(on: device) { (installedApps, error) in
DispatchQueue.main.async {
guard let installedApps = installedApps else {
print("Failed to fetch installed apps from \(device).", error!)
submenuController.placeholder = error?.localizedDescription
return
}
print("Fetched \(installedApps.count) apps for \(device).")
let sortedApps = installedApps.sorted { (app1, app2) in
if app1.name == app2.name
{
return app1.bundleIdentifier < app2.bundleIdentifier
}
else
{
return app1.name < app2.name
}
}
submenuController.items = sortedApps
if submenuController.items.isEmpty
{
submenuController.placeholder = NSLocalizedString("No Sideloaded Apps", comment: "")
}
}
}
return submenu
}
}
func menuDidClose(_ menu: NSMenu)
{
guard menu == self.appMenu else { return }
// Clearing _jitAppListMenuControllers now prevents action handler from being called.
// self._jitAppListMenuControllers = []
// Set `submenuHandler` to nil to prevent prematurely fetching installed apps in menuWillOpen(_:)
// when assigning self.connectedDevices to `items` (which implicitly calls `submenuHandler`)
self.enableJITMenuController.submenuHandler = nil
}
func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?)
{
guard menu == self.appMenu else { return }
// The submenu won't update correctly if the user holds/releases
// the Option key while the submenu is visible.
// Workaround: temporarily set submenu to nil to dismiss it,
// which will then cause the correct submenu to appear.
let previousItem: NSMenuItem
switch item
{
case self.sideloadAppMenuItem: previousItem = self.installAltStoreMenuItem
case self.installAltStoreMenuItem: previousItem = self.sideloadAppMenuItem
default: return
}
let submenu = previousItem.submenu
previousItem.submenu = nil
previousItem.submenu = submenu
}
}
extension AppDelegate: NSTextFieldDelegate
{
func controlTextDidChange(_ obj: Notification)
{
self.validate()
}
func controlTextDidEndEditing(_ obj: Notification)
{
self.validate()
}
private func validate()
{
guard
let appleID = self.authenticationAppleIDTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines),
let password = self.authenticationPasswordTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
else { return }
if appleID.isEmpty || password.isEmpty
{
self.authenticationAlert?.buttons.first?.isEnabled = false
}
else
{
self.authenticationAlert?.buttons.first?.isEnabled = true
}
self.authenticationAlert?.layout()
}
}
extension AppDelegate: UNUserNotificationCenterDelegate
{
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
{
completionHandler([.alert, .sound, .badge])
}
}
// MARK: - Sparkle
//extension AppDelegate: SPUUpdaterDelegate {
//
//}
//
//extension AppDelegate: SPUStandardUserDriverDelegate {
//
//}

View File

@@ -1,68 +0,0 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "Icon@16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "Icon@32-1.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "Icon@32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "Icon@64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "Icon@128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "Icon@256-1.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "Icon@256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "Icon@512-1.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "Icon@512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "Icon@1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -1,25 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "MenuBar@19.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MenuBar@38.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,390 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21179.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21179.7"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="4" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="urc-xw-Dhc">
<rect key="frame" x="0.0" y="0.0" width="300" height="46"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zLd-d8-ghZ">
<rect key="frame" x="0.0" y="25" width="300" height="21"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Apple ID" drawsBackground="YES" id="BXa-Re-rs3">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="QtW-r2-Vuh"/>
<outlet property="nextKeyView" destination="9rp-Vx-rvB" id="bQY-qj-Sej"/>
</connections>
</textField>
<secureTextField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9rp-Vx-rvB">
<rect key="frame" x="0.0" y="0.0" width="300" height="21"/>
<secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Password" drawsBackground="YES" usesSingleLineMode="YES" id="xqJ-wt-DlP">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</secureTextFieldCell>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="qav-xj-izy"/>
</connections>
</secureTextField>
</subviews>
<constraints>
<constraint firstItem="9rp-Vx-rvB" firstAttribute="width" secondItem="urc-xw-Dhc" secondAttribute="width" id="Eht-pU-Gyh"/>
<constraint firstItem="zLd-d8-ghZ" firstAttribute="width" secondItem="urc-xw-Dhc" secondAttribute="width" id="mg7-Kq-abL"/>
<constraint firstAttribute="width" constant="300" id="zqf-x6-BET"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="AltServer" customModuleProvider="target">
<connections>
<outlet property="appMenu" destination="uQy-DD-JDr" id="7cY-Ov-AOW"/>
<outlet property="authenticationAppleIDTextField" destination="zLd-d8-ghZ" id="wW5-0J-zdq"/>
<outlet property="authenticationPasswordTextField" destination="9rp-Vx-rvB" id="ZoC-DI-jzQ"/>
<outlet property="checkForUpdatesMenuItem" destination="Tnq-gD-Eic" id="wIe-Tx-KlM"/>
<outlet property="connectedDevicesMenu" destination="KJ9-WY-pW1" id="Mcv-64-iFU"/>
<outlet property="enableJITMenu" destination="la4-Sa-L3C" id="YrW-hR-uA7"/>
<outlet property="installAltStoreMenuItem" destination="MJ8-Lt-SSV" id="KYp-c5-8Ru"/>
<outlet property="installMailPluginMenuItem" destination="3CM-gV-X2G" id="lio-ha-z0S"/>
<outlet property="launchAtLoginMenuItem" destination="IyR-FQ-upe" id="Fxn-EP-hwH"/>
<outlet property="sideloadAppMenuItem" destination="x0e-zI-0A2" id="GJo-FY-1GO"/>
<outlet property="sideloadIPAConnectedDevicesMenu" destination="IuI-bV-fTY" id="QQw-St-HfG"/>
<outlet property="updaterController" destination="9QV-ha-p2H" id="oSL-jR-IAQ"/>
</connections>
</customObject>
<customObject id="9QV-ha-p2H" customClass="SPUStandardUpdaterController"/>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="AltServer" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="AltServer" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About AltServer" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Install AltStore…" id="MJ8-Lt-SSV">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Install AltStore…" systemMenu="recentDocuments" id="KJ9-WY-pW1">
<items>
<menuItem title="No Connected Devices" id="N5N-3K-XuR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="DKG-yI-Ujv"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Sideload .ipa…" id="x0e-zI-0A2" userLabel="Install .ipa">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Sideload .ipa…" systemMenu="recentDocuments" id="IuI-bV-fTY">
<items>
<menuItem title="No Connected Devices" id="in5-an-MD0">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="aUE-On-axK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Enable JIT" id="TvL-8M-oPZ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Enable JIT" systemMenu="recentDocuments" id="la4-Sa-L3C">
<items>
<menuItem title="No Connected Devices" id="Aya-FP-EzE">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="1ZZ-BB-xHy"/>
<menuItem title="Launch at Login" id="IyR-FQ-upe" userLabel="Launch At Login">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Install Mail Plug-in…" id="3CM-gV-X2G" userLabel="Mail Plug-in">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="mVM-Nm-Zi9"/>
<menuItem title="Check for Updates..." id="Tnq-gD-Eic" userLabel="Check for Updates">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="checkForUpdates:" target="9QV-ha-p2H" id="9fB-b9-def"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="hmG-xg-qgm"/>
<menuItem title="Quit AltServer" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="AltServer Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
</scene>
</scenes>
</document>

View File

@@ -1,25 +0,0 @@
//
// NSError+libimobiledevice.h
// AltServer
//
// Created by Riley Testut on 3/23/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
#import <libimobiledevice/mobile_image_mounter.h>
#import <libimobiledevice/debugserver.h>
#import <libimobiledevice/installation_proxy.h>
#import <AltSign/ALTDevice.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSError (libimobiledevice)
+ (nullable instancetype)errorWithMobileImageMounterError:(mobile_image_mounter_error_t)error device:(nullable ALTDevice *)device;
+ (nullable instancetype)errorWithDebugServerError:(debugserver_error_t)error device:(nullable ALTDevice *)device;
+ (nullable instancetype)errorWithInstallationProxyError:(instproxy_error_t)error device:(nullable ALTDevice *)device;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,86 +0,0 @@
//
// NSError+libimobiledevice.m
// AltServer
//
// Created by Riley Testut on 3/23/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
#import "NSError+libimobiledevice.h"
#import "NSError+ALTServerError.h"
@implementation NSError (libimobiledevice)
+ (nullable instancetype)errorWithMobileImageMounterError:(mobile_image_mounter_error_t)error device:(nullable ALTDevice *)device
{
NSMutableDictionary *userInfo = [@{
ALTUnderlyingErrorDomainErrorKey: @"Mobile Image Mounter",
ALTUnderlyingErrorCodeErrorKey: [@(error) description],
} mutableCopy];
if (device)
{
userInfo[ALTDeviceNameErrorKey] = device.name;
}
switch (error)
{
case MOBILE_IMAGE_MOUNTER_E_SUCCESS: return nil;
case MOBILE_IMAGE_MOUNTER_E_INVALID_ARG: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo];
case MOBILE_IMAGE_MOUNTER_E_PLIST_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidResponse userInfo:userInfo];
case MOBILE_IMAGE_MOUNTER_E_CONN_FAILED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUsbmuxd userInfo:userInfo];
case MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo];
case MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorDeviceLocked userInfo:userInfo];
case MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:userInfo];
}
}
+ (nullable instancetype)errorWithDebugServerError:(debugserver_error_t)error device:(nullable ALTDevice *)device
{
NSMutableDictionary *userInfo = [@{
ALTUnderlyingErrorDomainErrorKey: @"Debug Server",
ALTUnderlyingErrorCodeErrorKey: [@(error) description],
} mutableCopy];
if (device)
{
userInfo[ALTDeviceNameErrorKey] = device.name;
}
switch (error)
{
case DEBUGSERVER_E_SUCCESS: return nil;
case DEBUGSERVER_E_INVALID_ARG: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo];
case DEBUGSERVER_E_MUX_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUsbmuxd userInfo:userInfo];
case DEBUGSERVER_E_SSL_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorSSL userInfo:userInfo];
case DEBUGSERVER_E_RESPONSE_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidResponse userInfo:userInfo];
case DEBUGSERVER_E_TIMEOUT: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorTimedOut userInfo:userInfo];
case DEBUGSERVER_E_UNKNOWN_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:userInfo];
}
}
+ (nullable instancetype)errorWithInstallationProxyError:(instproxy_error_t)error device:(nullable ALTDevice *)device
{
NSMutableDictionary *userInfo = [@{
ALTUnderlyingErrorDomainErrorKey: @"Installation Proxy",
ALTUnderlyingErrorCodeErrorKey: [@(error) description],
} mutableCopy];
if (device)
{
userInfo[ALTDeviceNameErrorKey] = device.name;
}
switch (error)
{
case INSTPROXY_E_SUCCESS: return nil;
case INSTPROXY_E_INVALID_ARG: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo];
case INSTPROXY_E_PLIST_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidResponse userInfo:userInfo];
case INSTPROXY_E_CONN_FAILED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUsbmuxd userInfo:userInfo];
case INSTPROXY_E_RECEIVE_TIMEOUT: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorTimedOut userInfo:userInfo];
// case INSTPROXY_E_DEVICE_OS_VERSION_TOO_LOW: return [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnsupportediOSVersion userInfo:nil]; // Error message assumes we're installing AltStore
default: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:userInfo];
}
}
@end

View File

@@ -1,28 +0,0 @@
//
// ALTDebugConnection+Private.h
// AltServer
//
// Created by Riley Testut on 2/19/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
#import "ALTDebugConnection.h"
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/debugserver.h>
NS_ASSUME_NONNULL_BEGIN
@interface ALTDebugConnection ()
@property (nonatomic, readonly) dispatch_queue_t connectionQueue;
@property (nonatomic, nullable) debugserver_client_t client;
- (instancetype)initWithDevice:(ALTDevice *)device;
- (void)connectWithCompletionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,25 +0,0 @@
//
// ALTDebugConnection.h
// AltServer
//
// Created by Riley Testut on 2/19/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
#import "AltSign.h"
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(DebugConnection)
@interface ALTDebugConnection : NSObject
@property (nonatomic, copy, readonly) ALTDevice *device;
- (void)enableUnsignedCodeExecutionForProcessWithName:(NSString *)processName completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)enableUnsignedCodeExecutionForProcessWithID:(NSInteger)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)disconnect;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,315 +0,0 @@
//
// ALTDebugConnection.m
// AltServer
//
// Created by Riley Testut on 2/19/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
#import "ALTDebugConnection+Private.h"
#import "NSError+ALTServerError.h"
#import "NSError+libimobiledevice.h"
char *bin2hex(const unsigned char *bin, size_t length)
{
if (bin == NULL || length == 0)
{
return NULL;
}
char *hex = (char *)malloc(length * 2 + 1);
for (size_t i = 0; i < length; i++)
{
hex[i * 2] = "0123456789ABCDEF"[bin[i] >> 4];
hex[i * 2 + 1] = "0123456789ABCDEF"[bin[i] & 0x0F];
}
hex[length * 2] = '\0';
return hex;
}
@implementation ALTDebugConnection
- (instancetype)initWithDevice:(ALTDevice *)device
{
self = [super init];
if (self)
{
_device = device;
_connectionQueue = dispatch_queue_create_with_target("io.altstore.AltServer.DebugConnection",
DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL,
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0));
}
return self;
}
- (void)dealloc
{
[self disconnect];
}
- (void)disconnect
{
if (_client == nil)
{
return;
}
debugserver_client_free(_client);
_client = nil;
}
- (void)connectWithCompletionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler
{
__block idevice_t device = NULL;
void (^finish)(BOOL, NSError *) = ^(BOOL success, NSError *error) {
if (device)
{
idevice_free(device);
}
completionHandler(success, error);
};
dispatch_async(self.connectionQueue, ^{
/* Find Device */
if (idevice_new_with_options(&device, self.device.identifier.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS)
{
return finish(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]);
}
/* Connect to debugserver */
debugserver_client_t client = NULL;
debugserver_error_t error = debugserver_client_start_service(device, &client, "AltServer");
if (error != DEBUGSERVER_E_SUCCESS)
{
return finish(NO, [NSError errorWithDebugServerError:error device:self.device]);
}
self.client = client;
finish(YES, nil);
});
}
- (void)enableUnsignedCodeExecutionForProcessWithName:(NSString *)processName completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler
{
[self _enableUnsignedCodeExecutionForProcessWithName:processName pid:0 completionHandler:completionHandler];
}
- (void)enableUnsignedCodeExecutionForProcessWithID:(NSInteger)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler
{
[self _enableUnsignedCodeExecutionForProcessWithName:nil pid:(int32_t)pid completionHandler:completionHandler];
}
- (void)_enableUnsignedCodeExecutionForProcessWithName:(nullable NSString *)processName pid:(int32_t)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler
{
dispatch_async(self.connectionQueue, ^{
NSString *name = processName ?: NSLocalizedString(@"this app", @"");
NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"JIT could not be enabled for %@.", comment: @""), name];
NSString *attachCommand = nil;
if (processName)
{
NSString *encodedName = @(bin2hex((const unsigned char *)processName.UTF8String, (size_t)strlen(processName.UTF8String)));
attachCommand = [NSString stringWithFormat:@"vAttachOrWait;%@", encodedName];
}
else
{
// Convert to Big-endian.
int32_t rawPID = CFSwapInt32HostToBig(pid);
NSString *encodedName = @(bin2hex((const unsigned char *)&rawPID, 4));
attachCommand = [NSString stringWithFormat:@"vAttach;%@", encodedName];
}
NSError *error = nil;
if (![self sendCommand:attachCommand arguments:nil error:&error])
{
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
userInfo[ALTAppNameErrorKey] = processName;
userInfo[ALTDeviceNameErrorKey] = self.device.name;
userInfo[NSLocalizedFailureErrorKey] = localizedFailure;
NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
return completionHandler(NO, returnError);
}
NSString *detachCommand = @"D";
if (![self sendCommand:detachCommand arguments:nil error:&error])
{
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
userInfo[ALTAppNameErrorKey] = processName;
userInfo[ALTDeviceNameErrorKey] = self.device.name;
userInfo[NSLocalizedFailureErrorKey] = localizedFailure;
NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
return completionHandler(NO, returnError);
}
completionHandler(YES, nil);
});
}
#pragma mark - Private -
- (BOOL)sendCommand:(NSString *)command arguments:(nullable NSArray<NSString *> *)arguments error:(NSError **)error
{
int argc = (int)arguments.count;
char **argv = new char*[argc + 1];
for (int i = 0; i < argc; i++)
{
NSString *argument = arguments[i];
argv[i] = (char *)argument.UTF8String;
}
argv[argc] = NULL;
debugserver_command_t debugCommand = NULL;
debugserver_command_new(command.UTF8String, argc, argv, &debugCommand);
delete[] argv;
char *response = NULL;
size_t responseSize = 0;
debugserver_error_t debugServerError = debugserver_client_send_command(self.client, debugCommand, &response, &responseSize);
debugserver_command_free(debugCommand);
if (debugServerError != DEBUGSERVER_E_SUCCESS)
{
if (error)
{
*error = [NSError errorWithDebugServerError:debugServerError device:self.device];
}
return NO;
}
NSString *rawResponse = (response != nil) ? @(response) : nil;
if (![self processResponse:rawResponse error:error])
{
return NO;
}
return YES;
}
- (BOOL)processResponse:(NSString *)rawResponse error:(NSError **)error
{
if (rawResponse == nil)
{
if (error)
{
*error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil];
return NO;
}
}
if (rawResponse.length == 0 || [rawResponse isEqualToString:@"OK"])
{
return YES;
}
char type = [rawResponse characterAtIndex:0];
NSString *response = [rawResponse substringFromIndex:1];
switch (type)
{
case 'O':
{
// stdout/stderr
char *decodedResponse = NULL;
debugserver_decode_string(response.UTF8String, response.length, &decodedResponse);
NSLog(@"Response: %@", @(decodedResponse));
if (decodedResponse)
{
free(decodedResponse);
}
return YES;
}
case 'T':
{
// Thread Information
NSLog(@"Thread stopped. Details:\n%s", response.UTF8String + 1);
// Parse thread state to determine if app is running.
NSArray<NSString *> *components = [response componentsSeparatedByString:@";"];
if (components.count <= 1)
{
if (error)
{
*error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}];
}
return NO;
}
NSString *mainThread = components[1];
NSString *threadState = [[mainThread componentsSeparatedByString:@":"] lastObject];
NSScanner *scanner = [NSScanner scannerWithString:threadState];
unsigned long long mainThreadState = 0;
[scanner scanHexLongLong:&mainThreadState];
// If main thread state == 0, app is not running.
if (mainThreadState == 0)
{
if (error)
{
*error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil];
}
return NO;
}
return YES;
}
case 'E':
{
// Error
if (error)
{
NSInteger errorCode = [[[response componentsSeparatedByString:@";"] firstObject] integerValue];
switch (errorCode)
{
case 96:
*error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil];
break;
default:
*error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}];
break;
}
}
return NO;
}
case 'W':
{
// Warning
NSLog(@"WARNING: %@", response);
return YES;
}
}
return YES;
}
@end

View File

@@ -1,24 +0,0 @@
//
// ALTNotificationConnection+Private.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTNotificationConnection.h"
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/notification_proxy.h>
NS_ASSUME_NONNULL_BEGIN
@interface ALTNotificationConnection ()
@property (nonatomic, readonly) np_client_t client;
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,30 +0,0 @@
//
// ALTNotificationConnection.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "AltSign.h"
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(NotificationConnection)
@interface ALTNotificationConnection : NSObject
@property (nonatomic, copy, readonly) ALTDevice *device;
@property (nonatomic, copy, nullable) void (^receivedNotificationHandler)(CFNotificationName notification);
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)sendNotification:(CFNotificationName)notification
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)disconnect;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,94 +0,0 @@
//
// ALTNotificationConnection.m
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTNotificationConnection+Private.h"
#import "NSError+ALTServerError.h"
void ALTDeviceReceivedNotification(const char *notification, void *user_data);
@implementation ALTNotificationConnection
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client
{
self = [super init];
if (self)
{
_device = [device copy];
_client = client;
}
return self;
}
- (void)dealloc
{
[self disconnect];
}
- (void)disconnect
{
np_client_free(self.client);
_client = nil;
}
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
const char **notificationNames = (const char **)malloc((notifications.count + 1) * sizeof(char *));
for (int i = 0; i < notifications.count; i++)
{
NSString *name = notifications[i];
notificationNames[i] = name.UTF8String;
}
notificationNames[notifications.count] = NULL; // Must have terminating NULL entry.
np_error_t result = np_observe_notifications(self.client, notificationNames);
if (result != NP_E_SUCCESS)
{
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
result = np_set_notify_callback(self.client, ALTDeviceReceivedNotification, (__bridge void *)self);
if (result != NP_E_SUCCESS)
{
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
completionHandler(YES, nil);
free(notificationNames);
});
}
- (void)sendNotification:(CFNotificationName)notification completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
np_error_t result = np_post_notification(self.client, [(__bridge NSString *)notification UTF8String]);
if (result == NP_E_SUCCESS)
{
completionHandler(YES, nil);
}
else
{
completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
});
}
@end
void ALTDeviceReceivedNotification(const char *notification, void *user_data)
{
ALTNotificationConnection *connection = (__bridge ALTNotificationConnection *)user_data;
if (connection.receivedNotificationHandler)
{
connection.receivedNotificationHandler((__bridge CFNotificationName)@(notification));
}
}

View File

@@ -1,25 +0,0 @@
//
// ALTWiredConnection+Private.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTWiredConnection.h"
#include <libimobiledevice/libimobiledevice.h>
NS_ASSUME_NONNULL_BEGIN
@interface ALTWiredConnection ()
@property (nonatomic, readwrite, getter=isConnected) BOOL connected;
@property (nonatomic, readonly) idevice_connection_t connection;
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,29 +0,0 @@
//
// ALTWiredConnection.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "AltSign.h"
#import "ALTConnection.h"
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(WiredConnection)
@interface ALTWiredConnection : NSObject <ALTConnection>
@property (nonatomic, readonly, getter=isConnected) BOOL connected;
@property (nonatomic, copy, readonly) ALTDevice *device;
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler;
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler;
- (void)disconnect;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,119 +0,0 @@
//
// ALTWiredConnection.m
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTWiredConnection+Private.h"
#import "ALTConnection.h"
#import "NSError+ALTServerError.h"
@implementation ALTWiredConnection
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection
{
self = [super init];
if (self)
{
_device = [device copy];
_connection = connection;
self.connected = YES;
}
return self;
}
- (void)dealloc
{
[self disconnect];
}
- (void)disconnect
{
if (![self isConnected])
{
return;
}
idevice_disconnect(self.connection);
_connection = nil;
self.connected = NO;
}
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
void (^finish)(NSError *error) = ^(NSError *error) {
if (error != nil)
{
NSLog(@"Send Error: %@", error);
completionHandler(NO, error);
}
else
{
completionHandler(YES, nil);
}
};
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
NSMutableData *mutableData = [data mutableCopy];
while (mutableData.length > 0)
{
uint32_t sentBytes = 0;
if (idevice_connection_send(self.connection, (const char *)mutableData.bytes, (int32_t)mutableData.length, &sentBytes) != IDEVICE_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
[mutableData replaceBytesInRange:NSMakeRange(0, sentBytes) withBytes:NULL length:0];
}
finish(nil);
});
}
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler
{
void (^finish)(NSData *data, NSError *error) = ^(NSData *data, NSError *error) {
if (error != nil)
{
NSLog(@"Receive Data Error: %@", error);
}
completionHandler(data, error);
};
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
char bytes[4096];
NSMutableData *receivedData = [NSMutableData dataWithCapacity:expectedSize];
while (receivedData.length < expectedSize)
{
uint32_t size = MIN(4096, (uint32_t)expectedSize - (uint32_t)receivedData.length);
uint32_t receivedBytes = 0;
if (idevice_connection_receive_timeout(self.connection, bytes, size, &receivedBytes, 10000) != IDEVICE_E_SUCCESS)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
NSData *data = [NSData dataWithBytesNoCopy:bytes length:receivedBytes freeWhenDone:NO];
[receivedData appendData:data];
}
finish(receivedData, nil);
});
}
#pragma mark - NSObject -
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ (Wired)", self.device.name];
}
@end

View File

@@ -1,263 +0,0 @@
//
// RequestHandler.swift
// AltServer
//
// Created by Riley Testut on 5/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
typealias ServerConnectionManager = ConnectionManager<ServerRequestHandler>
private let connectionManager = ConnectionManager(requestHandler: ServerRequestHandler(),
connectionHandlers: [WirelessConnectionHandler(), WiredConnectionHandler()])
extension ServerConnectionManager
{
static var shared: ConnectionManager {
return connectionManager
}
}
struct ServerRequestHandler: RequestHandler
{
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result<AnisetteDataResponse, Error>) -> Void)
{
AnisetteDataManager.shared.requestAnisetteData { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let anisetteData):
let response = AnisetteDataResponse(anisetteData: anisetteData)
completionHandler(.success(response))
}
}
}
func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result<InstallationProgressResponse, Error>) -> Void)
{
var temporaryURL: URL?
func finish(_ result: Result<InstallationProgressResponse, Error>)
{
if let temporaryURL = temporaryURL
{
do { try FileManager.default.removeItem(at: temporaryURL) }
catch { print("Failed to remove .ipa.", error) }
}
completionHandler(result)
}
self.receiveApp(for: request, from: connection) { (result) in
print("Received app with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success(let fileURL):
temporaryURL = fileURL
print("Awaiting begin installation request...")
connection.receiveRequest() { (result) in
print("Received begin installation request with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success(.beginInstallation(let installRequest)):
print("Installing app to device \(request.udid)...")
self.installApp(at: fileURL, toDeviceWithUDID: request.udid, activeProvisioningProfiles: installRequest.activeProfiles, connection: connection) { (result) in
print("Installed app to device with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success:
let response = InstallationProgressResponse(progress: 1.0)
finish(.success(response))
}
}
case .success: finish(.failure(ALTServerError(.unknownRequest)))
}
}
}
}
}
func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
completionHandler: @escaping (Result<InstallProvisioningProfilesResponse, Error>) -> Void)
{
ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles) { (success, error) in
if let error = error, !success
{
print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
completionHandler(.failure(ALTServerError(error)))
}
else
{
print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
let response = InstallProvisioningProfilesResponse()
completionHandler(.success(response))
}
}
}
func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
completionHandler: @escaping (Result<RemoveProvisioningProfilesResponse, Error>) -> Void)
{
ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (success, error) in
if let error = error, !success
{
print("Failed to remove profiles \(request.bundleIdentifiers):", error)
completionHandler(.failure(ALTServerError(error)))
}
else
{
print("Removed profiles:", request.bundleIdentifiers)
let response = RemoveProvisioningProfilesResponse()
completionHandler(.success(response))
}
}
}
func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result<RemoveAppResponse, Error>) -> Void)
{
ALTDeviceManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier, fromDeviceWithUDID: request.udid) { (success, error) in
if let error = error, !success
{
print("Failed to remove app \(request.bundleIdentifier):", error)
completionHandler(.failure(ALTServerError(error)))
}
else
{
print("Removed app:", request.bundleIdentifier)
let response = RemoveAppResponse()
completionHandler(.success(response))
}
}
}
func handleEnableUnsignedCodeExecutionRequest(_ request: EnableUnsignedCodeExecutionRequest, for connection: Connection, completionHandler: @escaping (Result<EnableUnsignedCodeExecutionResponse, Error>) -> Void)
{
guard let device = ALTDeviceManager.shared.availableDevices.first(where: { $0.identifier == request.udid }) else { return completionHandler(.failure(ALTServerError(.deviceNotFound))) }
ALTDeviceManager.shared.prepare(device) { result in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success:
ALTDeviceManager.shared.startDebugConnection(to: device) { (connection, error) in
guard let connection = connection else { return completionHandler(.failure(error!)) }
func finish(success: Bool, error: Error?)
{
if let error = error, !success
{
print("Failed to enable unsigned code execution for process \(request.processID?.description ?? request.processName ?? "nil"):", error)
completionHandler(.failure(ALTServerError(error)))
}
else
{
print("Enabled unsigned code execution for process:", request.processID ?? request.processName ?? "nil")
let response = EnableUnsignedCodeExecutionResponse()
completionHandler(.success(response))
}
}
if let processID = request.processID
{
connection.enableUnsignedCodeExecutionForProcess(withID: processID, completionHandler: finish)
}
else if let processName = request.processName
{
connection.enableUnsignedCodeExecutionForProcess(withName: processName, completionHandler: finish)
}
else
{
finish(success: false, error: ALTServerError(.invalidRequest))
}
}
}
}
}
}
private extension RequestHandler
{
func receiveApp(for request: PrepareAppRequest, from connection: Connection, completionHandler: @escaping (Result<URL, ALTServerError>) -> Void)
{
connection.receiveData(expectedSize: request.contentSize) { (result) in
do
{
print("Received app data!")
let data = try result.get()
guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) }
print("Writing app data...")
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa")
try data.write(to: temporaryURL, options: .atomic)
print("Wrote app to URL:", temporaryURL)
completionHandler(.success(temporaryURL))
}
catch
{
print("Error processing app data:", error)
completionHandler(.failure(ALTServerError(error)))
}
}
}
func installApp(at fileURL: URL, toDeviceWithUDID udid: String, activeProvisioningProfiles: Set<String>?, connection: Connection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
{
let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default)
var isSending = false
var observation: NSKeyValueObservation?
let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in
print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription)
if let error = error.map({ ALTServerError($0) })
{
completionHandler(.failure(error))
}
else
{
completionHandler(.success(()))
}
observation?.invalidate()
observation = nil
}
observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in
serialQueue.async {
guard !isSending else { return }
isSending = true
print("Progress:", progress.fractionCompleted)
let response = InstallationProgressResponse(progress: progress.fractionCompleted)
connection.send(response) { (result) in
serialQueue.async {
isSending = false
}
}
}
})
}
}

View File

@@ -1,122 +0,0 @@
//
// WiredConnectionHandler.swift
// AltServer
//
// Created by Riley Testut on 6/1/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
class WiredConnectionHandler: ConnectionHandler
{
var connectionHandler: ((Connection) -> Void)?
var disconnectionHandler: ((Connection) -> Void)?
private var notificationConnections = [ALTDevice: NotificationConnection]()
private let queue = DispatchQueue(label: "WiredConnectionHandler", autoreleaseFrequency: .workItem, target: .global(qos: .utility))
func startListening()
{
NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil)
}
func stopListening()
{
NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidConnect, object: nil)
NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidDisconnect, object: nil)
}
}
private extension WiredConnectionHandler
{
func startNotificationConnection(to device: ALTDevice)
{
self.queue.async {
ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in
guard let connection = connection else { return }
let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest]
connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in
guard success else { return }
self.queue.async {
connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in
guard let self = self, let connection = connection else { return }
self.handle(notification, for: connection)
}
self.notificationConnections[device] = connection
}
}
}
}
}
func stopNotificationConnection(to device: ALTDevice)
{
self.queue.async {
guard let connection = self.notificationConnections[device] else { return }
connection.disconnect()
self.notificationConnections[device] = nil
}
}
func handle(_ notification: CFNotificationName, for connection: NotificationConnection)
{
switch notification
{
case .wiredServerConnectionAvailableRequest:
connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in
if let error = error, !success
{
print("Error sending wired server connection response.", error)
}
else
{
print("Sent wired server connection available response!")
}
}
case .wiredServerConnectionStartRequest:
ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in
if let wiredConnection = wiredConnection
{
print("Started wired server connection!")
self.connectionHandler?(wiredConnection)
var observation: NSKeyValueObservation?
observation = wiredConnection.observe(\.isConnected) { [weak self] (connection, change) in
guard !connection.isConnected else { return }
self?.disconnectionHandler?(connection)
observation?.invalidate()
}
}
else if let error = error
{
print("Error starting wired server connection.", error)
}
}
default: break
}
}
}
private extension WiredConnectionHandler
{
@objc func deviceDidConnect(_ notification: Notification)
{
guard let device = notification.object as? ALTDevice else { return }
self.startNotificationConnection(to: device)
}
@objc func deviceDidDisconnect(_ notification: Notification)
{
guard let device = notification.object as? ALTDevice else { return }
self.stopNotificationConnection(to: device)
}
}

View File

@@ -1,153 +0,0 @@
//
// WirelessConnectionHandler.swift
// AltKit
//
// Created by Riley Testut on 6/1/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
import Network
extension WirelessConnectionHandler
{
public enum State
{
case notRunning
case connecting
case running(NWListener.Service)
case failed(Swift.Error)
}
}
public class WirelessConnectionHandler: ConnectionHandler
{
public var connectionHandler: ((Connection) -> Void)?
public var disconnectionHandler: ((Connection) -> Void)?
public var stateUpdateHandler: ((State) -> Void)?
public private(set) var state: State = .notRunning {
didSet {
self.stateUpdateHandler?(self.state)
}
}
private lazy var listener = self.makeListener()
private let dispatchQueue = DispatchQueue(label: "io.altstore.WirelessConnectionListener", qos: .utility)
public func startListening()
{
switch self.state
{
case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue)
default: break
}
}
public func stopListening()
{
switch self.state
{
case .running: self.listener.cancel()
default: break
}
}
}
private extension WirelessConnectionHandler
{
func makeListener() -> NWListener
{
let listener = try! NWListener(using: .tcp)
let service: NWListener.Service
if let serverID = UserDefaults.standard.serverID?.data(using: .utf8)
{
let txtDictionary = ["serverID": serverID]
let txtData = NetService.data(fromTXTRecord: txtDictionary)
service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData)
}
else
{
service = NWListener.Service(type: ALTServerServiceType)
}
listener.service = service
listener.serviceRegistrationUpdateHandler = { (serviceChange) in
switch serviceChange
{
case .add(.service(let name, let type, let domain, _)):
let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil)
self.state = .running(service)
default: break
}
}
listener.stateUpdateHandler = { (state) in
switch state
{
case .ready: break
case .waiting, .setup: self.state = .connecting
case .cancelled: self.state = .notRunning
case .failed(let error): self.state = .failed(error)
@unknown default: break
}
}
listener.newConnectionHandler = { [weak self] (connection) in
self?.prepare(connection)
}
return listener
}
func prepare(_ nwConnection: NWConnection)
{
print("Preparing:", nwConnection)
// Use same instance for all callbacks.
let connection = NetworkConnection(nwConnection)
nwConnection.stateUpdateHandler = { [weak self] (state) in
switch state
{
case .setup, .preparing: break
case .ready:
print("Connected to client:", connection)
self?.connectionHandler?(connection)
case .waiting:
print("Waiting for connection...")
case .failed(let error):
print("Failed to connect to service \(nwConnection.endpoint).", error)
self?.disconnect(connection)
case .cancelled:
self?.disconnect(connection)
@unknown default: break
}
}
nwConnection.start(queue: self.dispatchQueue)
}
func disconnect(_ connection: Connection)
{
connection.disconnect()
self.disconnectionHandler?(connection)
if let networkConnection = connection as? NetworkConnection
{
networkConnection.nwConnection.stateUpdateHandler = nil
}
}
}

View File

@@ -1,327 +0,0 @@
//
// DeveloperDiskManager.swift
// AltServer
//
// Created by Riley Testut on 2/19/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import Foundation
import AltSign
enum DeveloperDiskError: LocalizedError
{
case unknownDownloadURL
case unsupportedOperatingSystem
case downloadedDiskNotFound
var errorDescription: String? {
switch self
{
case .unknownDownloadURL: return NSLocalizedString("The URL to download the Developer disk image could not be determined.", comment: "")
case .unsupportedOperatingSystem: return NSLocalizedString("The device's operating system does not support installing Developer disk images.", comment: "")
case .downloadedDiskNotFound: return NSLocalizedString("DeveloperDiskImage.dmg and its signature could not be found in the downloaded archive.", comment: "")
}
}
}
private extension URL
{
#if STAGING
static let developerDiskDownloadURLs = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altserver/developerdisks.json")!
#else
static let developerDiskDownloadURLs = URL(string: "https://cdn.altstore.io/file/altstore/altserver/developerdisks.json")!
#endif
}
private extension DeveloperDiskManager
{
struct FetchURLsResponse: Decodable
{
struct Disks: Decodable
{
var iOS: [String: DeveloperDiskURL]?
var tvOS: [String: DeveloperDiskURL]?
}
var version: Int
var disks: Disks
}
enum DeveloperDiskURL: Decodable
{
case archive(URL)
case separate(diskURL: URL, signatureURL: URL)
private enum CodingKeys: CodingKey
{
case archive
case disk
case signature
}
init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.contains(.archive)
{
let archiveURL = try container.decode(URL.self, forKey: .archive)
self = .archive(archiveURL)
}
else
{
let diskURL = try container.decode(URL.self, forKey: .disk)
let signatureURL = try container.decode(URL.self, forKey: .signature)
self = .separate(diskURL: diskURL, signatureURL: signatureURL)
}
}
}
}
class DeveloperDiskManager
{
private let session = URLSession(configuration: .ephemeral)
func downloadDeveloperDisk(for device: ALTDevice, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void)
{
do
{
guard let osName = device.type.osName else { throw DeveloperDiskError.unsupportedOperatingSystem }
let osKeyPath: KeyPath<FetchURLsResponse.Disks, [String: DeveloperDiskURL]?>
switch device.type
{
case .iphone, .ipad: osKeyPath = \FetchURLsResponse.Disks.iOS
case .appletv: osKeyPath = \FetchURLsResponse.Disks.tvOS
default: throw DeveloperDiskError.unsupportedOperatingSystem
}
var osVersion = device.osVersion
osVersion.patchVersion = 0 // Patch is irrelevant for developer disks
let osDirectoryURL = FileManager.default.developerDisksDirectory.appendingPathComponent(osName)
let developerDiskDirectoryURL = osDirectoryURL.appendingPathComponent(osVersion.stringValue, isDirectory: true)
try FileManager.default.createDirectory(at: developerDiskDirectoryURL, withIntermediateDirectories: true, attributes: nil)
let developerDiskURL = developerDiskDirectoryURL.appendingPathComponent("DeveloperDiskImage.dmg")
let developerDiskSignatureURL = developerDiskDirectoryURL.appendingPathComponent("DeveloperDiskImage.dmg.signature")
let isCachedDiskCompatible = self.isDeveloperDiskCompatible(with: device)
if isCachedDiskCompatible && FileManager.default.fileExists(atPath: developerDiskURL.path) && FileManager.default.fileExists(atPath: developerDiskSignatureURL.path)
{
// The developer disk is cached and we've confirmed it works, so re-use it.
return completionHandler(.success((developerDiskURL, developerDiskSignatureURL)))
}
func finish(_ result: Result<(URL, URL), Error>)
{
do
{
let (diskFileURL, signatureFileURL) = try result.get()
if FileManager.default.fileExists(atPath: developerDiskURL.path)
{
try FileManager.default.removeItem(at: developerDiskURL)
}
if FileManager.default.fileExists(atPath: developerDiskSignatureURL.path)
{
try FileManager.default.removeItem(at: developerDiskSignatureURL)
}
try FileManager.default.copyItem(at: diskFileURL, to: developerDiskURL)
try FileManager.default.copyItem(at: signatureFileURL, to: developerDiskSignatureURL)
completionHandler(.success((developerDiskURL, developerDiskSignatureURL)))
}
catch
{
completionHandler(.failure(error))
}
}
self.fetchDeveloperDiskURLs { (result) in
do
{
let developerDiskURLs = try result.get()
guard let diskURL = developerDiskURLs[keyPath: osKeyPath]?[osVersion.stringValue] else { throw DeveloperDiskError.unknownDownloadURL }
switch diskURL
{
case .archive(let archiveURL): self.downloadDiskArchive(from: archiveURL, completionHandler: finish(_:))
case .separate(let diskURL, let signatureURL): self.downloadDisk(from: diskURL, signatureURL: signatureURL, completionHandler: finish(_:))
}
}
catch
{
finish(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
func setDeveloperDiskCompatible(_ isCompatible: Bool, with device: ALTDevice)
{
guard let id = self.developerDiskCompatibilityID(for: device) else { return }
UserDefaults.standard.set(isCompatible, forKey: id)
}
func isDeveloperDiskCompatible(with device: ALTDevice) -> Bool
{
guard let id = self.developerDiskCompatibilityID(for: device) else { return false }
let isCompatible = UserDefaults.standard.bool(forKey: id)
return isCompatible
}
}
private extension DeveloperDiskManager
{
func developerDiskCompatibilityID(for device: ALTDevice) -> String?
{
guard let osName = device.type.osName else { return nil }
var osVersion = device.osVersion
osVersion.patchVersion = 0 // Patch is irrelevant for developer disks
let id = ["ALTDeveloperDiskCompatible", osName, device.osVersion.stringValue].joined(separator: "_")
return id
}
func fetchDeveloperDiskURLs(completionHandler: @escaping (Result<FetchURLsResponse.Disks, Error>) -> Void)
{
let dataTask = self.session.dataTask(with: .developerDiskDownloadURLs) { (data, response, error) in
do
{
guard let data = data else { throw error! }
let response = try JSONDecoder().decode(FetchURLsResponse.self, from: data)
completionHandler(.success(response.disks))
}
catch
{
completionHandler(.failure(error))
}
}
dataTask.resume()
}
func downloadDiskArchive(from url: URL, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void)
{
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
do
{
guard let fileURL = fileURL else { throw error! }
defer { try? FileManager.default.removeItem(at: fileURL) }
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
defer { try? FileManager.default.removeItem(at: temporaryDirectory) }
try FileManager.default.unzipArchive(at: fileURL, toDirectory: temporaryDirectory)
guard let enumerator = FileManager.default.enumerator(at: temporaryDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) else {
throw CocoaError(.fileNoSuchFile, userInfo: [NSURLErrorKey: temporaryDirectory])
}
var tempDiskFileURL: URL?
var tempSignatureFileURL: URL?
for case let fileURL as URL in enumerator
{
switch fileURL.pathExtension.lowercased()
{
case "dmg": tempDiskFileURL = fileURL
case "signature": tempSignatureFileURL = fileURL
default: break
}
}
guard let diskFileURL = tempDiskFileURL, let signatureFileURL = tempSignatureFileURL else { throw DeveloperDiskError.downloadedDiskNotFound }
completionHandler(.success((diskFileURL, signatureFileURL)))
}
catch
{
completionHandler(.failure(error))
}
}
downloadTask.resume()
}
func downloadDisk(from diskURL: URL, signatureURL: URL, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void)
{
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
do { try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil) }
catch { return completionHandler(.failure(error)) }
var diskFileURL: URL?
var signatureFileURL: URL?
var downloadError: Error?
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
dispatchGroup.enter()
let diskDownloadTask = URLSession.shared.downloadTask(with: diskURL) { (fileURL, response, error) in
do
{
guard let fileURL = fileURL else { throw error! }
let destinationURL = temporaryDirectory.appendingPathComponent("DeveloperDiskImage.dmg")
try FileManager.default.copyItem(at: fileURL, to: destinationURL)
diskFileURL = destinationURL
}
catch
{
downloadError = error
}
dispatchGroup.leave()
}
let signatureDownloadTask = URLSession.shared.downloadTask(with: signatureURL) { (fileURL, response, error) in
do
{
guard let fileURL = fileURL else { throw error! }
let destinationURL = temporaryDirectory.appendingPathComponent("DeveloperDiskImage.dmg.signature")
try FileManager.default.copyItem(at: fileURL, to: destinationURL)
signatureFileURL = destinationURL
}
catch
{
downloadError = error
}
dispatchGroup.leave()
}
diskDownloadTask.resume()
signatureDownloadTask.resume()
dispatchGroup.notify(queue: .global(qos: .userInitiated)) {
defer {
try? FileManager.default.removeItem(at: temporaryDirectory)
}
guard let diskFileURL = diskFileURL, let signatureFileURL = signatureFileURL else {
return completionHandler(.failure(downloadError ?? DeveloperDiskError.downloadedDiskNotFound))
}
completionHandler(.success((diskFileURL, signatureFileURL)))
}
}
}

View File

@@ -1,941 +0,0 @@
//
// ALTDeviceManager+Installation.swift
// AltServer
//
// Created by Riley Testut on 7/1/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Cocoa
import UserNotifications
import ObjectiveC
private let appGroupsSemaphore = DispatchSemaphore(value: 1)
private let developerDiskManager = DeveloperDiskManager()
enum InstallError: Int, LocalizedError, _ObjectiveCBridgeableError
{
case cancelled
case noTeam
case missingPrivateKey
case missingCertificate
var errorDescription: String? {
switch self
{
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
case .noTeam: return "You are not a member of any developer teams."
case .missingPrivateKey: return "The developer certificate's private key could not be found."
case .missingCertificate: return "The developer certificate could not be found."
}
}
init?(_bridgedNSError error: NSError)
{
guard error.domain == InstallError.cancelled._domain else { return nil }
if let installError = InstallError(rawValue: error.code)
{
self = installError
}
else
{
return nil
}
}
}
extension ALTDeviceManager
{
func installApplication(at url: URL, to altDevice: ALTDevice, appleID: String, password: String, completion: @escaping (Result<ALTApplication, Error>) -> Void)
{
let destinationDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
var appName = (url.isFileURL) ? url.deletingPathExtension().lastPathComponent : NSLocalizedString("AltStore", comment: "")
func finish(_ result: Result<ALTApplication, Error>, title: String = "")
{
DispatchQueue.main.async {
switch result
{
case .success(let app): completion(.success(app))
case .failure(var error as NSError):
if error.localizedFailure == nil
{
error = error.withLocalizedFailure(String(format: NSLocalizedString("Could not install %@ to %@.", comment: ""), appName, altDevice.name))
}
completion(.failure(error))
}
}
try? FileManager.default.removeItem(at: destinationDirectoryURL)
}
AnisetteDataManager.shared.requestAnisetteData { (result) in
do
{
let anisetteData = try result.get()
self.authenticate(appleID: appleID, password: password, anisetteData: anisetteData) { (result) in
do
{
let (account, session) = try result.get()
self.fetchTeam(for: account, session: session) { (result) in
do
{
let team = try result.get()
self.register(altDevice, team: team, session: session) { (result) in
do
{
let device = try result.get()
device.osVersion = altDevice.osVersion
self.fetchCertificate(for: team, session: session) { (result) in
do
{
let certificate = try result.get()
if !url.isFileURL
{
// Show alert before downloading remote .ipa.
self.showInstallationAlert(appName: NSLocalizedString("AltStore", comment: ""), deviceName: device.name)
}
self.prepare(device) { (result) in
switch result
{
case .failure(let error):
print("Failed to install DeveloperDiskImage.dmg to \(device).", error)
fallthrough // Continue installing app even if we couldn't install Developer disk image.
case .success:
self.downloadApp(from: url) { (result) in
do
{
let fileURL = try result.get()
try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil)
let appBundleURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: destinationDirectoryURL)
guard let application = ALTApplication(fileURL: appBundleURL) else { throw ALTError(.invalidApp) }
if url.isFileURL
{
// Show alert after "downloading" local .ipa.
self.showInstallationAlert(appName: application.name, deviceName: device.name)
}
appName = application.name
// Refresh anisette data to prevent session timeouts.
AnisetteDataManager.shared.requestAnisetteData { (result) in
do
{
let anisetteData = try result.get()
session.anisetteData = anisetteData
self.prepareAllProvisioningProfiles(for: application, device: device, team: team, session: session) { (result) in
do
{
let profiles = try result.get()
self.install(application, to: device, team: team, certificate: certificate, profiles: profiles) { (result) in
finish(result.map { application }, title: "Failed to Install AltStore")
}
}
catch
{
finish(.failure(error), title: "Failed to Fetch Provisioning Profiles")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Refresh Anisette Data")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Download AltStore")
}
}
}
}
}
catch
{
finish(.failure(error), title: "Failed to Fetch Certificate")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Register Device")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Fetch Team")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Authenticate")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Fetch Anisette Data")
}
}
}
}
extension ALTDeviceManager
{
func prepare(_ device: ALTDevice, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
ALTDeviceManager.shared.isDeveloperDiskImageMounted(for: device) { (isMounted, error) in
switch (isMounted, error)
{
case (_, let error?): return completionHandler(.failure(error))
case (true, _): return completionHandler(.success(()))
case (false, _):
developerDiskManager.downloadDeveloperDisk(for: device) { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success((let diskFileURL, let signatureFileURL)):
ALTDeviceManager.shared.installDeveloperDiskImage(at: diskFileURL, signatureURL: signatureFileURL, to: device) { (success, error) in
switch Result(success, error)
{
case .failure(let error as ALTServerError) where error.code == .incompatibleDeveloperDisk:
developerDiskManager.setDeveloperDiskCompatible(false, with: device)
completionHandler(.failure(error))
case .failure(let error):
// Don't mark developer disk as incompatible because it probably failed for a different reason.
completionHandler(.failure(error))
case .success:
developerDiskManager.setDeveloperDiskCompatible(true, with: device)
completionHandler(.success(()))
}
}
}
}
}
}
}
}
private extension ALTDeviceManager
{
func downloadApp(from url: URL, completionHandler: @escaping (Result<URL, Error>) -> Void)
{
guard !url.isFileURL else { return completionHandler(.success(url)) }
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
do
{
let (fileURL, _) = try Result((fileURL, response), error).get()
completionHandler(.success(fileURL))
do { try FileManager.default.removeItem(at: fileURL) }
catch { print("Failed to remove downloaded .ipa.", error) }
}
catch
{
completionHandler(.failure(error))
}
}
downloadTask.resume()
}
func authenticate(appleID: String, password: String, anisetteData: ALTAnisetteData, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void)
{
func handleVerificationCode(_ completionHandler: @escaping (String?) -> Void)
{
DispatchQueue.main.async {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Two-Factor Authentication Enabled", comment: "")
alert.informativeText = NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: "")
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 22))
textField.delegate = self
textField.translatesAutoresizingMaskIntoConstraints = false
textField.placeholderString = NSLocalizedString("123456", comment: "")
alert.accessoryView = textField
alert.window.initialFirstResponder = textField
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
self.securityCodeAlert = alert
self.securityCodeTextField = textField
self.validate()
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let response = alert.runModal()
if response == .alertFirstButtonReturn
{
let code = textField.stringValue
completionHandler(code)
}
else
{
completionHandler(nil)
}
}
}
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData, verificationHandler: handleVerificationCode) { (account, session, error) in
if let account = account, let session = session
{
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(error ?? ALTAppleAPIError(.unknown)))
}
}
}
func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTTeam, Error>) -> Void)
{
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in
do
{
let teams = try Result(teams, error).get()
if let team = teams.first(where: { $0.type == .individual })
{
return completionHandler(.success(team))
}
else if let team = teams.first(where: { $0.type == .free })
{
return completionHandler(.success(team))
}
else if let team = teams.first
{
return completionHandler(.success(team))
}
else
{
throw InstallError.noTeam
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func fetchCertificate(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTCertificate, Error>) -> Void)
{
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
let certificateFileURL = FileManager.default.certificatesDirectory.appendingPathComponent(team.identifier + ".p12")
try FileManager.default.createDirectory(at: FileManager.default.certificatesDirectory, withIntermediateDirectories: true, attributes: nil)
var isCancelled = false
// Check if there is another AltStore certificate, which means AltStore has been installed with this Apple ID before.
let altstoreCertificate = certificates.first { $0.machineName?.starts(with: "AltStore") == true }
if let previousCertificate = altstoreCertificate
{
if FileManager.default.fileExists(atPath: certificateFileURL.path),
let data = try? Data(contentsOf: certificateFileURL),
let certificate = ALTCertificate(p12Data: data, password: previousCertificate.machineIdentifier)
{
// Manually set machineIdentifier so we can encrypt + embed certificate if needed.
certificate.machineIdentifier = previousCertificate.machineIdentifier
return completionHandler(.success(certificate))
}
DispatchQueue.main.sync {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Multiple AltServers Not Supported", comment: "")
alert.informativeText = NSLocalizedString("Please use the same AltServer you previously used with this Apple ID, or else apps installed with other AltServers will stop working.\n\nAre you sure you want to continue?", comment: "")
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let buttonIndex = alert.runModal()
if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn
{
isCancelled = true
}
}
guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) }
}
func addCertificate()
{
ALTAppleAPI.shared.addCertificate(machineName: "AltStore", to: team, session: session) { (certificate, error) in
do
{
let certificate = try Result(certificate, error).get()
guard let privateKey = certificate.privateKey else { throw InstallError.missingPrivateKey }
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
throw InstallError.missingCertificate
}
certificate.privateKey = privateKey
completionHandler(.success(certificate))
if let machineIdentifier = certificate.machineIdentifier,
let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier)
{
// Cache certificate.
do { try encryptedData.write(to: certificateFileURL, options: .atomic) }
catch { print("Failed to cache certificate:", error) }
}
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
if let certificate = altstoreCertificate ?? certificates.first
{
if team.type != .free
{
DispatchQueue.main.sync {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Installing this app will revoke your iOS development certificate.", comment: "")
alert.informativeText = NSLocalizedString("""
This will not affect apps you've submitted to the App Store, but may cause apps you've installed to your devices with Xcode to stop working until you reinstall them.
To prevent this from happening, feel free to try again with another Apple ID.
""", comment: "")
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let buttonIndex = alert.runModal()
if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn
{
isCancelled = true
}
}
guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) }
}
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
do
{
try Result(success, error).get()
addCertificate()
}
catch
{
completionHandler(.failure(error))
}
}
}
else
{
addCertificate()
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func prepareAllProvisioningProfiles(for application: ALTApplication, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession,
completion: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
{
self.prepareProvisioningProfile(for: application, parentApp: nil, device: device, team: team, session: session) { (result) in
do
{
let profile = try result.get()
var profiles = [application.bundleIdentifier: profile]
var error: Error?
let dispatchGroup = DispatchGroup()
for appExtension in application.appExtensions
{
dispatchGroup.enter()
self.prepareProvisioningProfile(for: appExtension, parentApp: application, device: device, team: team, session: session) { (result) in
switch result
{
case .failure(let e): error = e
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
}
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .global()) {
if let error = error
{
completion(.failure(error))
}
else
{
completion(.success(profiles))
}
}
}
catch
{
completion(.failure(error))
}
}
}
func prepareProvisioningProfile(for application: ALTApplication, parentApp: ALTApplication?, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
let parentBundleID = parentApp?.bundleIdentifier ?? application.bundleIdentifier
let updatedParentBundleID: String
if application.isAltStoreApp
{
// Use legacy bundle ID format for AltStore (and its extensions).
updatedParentBundleID = "com.\(team.identifier).\(parentBundleID)"
}
else
{
updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track.
}
let bundleID = application.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID)
let preferredName: String
if let parentApp = parentApp
{
preferredName = parentApp.name + " " + application.name
}
else
{
preferredName = application.name
}
self.registerAppID(name: preferredName, bundleID: bundleID, team: team, session: session) { (result) in
do
{
let appID = try result.get()
self.updateFeatures(for: appID, app: application, team: team, session: session) { (result) in
do
{
let appID = try result.get()
self.updateAppGroups(for: appID, app: application, team: team, session: session) { (result) in
do
{
let appID = try result.get()
self.fetchProvisioningProfile(for: appID, device: device, team: team, session: session) { (result) in
completionHandler(result)
}
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func registerAppID(name appName: String, bundleID: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in
do
{
let appIDs = try Result(appIDs, error).get()
if let appID = appIDs.first(where: { $0.bundleIdentifier == bundleID })
{
completionHandler(.success(appID))
}
else
{
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team, session: session) { (appID, error) in
completionHandler(Result(appID, error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func updateFeatures(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
let requiredFeatures = app.entitlements.compactMap { (entitlement, value) -> (ALTFeature, Any)? in
guard let feature = ALTFeature(entitlement: entitlement) else { return nil }
return (feature, value)
}
var features = requiredFeatures.reduce(into: [ALTFeature: Any]()) { $0[$1.0] = $1.1 }
if let applicationGroups = app.entitlements[.appGroups] as? [String], !applicationGroups.isEmpty
{
// App uses app groups, so assign `true` to enable the feature.
features[.appGroups] = true
}
else
{
// App has no app groups, so assign `false` to disable the feature.
features[.appGroups] = false
}
var updateFeatures = false
// Determine whether the required features are already enabled for the AppID.
for (feature, value) in features
{
if let appIDValue = appID.features[feature] as AnyObject?, (value as AnyObject).isEqual(appIDValue)
{
// AppID already has this feature enabled and the values are the same.
continue
}
else if appID.features[feature] == nil, let shouldEnableFeature = value as? Bool, !shouldEnableFeature
{
// AppID doesn't already have this feature enabled, but we want it disabled anyway.
continue
}
else
{
// AppID either doesn't have this feature enabled or the value has changed,
// so we need to update it to reflect new values.
updateFeatures = true
break
}
}
if updateFeatures
{
let appID = appID.copy() as! ALTAppID
appID.features = features
ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in
completionHandler(Result(appID, error))
}
}
else
{
completionHandler(.success(appID))
}
}
func updateAppGroups(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
guard let applicationGroups = app.entitlements[.appGroups] as? [String], !applicationGroups.isEmpty else {
// Assigning an App ID to an empty app group array fails,
// so just do nothing if there are no app groups.
return completionHandler(.success(appID))
}
// Dispatch onto global queue to prevent appGroupsSemaphore deadlock.
DispatchQueue.global().async {
// Ensure we're not concurrently fetching and updating app groups,
// which can lead to race conditions such as adding an app group twice.
appGroupsSemaphore.wait()
func finish(_ result: Result<ALTAppID, Error>)
{
appGroupsSemaphore.signal()
completionHandler(result)
}
ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in
switch Result(groups, error)
{
case .failure(let error): finish(.failure(error))
case .success(let fetchedGroups):
let dispatchGroup = DispatchGroup()
var groups = [ALTAppGroup]()
var errors = [Error]()
for groupIdentifier in applicationGroups
{
let adjustedGroupIdentifier = groupIdentifier + "." + team.identifier
if let group = fetchedGroups.first(where: { $0.groupIdentifier == adjustedGroupIdentifier })
{
groups.append(group)
}
else
{
dispatchGroup.enter()
// Not all characters are allowed in group names, so we replace periods with spaces (like Apple does).
let name = "AltStore " + groupIdentifier.replacingOccurrences(of: ".", with: " ")
ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in
switch Result(group, error)
{
case .success(let group): groups.append(group)
case .failure(let error): errors.append(error)
}
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .global()) {
if let error = errors.first
{
finish(.failure(error))
}
else
{
ALTAppleAPI.shared.assign(appID, to: Array(groups), team: team, session: session) { (success, error) in
let result = Result(success, error)
finish(result.map { _ in appID })
}
}
}
}
}
}
}
func register(_ device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
{
ALTAppleAPI.shared.fetchDevices(for: team, types: device.type, session: session) { (devices, error) in
do
{
let devices = try Result(devices, error).get()
if let device = devices.first(where: { $0.identifier == device.identifier })
{
completionHandler(.success(device))
}
else
{
ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, type: device.type, team: team, session: session) { (device, error) in
completionHandler(Result(device, error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func fetchProvisioningProfile(for appID: ALTAppID, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: device.type, team: team, session: session) { (profile, error) in
completionHandler(Result(profile, error))
}
}
func install(_ application: ALTApplication, to device: ALTDevice, team: ALTTeam, certificate: ALTCertificate, profiles: [String: ALTProvisioningProfile], completionHandler: @escaping (Result<Void, Error>) -> Void)
{
func prepare(_ bundle: Bundle, additionalInfoDictionaryValues: [String: Any] = [:]) throws
{
guard let identifier = bundle.bundleIdentifier else { throw ALTError(.missingAppBundle) }
guard let profile = profiles[identifier] else { throw ALTError(.missingProvisioningProfile) }
guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
infoDictionary[Bundle.Info.altBundleID] = identifier
if (infoDictionary.keys.contains(Bundle.Info.deviceID)) {
infoDictionary[Bundle.Info.deviceID] = device.identifier
}
for (key, value) in additionalInfoDictionaryValues
{
infoDictionary[key] = value
}
if let appGroups = profile.entitlements[.appGroups] as? [String]
{
infoDictionary[Bundle.Info.appGroups] = appGroups
}
try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
}
DispatchQueue.global().async {
do
{
guard let appBundle = Bundle(url: application.fileURL) else { throw ALTError(.missingAppBundle) }
guard let infoDictionary = appBundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
let openAppURL = URL(string: "altstore-" + application.bundleIdentifier + "://")!
var allURLSchemes = infoDictionary[Bundle.Info.urlTypes] as? [[String: Any]] ?? []
// Embed open URL so AltBackup can return to AltStore.
let altstoreURLScheme = ["CFBundleTypeRole": "Editor",
"CFBundleURLName": application.bundleIdentifier,
"CFBundleURLSchemes": [openAppURL.scheme!]] as [String : Any]
allURLSchemes.append(altstoreURLScheme)
var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes]
if application.isAltStoreApp
{
additionalValues[Bundle.Info.deviceID] = device.identifier
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID
if
let machineIdentifier = certificate.machineIdentifier,
let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier)
{
additionalValues[Bundle.Info.certificateID] = certificate.serialNumber
let certificateURL = application.fileURL.appendingPathComponent("ALTCertificate.p12")
try encryptedData.write(to: certificateURL, options: .atomic)
}
}
else if infoDictionary.keys.contains(Bundle.Info.deviceID)
{
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
additionalValues[Bundle.Info.deviceID] = device.identifier
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID
}
try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
for appExtension in application.appExtensions
{
guard let bundle = Bundle(url: appExtension.fileURL) else { throw ALTError(.missingAppBundle) }
try prepare(bundle)
}
let resigner = ALTSigner(team: team, certificate: certificate)
resigner.signApp(at: application.fileURL, provisioningProfiles: Array(profiles.values)) { (success, error) in
do
{
try Result(success, error).get()
let activeProfiles: Set<String>? = (team.type == .free && application.isAltStoreApp) ? Set(profiles.values.map(\.bundleIdentifier)) : nil
ALTDeviceManager.shared.installApp(at: application.fileURL, toDeviceWithUDID: device.identifier, activeProvisioningProfiles: activeProfiles) { (success, error) in
completionHandler(Result(success, error))
}
}
catch
{
print("Failed to install app", error)
completionHandler(.failure(error))
}
}
}
catch
{
print("Failed to install AltStore", error)
completionHandler(.failure(error))
}
}
}
func showInstallationAlert(appName: String, deviceName: String)
{
let content = UNMutableNotificationContent()
content.title = String(format: NSLocalizedString("Installing %@ to %@...", comment: ""), appName, deviceName)
content.body = NSLocalizedString("This may take a few seconds.", comment: "")
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
}
}
private var securityCodeAlertKey = 0
private var securityCodeTextFieldKey = 0
extension ALTDeviceManager: NSTextFieldDelegate
{
var securityCodeAlert: NSAlert? {
get { return objc_getAssociatedObject(self, &securityCodeAlertKey) as? NSAlert }
set { objc_setAssociatedObject(self, &securityCodeAlertKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
var securityCodeTextField: NSTextField? {
get { return objc_getAssociatedObject(self, &securityCodeTextFieldKey) as? NSTextField }
set { objc_setAssociatedObject(self, &securityCodeTextFieldKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
public func controlTextDidChange(_ obj: Notification)
{
self.validate()
}
public func controlTextDidEndEditing(_ obj: Notification)
{
self.validate()
}
private func validate()
{
guard let code = self.securityCodeTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) else { return }
if code.count == 6
{
self.securityCodeAlert?.buttons.first?.isEnabled = true
}
else
{
self.securityCodeAlert?.buttons.first?.isEnabled = false
}
self.securityCodeAlert?.layout()
}
}

View File

@@ -1,56 +0,0 @@
//
// ALTDeviceManager.h
// AltServer
//
// Created by Riley Testut on 5/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "AltSign.h"
@class ALTWiredConnection;
@class ALTNotificationConnection;
@class ALTDebugConnection;
@class ALTInstalledApp;
NS_ASSUME_NONNULL_BEGIN
extern NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidConnect);
extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidDisconnect);
@interface ALTDeviceManager : NSObject
@property (class, nonatomic, readonly) ALTDeviceManager *sharedManager;
@property (nonatomic, readonly) NSArray<ALTDevice *> *connectedDevices;
@property (nonatomic, readonly) NSArray<ALTDevice *> *availableDevices;
- (void)start;
/* App Installation */
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet<NSString *> *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)removeAppForBundleIdentifier:(NSString *)bundleIdentifier fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
/* Provisioning Profiles */
- (void)installProvisioningProfiles:(NSSet<ALTProvisioningProfile *> *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet<NSString *> *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)removeProvisioningProfilesForBundleIdentifiers:(NSSet<NSString *> *)bundleIdentifiers fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
/* Developer Disk Image */
- (void)isDeveloperDiskImageMountedForDevice:(ALTDevice *)device
completionHandler:(void (^)(BOOL isMounted, NSError *_Nullable error))completionHandler;
- (void)installDeveloperDiskImageAtURL:(NSURL *)diskURL signatureURL:(NSURL *)signatureURL toDevice:(ALTDevice *)device
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
/* Apps */
- (void)fetchInstalledAppsOnDevice:(ALTDevice *)altDevice completionHandler:(void (^)(NSSet<ALTInstalledApp *> *_Nullable installedApps, NSError *_Nullable error))completionHandler;
/* Connections */
- (void)startWiredConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTWiredConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
- (void)startNotificationConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTNotificationConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
- (void)startDebugConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTDebugConnection *_Nullable connection, NSError * _Nullable error))completionHandler;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +0,0 @@
//
// FileManager+URLs.swift
// AltServer
//
// Created by Riley Testut on 2/23/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import Foundation
extension FileManager
{
var altserverDirectory: URL {
let applicationSupportDirectoryURL = self.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
let altserverDirectoryURL = applicationSupportDirectoryURL.appendingPathComponent("com.rileytestut.AltServer")
return altserverDirectoryURL
}
var certificatesDirectory: URL {
let certificatesDirectoryURL = self.altserverDirectory.appendingPathComponent("Certificates")
return certificatesDirectoryURL
}
var developerDisksDirectory: URL {
let developerDisksDirectoryURL = self.altserverDirectory.appendingPathComponent("DeveloperDiskImages")
return developerDisksDirectoryURL
}
}

View File

@@ -1,38 +0,0 @@
//
// UserDefaults+AltServer.swift
// AltServer
//
// Created by Riley Testut on 7/31/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
extension UserDefaults
{
var serverID: String? {
get {
return self.string(forKey: "serverID")
}
set {
self.set(newValue, forKey: "serverID")
}
}
var didPresentInitialNotification: Bool {
get {
return self.bool(forKey: "didPresentInitialNotification")
}
set {
self.set(newValue, forKey: "didPresentInitialNotification")
}
}
func registerDefaults()
{
if self.serverID == nil
{
self.serverID = UUID().uuidString
}
}
}

View File

@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Riley Testut. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SUFeedURL</key>
<string>https://altstore.io/altserver/sparkle-macos.xml</string>
<key>SUFeedURL-Staging</key>
<string>https://altstore.io/altserver/sparkle-macos-staging.xml</string>
</dict>
</plist>

View File

@@ -1,29 +0,0 @@
//
// InstalledApp.swift
// AltServer
//
// Created by Riley Testut on 5/25/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import Foundation
@objc(ALTInstalledApp) @objcMembers
class InstalledApp: NSObject, MenuDisplayable
{
let name: String
let bundleIdentifier: String
let executableName: String
init?(dictionary: [String: Any])
{
guard let name = dictionary[kCFBundleNameKey as String] as? String,
let bundleIdentifier = dictionary[kCFBundleIdentifierKey as String] as? String,
let executableName = dictionary[kCFBundleExecutableKey as String] as? String
else { return nil }
self.name = name
self.bundleIdentifier = bundleIdentifier
self.executableName = executableName
}
}

View File

@@ -1,115 +0,0 @@
//
// MenuController.swift
// AltServer
//
// Created by Riley Testut on 3/3/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import Foundation
import AppKit
protocol MenuDisplayable
{
var name: String { get }
}
class MenuController<T: MenuDisplayable & Hashable>: NSObject, NSMenuDelegate
{
let menu: NSMenu
var items: [T] {
didSet {
self.submenus.removeAll()
self.updateMenu()
}
}
var placeholder: String? {
didSet {
self.updateMenu()
}
}
var action: ((T) -> Void)?
var submenuHandler: ((T) -> NSMenu)?
private var submenus = [T: NSMenu]()
init(menu: NSMenu, items: [T])
{
self.menu = menu
self.items = items
super.init()
self.menu.delegate = self
}
@objc
private func performAction(_ menuItem: NSMenuItem)
{
guard case let index = self.menu.index(of: menuItem), index != -1 else { return }
let item = self.items[index]
self.action?(item)
}
@objc
func numberOfItems(in menu: NSMenu) -> Int
{
let numberOfItems = (self.items.isEmpty && self.placeholder != nil) ? 1 : self.items.count
return numberOfItems
}
@objc
func menu(_ menu: NSMenu, update menuItem: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool
{
if let text = self.placeholder, self.items.isEmpty
{
menuItem.title = text
menuItem.isEnabled = false
menuItem.target = nil
menuItem.action = nil
}
else
{
let item = self.items[index]
menuItem.title = item.name
menuItem.isEnabled = true
menuItem.target = self
menuItem.action = #selector(MenuController.performAction(_:))
menuItem.tag = index
if let submenu = self.submenus[item] ?? self.submenuHandler?(item)
{
menuItem.submenu = submenu
// Cache submenu to prevent duplicate calls to submenuHandler.
self.submenus[item] = submenu
}
}
return true
}
}
private extension MenuController
{
func updateMenu()
{
self.menu.removeAllItems()
let numberOfItems = self.numberOfItems(in: self.menu)
for index in 0 ..< numberOfItems
{
let menuItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
guard self.menu(self.menu, update: menuItem, at: index, shouldCancel: false) else { break }
self.menu.addItem(menuItem)
}
self.menu.update()
}
}

View File

@@ -1,372 +0,0 @@
//
// PluginManager.swift
// AltServer
//
// Created by Riley Testut on 9/16/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
import AppKit
import CryptoKit
import STPrivilegedTask
private let pluginDirectoryURL = URL(fileURLWithPath: "/Library/Mail/Bundles", isDirectory: true)
private let pluginURL = pluginDirectoryURL.appendingPathComponent("AltPlugin.mailbundle")
enum PluginError: LocalizedError
{
case cancelled
case unknown
case notFound
case mismatchedHash(hash: String, expectedHash: String)
case taskError(String)
case taskErrorCode(Int)
var errorDescription: String? {
switch self
{
case .cancelled: return NSLocalizedString("Mail plug-in installation was cancelled.", comment: "")
case .unknown: return NSLocalizedString("Failed to install Mail plug-in.", comment: "")
case .notFound: return NSLocalizedString("The Mail plug-in does not exist at the requested URL.", comment: "")
case .mismatchedHash(let hash, let expectedHash): return String(format: NSLocalizedString("The hash of the downloaded Mail plug-in does not match the expected hash.\n\nHash:\n%@\n\nExpected Hash:\n%@", comment: ""), hash, expectedHash)
case .taskError(let output): return output
case .taskErrorCode(let errorCode): return String(format: NSLocalizedString("There was an error installing the Mail plug-in. (Error Code: %@)", comment: ""), NSNumber(value: errorCode))
}
}
}
private extension URL
{
#if STAGING
static let altPluginUpdateURL = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altserver/altplugin/altplugin.json")!
#else
static let altPluginUpdateURL = URL(string: "https://cdn.altstore.io/file/altstore/altserver/altplugin/altplugin.json")!
#endif
}
class PluginManager
{
private let session = URLSession(configuration: .ephemeral)
private var latestPluginVersion: PluginVersion?
var isMailPluginInstalled: Bool {
let isMailPluginInstalled = FileManager.default.fileExists(atPath: pluginURL.path)
return isMailPluginInstalled
}
func isUpdateAvailable(completionHandler: @escaping (Result<Bool, Error>) -> Void)
{
self.isUpdateAvailable(useCache: false, completionHandler: completionHandler)
}
private func isUpdateAvailable(useCache: Bool, completionHandler: @escaping (Result<Bool, Error>) -> Void)
{
do
{
// If Mail plug-in is not yet installed, then there is no update available.
var isDirectory: ObjCBool = false
guard FileManager.default.fileExists(atPath: pluginURL.path, isDirectory: &isDirectory), isDirectory.boolValue else { return completionHandler(.success(false)) }
// Load Info.plist from disk because Bundle.infoDictionary is cached by system.
let infoDictionaryURL = pluginURL.appendingPathComponent("Contents/Info.plist")
guard let infoDictionary = NSDictionary(contentsOf: infoDictionaryURL) as? [String: Any],
let localVersion = infoDictionary["CFBundleShortVersionString"] as? String
else { throw CocoaError(.fileReadCorruptFile, userInfo: [NSURLErrorKey: infoDictionaryURL]) }
if let pluginVersion = self.latestPluginVersion, useCache
{
let isUpdateAvailable = (localVersion != pluginVersion.version)
completionHandler(.success(isUpdateAvailable))
}
else
{
self.fetchLatestPluginVersion(useCache: useCache) { result in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let pluginVersion):
let isUpdateAvailable = (localVersion != pluginVersion.version)
completionHandler(.success(isUpdateAvailable))
}
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
extension PluginManager
{
func installMailPlugin(completionHandler: @escaping (Result<Void, Error>) -> Void)
{
self.isUpdateAvailable(useCache: true) { result in
DispatchQueue.main.async {
do
{
let isUpdateAvailable = try result.get()
let alert = NSAlert()
if isUpdateAvailable
{
alert.messageText = NSLocalizedString("Update Mail Plug-in", comment: "")
alert.informativeText = NSLocalizedString("An update is available for AltServer's Mail plug-in. Please update the plug-in now in order to keep using AltStore.", comment: "")
alert.addButton(withTitle: NSLocalizedString("Update Plug-in", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
}
else
{
alert.messageText = NSLocalizedString("Install Mail Plug-in", comment: "")
alert.informativeText = NSLocalizedString("AltServer requires a Mail plug-in in order to retrieve necessary information about your Apple ID. Would you like to install it now?", comment: "")
alert.addButton(withTitle: NSLocalizedString("Install Plug-in", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
}
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let response = alert.runModal()
guard response == .alertFirstButtonReturn else { throw PluginError.cancelled }
self.downloadPlugin { (result) in
do
{
let fileURL = try result.get()
// Ensure plug-in directory exists.
let authorization = try self.runAndKeepAuthorization("mkdir", arguments: ["-p", pluginDirectoryURL.path])
// Create temporary directory.
let temporaryDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
defer { try? FileManager.default.removeItem(at: temporaryDirectoryURL) }
// Unzip AltPlugin to temporary directory.
try self.runAndKeepAuthorization("unzip", arguments: ["-o", fileURL.path, "-d", temporaryDirectoryURL.path], authorization: authorization)
if FileManager.default.fileExists(atPath: pluginURL.path)
{
// Delete existing Mail plug-in.
try self.runAndKeepAuthorization("rm", arguments: ["-rf", pluginURL.path], authorization: authorization)
}
// Copy AltPlugin to Mail plug-ins directory.
// Must be separate step than unzip to prevent macOS from considering plug-in corrupted.
let unzippedPluginURL = temporaryDirectoryURL.appendingPathComponent(pluginURL.lastPathComponent)
try self.runAndKeepAuthorization("cp", arguments: ["-R", unzippedPluginURL.path, pluginDirectoryURL.path], authorization: authorization)
guard self.isMailPluginInstalled else { throw PluginError.unknown }
// Enable Mail plug-in preferences.
try self.run("defaults", arguments: ["write", "/Library/Preferences/com.apple.mail", "EnableBundles", "-bool", "YES"], authorization: authorization)
print("Finished installing Mail plug-in!")
completionHandler(.success(()))
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
}
func uninstallMailPlugin(completionHandler: @escaping (Result<Void, Error>) -> Void)
{
let alert = NSAlert()
alert.messageText = NSLocalizedString("Uninstall Mail Plug-in", comment: "")
alert.informativeText = NSLocalizedString("Are you sure you want to uninstall the AltServer Mail plug-in? You will no longer be able to install or refresh apps with AltStore.", comment: "")
alert.addButton(withTitle: NSLocalizedString("Uninstall Plug-in", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let response = alert.runModal()
guard response == .alertFirstButtonReturn else { return completionHandler(.failure(PluginError.cancelled)) }
DispatchQueue.global().async {
do
{
if FileManager.default.fileExists(atPath: pluginURL.path)
{
// Delete Mail plug-in from privileged directory.
try self.run("rm", arguments: ["-rf", pluginURL.path])
}
completionHandler(.success(()))
}
catch
{
completionHandler(.failure(error))
}
}
}
}
private extension PluginManager
{
func fetchLatestPluginVersion(useCache: Bool, completionHandler: @escaping (Result<PluginVersion, Error>) -> Void)
{
if let pluginVersion = self.latestPluginVersion, useCache
{
return completionHandler(.success(pluginVersion))
}
guard #available(macOS 11, *) else {
// macOS versions prior to 11.0 require Mail plug-ins be *unsigned*,
// so we hardcode these versions to use the unsigned AltPlugin v1.0.
return completionHandler(.success(.v1_0))
}
let dataTask = self.session.dataTask(with: .altPluginUpdateURL) { (data, response, error) in
do
{
if let response = response as? HTTPURLResponse
{
guard response.statusCode != 404 else { return completionHandler(.failure(PluginError.notFound)) }
}
guard let data = data else { throw error! }
let response = try JSONDecoder().decode(PluginVersionResponse.self, from: data)
completionHandler(.success(response.pluginVersion))
}
catch
{
completionHandler(.failure(error))
}
}
dataTask.resume()
}
func downloadPlugin(completion: @escaping (Result<URL, Error>) -> Void)
{
self.fetchLatestPluginVersion(useCache: true) { result in
switch result
{
case .failure(let error): completion(.failure(error))
case .success(let pluginVersion):
func finish(_ result: Result<URL, Error>)
{
do
{
let fileURL = try result.get()
if #available(OSX 10.15, *)
{
let data = try Data(contentsOf: fileURL)
let sha256Hash = SHA256.hash(data: data)
let hashString = sha256Hash.compactMap { String(format: "%02x", $0) }.joined()
print("Comparing Mail plug-in hash (\(hashString)) against expected hash (\(pluginVersion.sha256Hash))...")
guard hashString == pluginVersion.sha256Hash else { throw PluginError.mismatchedHash(hash: hashString, expectedHash: pluginVersion.sha256Hash) }
}
completion(.success(fileURL))
}
catch
{
completion(.failure(error))
}
}
if pluginVersion.url.isFileURL
{
finish(.success(pluginVersion.url))
}
else
{
let downloadTask = URLSession.shared.downloadTask(with: pluginVersion.url) { (fileURL, response, error) in
if let response = response as? HTTPURLResponse
{
guard response.statusCode != 404 else { return finish(.failure(PluginError.notFound)) }
}
let result = Result(fileURL, error)
finish(result)
if let fileURL = fileURL
{
try? FileManager.default.removeItem(at: fileURL)
}
}
downloadTask.resume()
}
}
}
}
func run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws
{
_ = try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: true)
}
@discardableResult
func runAndKeepAuthorization(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws -> AuthorizationRef
{
return try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: false)
}
func _run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil, freeAuthorization: Bool) throws -> AuthorizationRef
{
var launchPath = "/usr/bin/" + program
if !FileManager.default.fileExists(atPath: launchPath)
{
launchPath = "/bin/" + program
}
print("Running program:", launchPath)
let task = STPrivilegedTask()
task.launchPath = launchPath
task.arguments = arguments
task.freeAuthorizationWhenDone = freeAuthorization
let errorCode: OSStatus
if let authorization = authorization
{
errorCode = task.launch(withAuthorization: authorization)
}
else
{
errorCode = task.launch()
}
guard errorCode == 0 else { throw PluginError.taskErrorCode(Int(errorCode)) }
task.waitUntilExit()
print("Exit code:", task.terminationStatus)
guard task.terminationStatus == 0 else {
let outputData = task.outputFileHandle.readDataToEndOfFile()
if let outputString = String(data: outputData, encoding: .utf8), !outputString.isEmpty
{
throw PluginError.taskError(outputString)
}
throw PluginError.taskErrorCode(Int(task.terminationStatus))
}
guard let authorization = task.authorization else { throw PluginError.unknown }
return authorization
}
}

View File

@@ -1,37 +0,0 @@
//
// PluginVersion.swift
// AltServer
//
// Created by Riley Testut and Weedles on 2/15/22 <3
// Copyright © 2022 Riley Testut. All rights reserved.
//
import Foundation
struct PluginVersion: Decodable
{
var url: URL
var sha256Hash: String
var version: String
static let v1_0 = PluginVersion(url: URL(string: "https://f000.backblazeb2.com/file/altstore/altserver/altplugin/1_0.zip")!,
sha256Hash: "070e9b7e1f74e7a6474d36253ab5a3623ff93892acc9e1043c3581f2ded12200",
version: "1.0")
static let v1_9 = PluginVersion(url: Bundle.main.url(forResource: "AltPlugin", withExtension: "zip")!,
sha256Hash: "83ead26d8776ef6850e06fe3d1c5c5559aca284718b1cf3cc49785ba6b1e2849",
version: "1.9")
private enum CodingKeys: String, CodingKey
{
case url
case sha256Hash = "sha256"
case version
}
}
struct PluginVersionResponse: Decodable
{
var version: Int
var pluginVersion: PluginVersion
}

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
#include "Build.xcconfig"

View File

@@ -1,7 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "ALTAnisetteData.h"
#import "AltXPCProtocol.h"
#import "ALTPluginService.h"

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.authkit.client.internal</key>
<true/>
</dict>
</plist>

View File

@@ -1,24 +0,0 @@
//
// AltXPC.swift
// AltXPC
//
// Created by Riley Testut on 12/3/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
@objc(AltXPC)
class AltXPC: NSObject, AltXPCProtocol
{
func ping(_ completionHandler: @escaping () -> Void)
{
completionHandler()
}
func requestAnisetteData(completionHandler: @escaping (ALTAnisetteData?, Error?) -> Void)
{
let anisetteData = ALTPluginService.shared.requestAnisetteData()
completionHandler(anisetteData, nil)
}
}

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>AltXPC</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Riley Testut. All rights reserved.</string>
<key>XPCService</key>
<dict>
<key>ServiceType</key>
<string>Application</string>
</dict>
</dict>
</plist>

View File

@@ -1,31 +0,0 @@
//
// main.swift
// AltXPC
//
// Created by Riley Testut on 12/3/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
class ServiceDelegate : NSObject, NSXPCListenerDelegate
{
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool
{
newConnection.exportedInterface = NSXPCInterface(with: AltXPCProtocol.self)
let exportedObject = AltXPC()
newConnection.exportedObject = exportedObject
newConnection.resume()
return true
}
}
let serviceDelegate = ServiceDelegate()
let listener = NSXPCListener.service()
listener.delegate = serviceDelegate
listener.resume()
RunLoop.main.run()