mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-10 15:23:27 +01:00
Implement emotional damage (#95)
* Implement em_proxy * Update libimobiledevice * Add minimuxer library to Xcode * Build missing C files for libimobiledevice * Remove objective C library * Add pairing file to Info.plist * Heartbeat self on startup * Enable JIT on-device * Implement on-device installation * Fix OpenSSL header errors * Random submodule bullcrap go * Search release folder for emotional damage * Clean dependencies * Build Rust dependencies attempt 1/999 * Update em_proxy * Implement refreshing apps * Clean up old operations * Remove all AltServer code * Remove files from Xcode project * Implement auto mounting the developer DMG * Recover from app being backgrounded * Fixed keeping pairing file in app after updating SideStore (#3) * Use compliant error handling for minimuxer * Fix app failing to install * Don't kill proxy on backgrounding * Makes sure the ALTPairingFile gets transferred even if team IDs change (#4) * Step 1 to allow SideStore to resign itself * Update ResignAppOperation.swift * Adding cache for action runner (#5) * Start caching commit for actions Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> * Update build.yml Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> * Update build.yml Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> * Use rust lib directories to cache Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> * Cache cargo also Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> * Fix spacing Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> * Replace cargo id for caching Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> * Remove cache if statements Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> * Add disconnected WireGuard detection * Add minimuxer logging Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com> Co-authored-by: jawshoeadan <62785552+jawshoeadan@users.noreply.github.com> Co-authored-by: Joelle Stickney <joellestickney@gmail.com> Co-authored-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import Intents
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import EmotionalDamage
|
||||
|
||||
extension AppDelegate
|
||||
{
|
||||
@@ -75,8 +76,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
self.setTintColor()
|
||||
|
||||
ServerManager.shared.startDiscovering()
|
||||
|
||||
SecureValueTransformer.register()
|
||||
|
||||
if UserDefaults.standard.firstLaunch == nil
|
||||
@@ -95,18 +94,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication)
|
||||
{
|
||||
ServerManager.shared.stopDiscovering()
|
||||
}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication)
|
||||
{
|
||||
AppManager.shared.update()
|
||||
ServerManager.shared.startDiscovering()
|
||||
|
||||
PatreonAPI.shared.refreshPatreonAccount()
|
||||
start_em_proxy(bind_addr: "127.0.0.1:51820")
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
<string>00008101-000129D63698001E</string>
|
||||
<key>ALTServerID</key>
|
||||
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
|
||||
<key>ALTPairingFile</key>
|
||||
<string><insert pairing file here></string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
import UIKit
|
||||
import Roxas
|
||||
import EmotionalDamage
|
||||
import minimuxer
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
@@ -36,6 +38,12 @@ class LaunchViewController: RSTLaunchViewController
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
start_em_proxy(bind_addr: "127.0.0.1:51820")
|
||||
|
||||
let pf = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String
|
||||
set_usbmuxd_socket()
|
||||
start_minimuxer(pairing_file: pf.unsafelyUnwrapped)
|
||||
auto_mount_dev_image()
|
||||
|
||||
// Create destinationViewController now so view controllers can register for receiving Notifications.
|
||||
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
|
||||
|
||||
@@ -217,23 +217,6 @@ extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func findServer(context: OperationContext = OperationContext(), completionHandler: @escaping (Result<Server, Error>) -> Void) -> FindServerOperation
|
||||
{
|
||||
let findServerOperation = FindServerOperation(context: context)
|
||||
findServerOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let server): context.server = server
|
||||
}
|
||||
}
|
||||
|
||||
self.run([findServerOperation], context: context)
|
||||
|
||||
return findServerOperation
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func authenticate(presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<(ALTTeam, ALTCertificate, ALTAppleAPISession), Error>) -> Void) -> AuthenticationOperation
|
||||
{
|
||||
@@ -242,8 +225,6 @@ extension AppManager
|
||||
return operation
|
||||
}
|
||||
|
||||
let findServerOperation = self.findServer(context: context) { _ in }
|
||||
|
||||
let authenticationOperation = AuthenticationOperation(context: context, presentingViewController: presentingViewController)
|
||||
authenticationOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
@@ -254,7 +235,6 @@ extension AppManager
|
||||
|
||||
completionHandler(result)
|
||||
}
|
||||
authenticationOperation.addDependency(findServerOperation)
|
||||
|
||||
self.run([authenticationOperation], context: context)
|
||||
|
||||
@@ -555,13 +535,10 @@ extension AppManager
|
||||
// authentication, so we keep it separate.
|
||||
let context = OperationContext()
|
||||
|
||||
let findServerOperation = self.findServer(context: context) { _ in }
|
||||
|
||||
let deactivateAppOperation = DeactivateAppOperation(app: installedApp, context: context)
|
||||
deactivateAppOperation.resultHandler = { (result) in
|
||||
completionHandler(result)
|
||||
}
|
||||
deactivateAppOperation.addDependency(findServerOperation)
|
||||
|
||||
self.run([deactivateAppOperation], context: context, requiresSerialQueue: true)
|
||||
}
|
||||
@@ -695,13 +672,11 @@ extension AppManager
|
||||
let context = Context()
|
||||
context.installedApp = installedApp
|
||||
|
||||
let findServerOperation = self.findServer(context: context) { _ in }
|
||||
|
||||
let enableJITOperation = EnableJITOperation(context: context)
|
||||
enableJITOperation.resultHandler = { (result) in
|
||||
completionHandler(result)
|
||||
}
|
||||
enableJITOperation.addDependency(findServerOperation)
|
||||
|
||||
self.run([enableJITOperation], context: context, requiresSerialQueue: true)
|
||||
}
|
||||
@@ -747,7 +722,7 @@ extension AppManager
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let installationConnection): context.installationConnection = installationConnection
|
||||
case .success(_): print("App sent over AFC")
|
||||
}
|
||||
}
|
||||
sendAppOperation.addDependency(patchAppOperation)
|
||||
@@ -900,8 +875,7 @@ private extension AppManager
|
||||
|
||||
if app.certificateSerialNumber != group.context.certificate?.serialNumber ||
|
||||
uti != nil ||
|
||||
app.needsResign ||
|
||||
(group.context.server?.connectionType == .local && !UserDefaults.standard.localServerSupportsRefreshing)
|
||||
app.needsResign
|
||||
{
|
||||
// Resign app instead of just refreshing profiles because either:
|
||||
// * Refreshing using different certificate
|
||||
@@ -1147,7 +1121,7 @@ private extension AppManager
|
||||
presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
presentingViewController.present(navigationController, animated: true, completion: nil)
|
||||
presentingViewController.present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -1204,7 +1178,7 @@ private extension AppManager
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let installationConnection): context.installationConnection = installationConnection
|
||||
case .success(_): print("App reported as installed")
|
||||
}
|
||||
}
|
||||
sendAppOperation.addDependency(resignAppOperation)
|
||||
@@ -1649,27 +1623,6 @@ private extension AppManager
|
||||
|
||||
func finish(_ operation: AppOperation, result: Result<InstalledApp, Error>, group: RefreshGroup, progress: Progress?)
|
||||
{
|
||||
let result = result.mapError { (resultError) -> Error in
|
||||
guard let error = resultError as? ALTServerError else { return resultError }
|
||||
|
||||
switch error.code
|
||||
{
|
||||
case .deviceNotFound, .lostConnection:
|
||||
if let server = group.context.server, server.isPreferred || server.connectionType != .wireless
|
||||
{
|
||||
// Preferred server (or not random wireless connection), so report errors normally.
|
||||
return error
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not preferred server, so ignore these specific errors and throw serverNotFound instead.
|
||||
return ConnectionError.serverNotFound
|
||||
}
|
||||
|
||||
default: return error
|
||||
}
|
||||
}
|
||||
|
||||
// Must remove before saving installedApp.
|
||||
if let currentProgress = self.progress(for: operation), currentProgress == progress
|
||||
{
|
||||
@@ -1709,7 +1662,7 @@ private extension AppManager
|
||||
}
|
||||
|
||||
if #available(iOS 14, *)
|
||||
{
|
||||
{
|
||||
WidgetCenter.shared.getCurrentConfigurations { (result) in
|
||||
guard case .success(let widgets) = result else { return }
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import UIKit
|
||||
import CoreData
|
||||
|
||||
import AltStoreCore
|
||||
import EmotionalDamage
|
||||
|
||||
enum RefreshError: LocalizedError
|
||||
{
|
||||
@@ -80,10 +81,11 @@ class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledA
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if UIApplication.shared.applicationState == .background
|
||||
{
|
||||
ServerManager.shared.stopDiscovering()
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override func main()
|
||||
@@ -94,11 +96,7 @@ class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledA
|
||||
self.finish(.failure(RefreshError.noInstalledApps))
|
||||
return
|
||||
}
|
||||
|
||||
if !ServerManager.shared.isDiscovering
|
||||
{
|
||||
ServerManager.shared.startDiscovering()
|
||||
}
|
||||
start_em_proxy(bind_addr: "127.0.0.1:51820")
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||
@@ -207,10 +205,6 @@ private extension BackgroundRefreshAppsOperation
|
||||
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
||||
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
||||
}
|
||||
catch ConnectionError.serverNotFound
|
||||
{
|
||||
shouldPresentAlert = false
|
||||
}
|
||||
catch RefreshError.noInstalledApps
|
||||
{
|
||||
shouldPresentAlert = false
|
||||
|
||||
@@ -11,6 +11,7 @@ import Foundation
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import minimuxer
|
||||
|
||||
@objc(DeactivateAppOperation)
|
||||
class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
@@ -36,54 +37,27 @@ class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
return
|
||||
}
|
||||
|
||||
guard let server = self.context.server else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { return self.finish(.failure(OperationError.unknownUDID)) }
|
||||
|
||||
ServerManager.shared.connect(to: server) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(let connection):
|
||||
print("Sending deactivate app request...")
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let installedApp = context.object(with: self.app.objectID) as! InstalledApp
|
||||
|
||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
||||
let allIdentifiers = [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||
|
||||
let request = RemoveProvisioningProfilesRequest(udid: udid, bundleIdentifiers: Set(allIdentifiers))
|
||||
connection.send(request) { (result) in
|
||||
print("Sent deactive app request!")
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success:
|
||||
print("Waiting for deactivate app response...")
|
||||
connection.receiveResponse() { (result) in
|
||||
print("Receiving deactivate app response:", result)
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(.error(let response)): self.finish(.failure(response.error))
|
||||
case .success(.removeProvisioningProfiles):
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
let installedApp = context.object(with: self.app.objectID) as! InstalledApp
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
|
||||
case .success: self.finish(.failure(ALTServerError(.unknownResponse)))
|
||||
}
|
||||
}
|
||||
}
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let installedApp = context.object(with: self.app.objectID) as! InstalledApp
|
||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
||||
let allIdentifiers = [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||
|
||||
for profile in allIdentifiers {
|
||||
do {
|
||||
let res = try remove_provisioning_profile(id: profile)
|
||||
if case Uhoh.Bad(let code) = res {
|
||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
||||
}
|
||||
} catch Uhoh.Bad(let code) {
|
||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
||||
} catch {
|
||||
self.finish(.failure(ALTServerError(.unknownResponse)))
|
||||
}
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import minimuxer
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
@available(iOS 14, *)
|
||||
protocol EnableJITContext
|
||||
{
|
||||
var server: Server? { get }
|
||||
var installedApp: InstalledApp? { get }
|
||||
|
||||
var error: Error? { get }
|
||||
@@ -42,95 +42,26 @@ class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||
return
|
||||
}
|
||||
|
||||
guard let server = self.context.server, let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { return self.finish(.failure(OperationError.unknownUDID)) }
|
||||
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
guard let bundle = Bundle(url: installedApp.fileURL),
|
||||
let processName = bundle.executableURL?.lastPathComponent
|
||||
else { return self.finish(.failure(OperationError.invalidApp)) }
|
||||
|
||||
let appName = installedApp.name
|
||||
let openAppURL = installedApp.openAppURL
|
||||
|
||||
ServerManager.shared.connect(to: server) { result in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(let connection):
|
||||
print("Sending enable JIT request...")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let v = minimuxer_to_operation(code: 1)
|
||||
|
||||
// Launch app to make sure it is running in foreground.
|
||||
UIApplication.shared.open(openAppURL) { success in
|
||||
guard success else { return self.finish(.failure(OperationError.openAppFailed(name: appName))) }
|
||||
|
||||
// Combine immediately finishes if an error is thrown, but we want to wait at least until app enters background.
|
||||
// As a workaround, we set error type to Never and use Result<Void, Error> as the value type instead.
|
||||
let result = Future<Result<Void, Error>, Never> { promise in
|
||||
let request = EnableUnsignedCodeExecutionRequest(udid: udid, processName: processName)
|
||||
connection.send(request) { result in
|
||||
print("Sent enable JIT request!")
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): promise(.success(.failure(error)))
|
||||
case .success:
|
||||
print("Waiting for enable JIT response...")
|
||||
connection.receiveResponse() { result in
|
||||
print("Received enable JIT response:", result)
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): promise(.success(.failure(error)))
|
||||
case .success(.error(let response)): promise(.success(.failure(response.error)))
|
||||
case .success(.enableUnsignedCodeExecution): promise(.success(.success(())))
|
||||
case .success: promise(.success(.failure(ALTServerError(.unknownResponse))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Handle case where app does not enter background (e.g. iPad multitasking).
|
||||
self.cancellable = result
|
||||
.combineLatest(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification, object: nil))
|
||||
.first()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { (result, _) in
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
content.title = String(format: NSLocalizedString("Could not enable JIT for %@", comment: ""), appName)
|
||||
content.body = error.localizedDescription
|
||||
|
||||
UIDevice.current.vibrate(pattern: .error)
|
||||
|
||||
case .success:
|
||||
content.title = String(format: NSLocalizedString("Enabled JIT for %@", comment: ""), appName)
|
||||
content.body = String(format: NSLocalizedString("JIT will remain enabled until you quit the app.", comment: ""))
|
||||
|
||||
UIDevice.current.vibrate(pattern: .success)
|
||||
}
|
||||
|
||||
if UIApplication.shared.applicationState == .background
|
||||
{
|
||||
// For some reason, notification won't show up reliably unless we provide a trigger (as of iOS 15).
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
|
||||
|
||||
let request = UNNotificationRequest(identifier: AppManager.enableJITResultNotificationID, content: content, trigger: trigger)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
}
|
||||
|
||||
self.finish(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
var x = try debug_app(app_id: installedApp.resignedBundleIdentifier)
|
||||
switch x {
|
||||
case .Good:
|
||||
self.finish(.success(()))
|
||||
case .Bad(let code):
|
||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
||||
}
|
||||
} catch Uhoh.Bad(let code) {
|
||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
||||
} catch {
|
||||
self.finish(.failure(OperationError.unknown))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
//
|
||||
// FindServerOperation.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 9/8/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
private let ReceivedServerConnectionResponse: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
||||
{ (center, observer, name, object, userInfo) in
|
||||
guard let name = name, let observer = observer else { return }
|
||||
|
||||
let operation = unsafeBitCast(observer, to: FindServerOperation.self)
|
||||
operation.handle(name)
|
||||
}
|
||||
|
||||
@objc(FindServerOperation)
|
||||
class FindServerOperation: ResultOperation<Server>
|
||||
{
|
||||
let context: OperationContext
|
||||
|
||||
private var isWiredServerConnectionAvailable = false
|
||||
private var localServerMachServiceName: String?
|
||||
|
||||
init(context: OperationContext = OperationContext())
|
||||
{
|
||||
self.context = context
|
||||
}
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
if let server = self.context.server
|
||||
{
|
||||
self.finish(.success(server))
|
||||
return
|
||||
}
|
||||
|
||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||
|
||||
// Prepare observers to receive callback from wired connection or background daemon (if available).
|
||||
CFNotificationCenterAddObserver(notificationCenter, observer, ReceivedServerConnectionResponse, CFNotificationName.wiredServerConnectionAvailableResponse.rawValue, nil, .deliverImmediately)
|
||||
|
||||
// Post notifications.
|
||||
CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionAvailableRequest, nil, nil, true)
|
||||
|
||||
self.discoverLocalServer()
|
||||
|
||||
// Wait for either callback or timeout.
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
||||
if let machServiceName = self.localServerMachServiceName
|
||||
{
|
||||
// Prefer background daemon, if it exists and is running.
|
||||
let server = Server(connectionType: .local, machServiceName: machServiceName)
|
||||
self.finish(.success(server))
|
||||
}
|
||||
else if self.isWiredServerConnectionAvailable
|
||||
{
|
||||
let server = Server(connectionType: .wired)
|
||||
self.finish(.success(server))
|
||||
}
|
||||
else if let server = ServerManager.shared.discoveredServers.first(where: { $0.isPreferred })
|
||||
{
|
||||
// Preferred server.
|
||||
self.finish(.success(server))
|
||||
}
|
||||
else if let server = ServerManager.shared.discoveredServers.first
|
||||
{
|
||||
// Any available server.
|
||||
self.finish(.success(server))
|
||||
}
|
||||
else
|
||||
{
|
||||
// No servers.
|
||||
self.finish(.failure(ConnectionError.serverNotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func finish(_ result: Result<Server, Error>)
|
||||
{
|
||||
super.finish(result)
|
||||
|
||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||
|
||||
CFNotificationCenterRemoveObserver(notificationCenter, observer, .wiredServerConnectionAvailableResponse, nil)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension FindServerOperation
|
||||
{
|
||||
func discoverLocalServer()
|
||||
{
|
||||
for machServiceName in XPCConnection.machServiceNames
|
||||
{
|
||||
let xpcConnection = NSXPCConnection.makeConnection(machServiceName: machServiceName)
|
||||
|
||||
let connection = XPCConnection(xpcConnection)
|
||||
connection.connect { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): print("Could not connect to AltDaemon XPC service \(machServiceName).", error)
|
||||
case .success: self.localServerMachServiceName = machServiceName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handle(_ notification: CFNotificationName)
|
||||
{
|
||||
switch notification
|
||||
{
|
||||
case .wiredServerConnectionAvailableResponse: self.isWiredServerConnectionAvailable = true
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
// Created by Riley Testut on 6/19/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
@@ -41,8 +40,7 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
guard
|
||||
let certificate = self.context.certificate,
|
||||
let resignedApp = self.context.resignedApp,
|
||||
let connection = self.context.installationConnection
|
||||
let resignedApp = self.context.resignedApp
|
||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
@@ -145,27 +143,16 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
})
|
||||
}
|
||||
|
||||
let request = BeginInstallationRequest(activeProfiles: activeProfiles, bundleIdentifier: installedApp.resignedBundleIdentifier)
|
||||
connection.send(request) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success:
|
||||
|
||||
self.receive(from: connection) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .success:
|
||||
backgroundContext.perform {
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
let ns_bundle = NSString(string: installedApp.bundleIdentifier)
|
||||
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
|
||||
|
||||
let res = minimuxer_install_ipa(ns_bundle_ptr)
|
||||
if res == 0 {
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
|
||||
} else {
|
||||
self.finish(.failure(minimuxer_to_operation(code: res)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,42 +182,6 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
private extension InstallAppOperation
|
||||
{
|
||||
func receive(from connection: ServerConnection, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
connection.receiveResponse() { (result) in
|
||||
do
|
||||
{
|
||||
let response = try result.get()
|
||||
print(response)
|
||||
|
||||
switch response
|
||||
{
|
||||
case .installationProgress(let response):
|
||||
if response.progress == 1.0
|
||||
{
|
||||
self.progress.completedUnitCount = self.progress.totalUnitCount
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
else
|
||||
{
|
||||
self.progress.completedUnitCount = Int64(response.progress * 100)
|
||||
self.receive(from: connection, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
case .error(let response):
|
||||
completionHandler(.failure(response.error))
|
||||
|
||||
default:
|
||||
completionHandler(.failure(ALTServerError(.unknownRequest)))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cleanUp()
|
||||
{
|
||||
guard !self.didCleanUp else { return }
|
||||
|
||||
@@ -15,16 +15,14 @@ import AltSign
|
||||
|
||||
class OperationContext
|
||||
{
|
||||
var server: Server?
|
||||
var error: Error?
|
||||
|
||||
var presentingViewController: UIViewController?
|
||||
|
||||
let operations: NSHashTable<Foundation.Operation>
|
||||
|
||||
init(server: Server? = nil, error: Error? = nil, operations: [Foundation.Operation] = [])
|
||||
init(error: Error? = nil, operations: [Foundation.Operation] = [])
|
||||
{
|
||||
self.server = server
|
||||
self.error = error
|
||||
|
||||
self.operations = NSHashTable<Foundation.Operation>.weakObjects()
|
||||
@@ -36,7 +34,7 @@ class OperationContext
|
||||
|
||||
convenience init(context: OperationContext)
|
||||
{
|
||||
self.init(server: context.server, error: context.error, operations: context.operations.allObjects)
|
||||
self.init(error: context.error, operations: context.operations.allObjects)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +49,7 @@ class AuthenticatedOperationContext: OperationContext
|
||||
|
||||
convenience init(context: AuthenticatedOperationContext)
|
||||
{
|
||||
self.init(server: context.server, error: context.error, operations: context.operations.allObjects)
|
||||
self.init(error: context.error, operations: context.operations.allObjects)
|
||||
|
||||
self.session = context.session
|
||||
self.team = context.team
|
||||
@@ -105,7 +103,6 @@ class InstallAppOperationContext: AppOperationContext
|
||||
}()
|
||||
|
||||
var resignedApp: ALTApplication?
|
||||
var installationConnection: ServerConnection?
|
||||
var installedApp: InstalledApp? {
|
||||
didSet {
|
||||
self.installedAppContext = self.installedApp?.managedObjectContext
|
||||
|
||||
@@ -31,6 +31,19 @@ enum OperationError: LocalizedError
|
||||
case openAppFailed(name: String)
|
||||
case missingAppGroup
|
||||
|
||||
case noDevice
|
||||
case createService(name: String)
|
||||
case getFromDevice(name: String)
|
||||
case setArgument(name: String)
|
||||
case afc
|
||||
case install
|
||||
case uninstall
|
||||
case lookupApps
|
||||
case detach
|
||||
case functionArguments
|
||||
case profileInstall
|
||||
case noConnection
|
||||
|
||||
var failureReason: String? {
|
||||
switch self {
|
||||
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
||||
@@ -46,6 +59,18 @@ enum OperationError: LocalizedError
|
||||
case .openAppFailed(let name): return String(format: NSLocalizedString("AltStore was denied permission to launch %@.", comment: ""), name)
|
||||
case .missingAppGroup: return NSLocalizedString("AltStore's shared app group could not be found.", comment: "")
|
||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
||||
case .noDevice: return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||
case .createService(let name): return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
|
||||
case .getFromDevice(let name): return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
|
||||
case .setArgument(let name): return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
|
||||
case .afc: return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
||||
case .install: return NSLocalizedString("Unable to install the app from the staging directory", comment: "")
|
||||
case .uninstall: return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||
case .lookupApps: return NSLocalizedString("Unable to fetch apps from the device", comment: "")
|
||||
case .detach: return NSLocalizedString("Unable to detach from the app's process", comment: "")
|
||||
case .functionArguments: return NSLocalizedString("A function was passed invalid arguments", comment: "")
|
||||
case .profileInstall: return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .noConnection: return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,3 +115,50 @@ enum OperationError: LocalizedError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func minimuxer_to_operation(code: Int32) -> OperationError {
|
||||
switch code {
|
||||
case -1:
|
||||
return OperationError.noDevice
|
||||
case -2:
|
||||
return OperationError.createService(name: "debug")
|
||||
case -3:
|
||||
return OperationError.createService(name: "instproxy")
|
||||
case -4:
|
||||
return OperationError.getFromDevice(name: "installed apps")
|
||||
case -5:
|
||||
return OperationError.getFromDevice(name: "path to the app")
|
||||
case -6:
|
||||
return OperationError.getFromDevice(name: "bundle path")
|
||||
case -7:
|
||||
return OperationError.setArgument(name: "max packet")
|
||||
case -8:
|
||||
return OperationError.setArgument(name: "working directory")
|
||||
case -9:
|
||||
return OperationError.setArgument(name: "argv")
|
||||
case -10:
|
||||
return OperationError.getFromDevice(name: "launch success")
|
||||
case -11:
|
||||
return OperationError.detach
|
||||
case -12:
|
||||
return OperationError.functionArguments
|
||||
case -13:
|
||||
return OperationError.createService(name: "AFC")
|
||||
case -14:
|
||||
return OperationError.afc
|
||||
case -15:
|
||||
return OperationError.install
|
||||
case -16:
|
||||
return OperationError.uninstall
|
||||
case -17:
|
||||
return OperationError.createService(name: "misagent")
|
||||
case -18:
|
||||
return OperationError.profileInstall
|
||||
case -19:
|
||||
return OperationError.profileInstall
|
||||
case -20:
|
||||
return OperationError.noConnection
|
||||
default:
|
||||
return OperationError.unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import Foundation
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import minimuxer
|
||||
|
||||
@objc(RefreshAppOperation)
|
||||
class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
@@ -39,75 +40,37 @@ class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let server = self.context.server, let profiles = self.context.provisioningProfiles else { throw OperationError.invalidParameters }
|
||||
guard let profiles = self.context.provisioningProfiles else { throw OperationError.invalidParameters }
|
||||
|
||||
guard let app = self.context.app else { throw OperationError.appNotFound }
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||
|
||||
ServerManager.shared.connect(to: server) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(let connection):
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
print("Sending refresh app request...")
|
||||
|
||||
var activeProfiles: Set<String>?
|
||||
if UserDefaults.standard.activeAppsLimit != nil
|
||||
{
|
||||
// When installing these new profiles, AltServer will remove all non-active profiles to ensure we remain under limit.
|
||||
let activeApps = InstalledApp.fetchActiveApps(in: context)
|
||||
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in
|
||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
||||
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||
})
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
print("Sending refresh app request...")
|
||||
|
||||
for p in profiles {
|
||||
do {
|
||||
let x = try install_provisioning_profile(plist: p.value.data)
|
||||
if case .Bad(let code) = x {
|
||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
||||
}
|
||||
|
||||
let request = InstallProvisioningProfilesRequest(udid: udid, provisioningProfiles: Set(profiles.values), activeProfiles: activeProfiles)
|
||||
connection.send(request) { (result) in
|
||||
print("Sent refresh app request!")
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success:
|
||||
print("Waiting for refresh app response...")
|
||||
connection.receiveResponse() { (result) in
|
||||
print("Receiving refresh app response:", result)
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(.error(let response)): self.finish(.failure(response.error))
|
||||
|
||||
case .success(.installProvisioningProfiles):
|
||||
self.managedObjectContext.perform {
|
||||
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
||||
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
||||
return self.finish(.failure(OperationError.appNotFound))
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
if let provisioningProfile = profiles[app.bundleIdentifier]
|
||||
{
|
||||
installedApp.update(provisioningProfile: provisioningProfile)
|
||||
}
|
||||
|
||||
for installedExtension in installedApp.appExtensions
|
||||
{
|
||||
guard let provisioningProfile = profiles[installedExtension.bundleIdentifier] else { continue }
|
||||
installedExtension.update(provisioningProfile: provisioningProfile)
|
||||
}
|
||||
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
|
||||
case .success: self.finish(.failure(ALTServerError(.unknownRequest)))
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch Uhoh.Bad(let code) {
|
||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
||||
} catch {
|
||||
self.finish(.failure(OperationError.unknown))
|
||||
}
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
||||
self.managedObjectContext.perform {
|
||||
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
||||
return
|
||||
}
|
||||
installedApp.update(provisioningProfile: p.value)
|
||||
for installedExtension in installedApp.appExtensions {
|
||||
guard let provisioningProfile = profiles[installedExtension.bundleIdentifier] else { continue }
|
||||
installedExtension.update(provisioningProfile: provisioningProfile)
|
||||
}
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
import AltStoreCore
|
||||
import minimuxer
|
||||
|
||||
@objc(RemoveAppOperation)
|
||||
class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||
@@ -32,50 +33,27 @@ class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||
return
|
||||
}
|
||||
|
||||
guard let server = self.context.server, let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { return self.finish(.failure(OperationError.unknownUDID)) }
|
||||
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
|
||||
|
||||
ServerManager.shared.connect(to: server) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(let connection):
|
||||
print("Sending remove app request...")
|
||||
|
||||
let request = RemoveAppRequest(udid: udid, bundleIdentifier: resignedBundleIdentifier)
|
||||
connection.send(request) { (result) in
|
||||
print("Sent remove app request!")
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success:
|
||||
print("Waiting for remove app response...")
|
||||
connection.receiveResponse() { (result) in
|
||||
print("Receiving remove app response:", result)
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(.error(let response)): self.finish(.failure(response.error))
|
||||
case .success(.removeApp):
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
let installedApp = context.object(with: installedApp.objectID) as! InstalledApp
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
|
||||
case .success: self.finish(.failure(ALTServerError(.unknownResponse)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
let res = try remove_app(app_id: resignedBundleIdentifier)
|
||||
if case Uhoh.Bad(let code) = res {
|
||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
||||
}
|
||||
} catch Uhoh.Bad(let code) {
|
||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
||||
} catch {
|
||||
self.finish(.failure(ALTServerError(.appDeletionFailed)))
|
||||
}
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
let installedApp = context.object(with: installedApp.objectID) as! InstalledApp
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ private extension ResignAppOperation
|
||||
|
||||
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
||||
infoDictionary[Bundle.Info.altBundleID] = identifier
|
||||
infoDictionary[Bundle.Info.devicePairingString] = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String
|
||||
|
||||
for (key, value) in additionalInfoDictionaryValues
|
||||
{
|
||||
@@ -172,6 +173,8 @@ private extension ResignAppOperation
|
||||
if app.isAltStoreApp
|
||||
{
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
||||
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
|
||||
|
||||
|
||||
@@ -5,20 +5,17 @@
|
||||
// Created by Riley Testut on 6/7/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
@objc(SendAppOperation)
|
||||
class SendAppOperation: ResultOperation<ServerConnection>
|
||||
class SendAppOperation: ResultOperation<()>
|
||||
{
|
||||
let context: InstallAppOperationContext
|
||||
|
||||
private let dispatchQueue = DispatchQueue(label: "com.altstore.SendAppOperation")
|
||||
|
||||
private var serverConnection: ServerConnection?
|
||||
private let dispatchQueue = DispatchQueue(label: "com.sidestore.SendAppOperation")
|
||||
|
||||
init(context: InstallAppOperationContext)
|
||||
{
|
||||
@@ -39,87 +36,31 @@ class SendAppOperation: ResultOperation<ServerConnection>
|
||||
return
|
||||
}
|
||||
|
||||
guard let resignedApp = self.context.resignedApp, let server = self.context.server else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
guard let resignedApp = self.context.resignedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
// self.context.resignedApp.fileURL points to the app bundle, but we want the .ipa.
|
||||
let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.url)
|
||||
let fileURL = InstalledApp.refreshedIPAURL(for: app)
|
||||
|
||||
// Connect to server.
|
||||
ServerManager.shared.connect(to: server) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(let serverConnection):
|
||||
self.serverConnection = serverConnection
|
||||
|
||||
// Send app to server.
|
||||
self.sendApp(at: fileURL, via: serverConnection) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success:
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(serverConnection))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ns_bundle = NSString(string: app.bundleIdentifier)
|
||||
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
|
||||
|
||||
private extension SendAppOperation
|
||||
{
|
||||
func sendApp(at fileURL: URL, via connection: ServerConnection, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
do
|
||||
{
|
||||
guard let appData = try? Data(contentsOf: fileURL) else { throw OperationError.invalidApp }
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||
|
||||
var request = PrepareAppRequest(udid: udid, contentSize: appData.count)
|
||||
|
||||
if connection.server.connectionType == .local
|
||||
{
|
||||
// Background daemons have low memory limit (~6MB as of 13.5),
|
||||
// so send just the file URL rather than the app data itself.
|
||||
request.fileURL = fileURL
|
||||
if let data = NSData(contentsOf: fileURL) {
|
||||
let pls = UnsafeMutablePointer<UInt8>.allocate(capacity: data.length)
|
||||
for (index, data) in data.enumerated() {
|
||||
pls[index] = data
|
||||
}
|
||||
let res = minimuxer_yeet_app_afc(ns_bundle_ptr, pls, UInt(data.length))
|
||||
if res == 0 {
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} else {
|
||||
self.finish(.failure(minimuxer_to_operation(code: res)))
|
||||
}
|
||||
|
||||
connection.send(request) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
case .success:
|
||||
|
||||
if connection.server.connectionType == .local
|
||||
{
|
||||
// Sent file URL, so don't need to send any more.
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
else
|
||||
{
|
||||
print("Sending app data (\(appData.count) bytes)...")
|
||||
|
||||
connection.send(appData, prependSize: false) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
print("Failed to send app data (\(appData.count) bytes)")
|
||||
completionHandler(.failure(error))
|
||||
|
||||
case .success:
|
||||
print("Successfully sent app data (\(appData.count) bytes)")
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
} else {
|
||||
self.finish(.failure(ALTServerError(.underlyingError)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
import AltStoreCore
|
||||
import EmotionalDamage
|
||||
|
||||
@available(iOS 13, *)
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
||||
@@ -39,7 +40,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
||||
guard DatabaseManager.shared.isStarted else { return }
|
||||
|
||||
AppManager.shared.update()
|
||||
ServerManager.shared.startDiscovering()
|
||||
start_em_proxy(bind_addr: "127.0.0.1:51820")
|
||||
|
||||
PatreonAPI.shared.refreshPatreonAccount()
|
||||
}
|
||||
@@ -52,7 +53,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
||||
|
||||
guard UIApplication.shared.applicationState == .background else { return }
|
||||
|
||||
ServerManager.shared.stopDiscovering()
|
||||
|
||||
}
|
||||
|
||||
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
//
|
||||
// Server.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 6/20/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Network
|
||||
|
||||
enum ConnectionError: LocalizedError
|
||||
{
|
||||
case serverNotFound
|
||||
case connectionFailed
|
||||
case connectionDropped
|
||||
|
||||
var failureReason: String? {
|
||||
switch self
|
||||
{
|
||||
case .serverNotFound: return NSLocalizedString("Could not find AltServer.", comment: "")
|
||||
case .connectionFailed: return NSLocalizedString("Could not connect to AltServer.", comment: "")
|
||||
case .connectionDropped: return NSLocalizedString("The connection to AltServer was dropped.", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Server
|
||||
{
|
||||
enum ConnectionType
|
||||
{
|
||||
case wireless
|
||||
case wired
|
||||
case local
|
||||
case manual
|
||||
}
|
||||
}
|
||||
|
||||
struct Server: Equatable
|
||||
{
|
||||
var identifier: String? = nil
|
||||
var service: NetService? = nil
|
||||
|
||||
var isPreferred = false
|
||||
var connectionType: ConnectionType = .wireless
|
||||
|
||||
var machServiceName: String?
|
||||
}
|
||||
|
||||
extension Server
|
||||
{
|
||||
// Defined in extension so we can still use the automatically synthesized initializer.
|
||||
init?(service: NetService, txtData: Data) // TODO: this is all that's needed for a server connection
|
||||
{
|
||||
let txtDictionary = NetService.dictionary(fromTXTRecord: txtData)
|
||||
guard let identifierData = txtDictionary["serverID"], let identifier = String(data: identifierData, encoding: .utf8) else {
|
||||
NSLog("Ahh, no serverID in TXT record for service: \(service)")
|
||||
return nil
|
||||
}
|
||||
|
||||
self.service = service
|
||||
self.identifier = identifier
|
||||
self.isPreferred = true
|
||||
}
|
||||
|
||||
init?(service: NetService)
|
||||
{
|
||||
self.service = service
|
||||
self.connectionType = .manual
|
||||
self.identifier = String(data: "yolo".data(using: .utf8)!, encoding: .utf8)
|
||||
self.isPreferred = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
//
|
||||
// ServerConnection.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 1/7/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
class ServerConnection
|
||||
{
|
||||
var server: Server
|
||||
var connection: Connection
|
||||
|
||||
init(server: Server, connection: Connection)
|
||||
{
|
||||
self.server = server
|
||||
self.connection = connection
|
||||
}
|
||||
|
||||
func send<T: Encodable>(_ payload: T, prependSize: Bool = true, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
do
|
||||
{
|
||||
let data: Data
|
||||
|
||||
if let payload = payload as? Data
|
||||
{
|
||||
data = payload
|
||||
}
|
||||
else
|
||||
{
|
||||
data = try JSONEncoder().encode(payload)
|
||||
}
|
||||
|
||||
func process<T>(_ result: Result<T, ALTServerError>) -> Bool
|
||||
{
|
||||
switch result
|
||||
{
|
||||
case .success: return true
|
||||
case .failure(let error):
|
||||
completionHandler(.failure(error))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if prependSize
|
||||
{
|
||||
let requestSize = Int32(data.count)
|
||||
let requestSizeData = withUnsafeBytes(of: requestSize) { Data($0) }
|
||||
|
||||
self.connection.send(requestSizeData) { (result) in
|
||||
guard process(result) else { return }
|
||||
|
||||
self.connection.send(data) { (result) in
|
||||
guard process(result) else { return }
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.connection.send(data) { (result) in
|
||||
guard process(result) else { return }
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Invalid request.", error)
|
||||
completionHandler(.failure(ALTServerError(.invalidRequest)))
|
||||
}
|
||||
}
|
||||
|
||||
func receiveResponse(completionHandler: @escaping (Result<ServerResponse, Error>) -> Void)
|
||||
{
|
||||
let size = MemoryLayout<Int32>.size
|
||||
|
||||
self.connection.receiveData(expectedSize: size) { (result) in
|
||||
do
|
||||
{
|
||||
let data = try result.get()
|
||||
|
||||
let expectedBytes = Int(data.withUnsafeBytes { $0.load(as: Int32.self) })
|
||||
self.connection.receiveData(expectedSize: expectedBytes) { (result) in
|
||||
do
|
||||
{
|
||||
let data = try result.get()
|
||||
|
||||
let response = try AltStoreCore.JSONDecoder().decode(ServerResponse.self, from: data)
|
||||
completionHandler(.success(response))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
//
|
||||
// ServerManager.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 5/30/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
class ServerManager: NSObject
|
||||
{
|
||||
static let shared = ServerManager()
|
||||
|
||||
private(set) var isDiscovering = false
|
||||
private(set) var discoveredServers = [Server]()
|
||||
|
||||
private let serviceBrowser = NetServiceBrowser()
|
||||
private var services = Set<NetService>()
|
||||
|
||||
private let dispatchQueue = DispatchQueue(label: "io.altstore.ServerManager")
|
||||
|
||||
private var connectionListener: NWListener?
|
||||
private var incomingConnections: [NWConnection]?
|
||||
private var incomingConnectionsSemaphore: DispatchSemaphore?
|
||||
|
||||
private override init()
|
||||
{
|
||||
super.init()
|
||||
|
||||
self.serviceBrowser.delegate = self
|
||||
self.serviceBrowser.includesPeerToPeer = false
|
||||
}
|
||||
}
|
||||
|
||||
extension ServerManager
|
||||
{
|
||||
func startDiscovering()
|
||||
{
|
||||
guard !self.isDiscovering else { return }
|
||||
self.isDiscovering = true
|
||||
|
||||
self.serviceBrowser.searchForServices(ofType: ALTServerServiceType, inDomain: "")
|
||||
|
||||
self.startListeningForWiredConnections()
|
||||
|
||||
// Print log mentioning that we are manually adding this
|
||||
NSLog("Manually adding server")
|
||||
let ianTestService = NetService(domain: "69.69.0.1", type: "_altserver._tcp", name: "AltStore", port: 43311)
|
||||
|
||||
if let server = Server(service: ianTestService)
|
||||
{
|
||||
self.addDiscoveredServer(server)
|
||||
} else {
|
||||
NSLog("Check for manual server failed!!")
|
||||
}
|
||||
}
|
||||
|
||||
func stopDiscovering()
|
||||
{
|
||||
guard self.isDiscovering else { return }
|
||||
self.isDiscovering = false
|
||||
|
||||
self.discoveredServers.removeAll()
|
||||
self.services.removeAll()
|
||||
self.serviceBrowser.stop()
|
||||
|
||||
self.stopListeningForWiredConnection()
|
||||
}
|
||||
|
||||
func connect(to server: Server, completion: @escaping (Result<ServerConnection, Error>) -> Void)
|
||||
{
|
||||
DispatchQueue.global().async {
|
||||
func finish(_ result: Result<Connection, Error>)
|
||||
{
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): completion(.failure(error))
|
||||
case .success(let connection):
|
||||
let serverConnection = ServerConnection(server: server, connection: connection)
|
||||
completion(.success(serverConnection))
|
||||
}
|
||||
}
|
||||
|
||||
switch server.connectionType
|
||||
{
|
||||
case .local: self.connectToLocalServer(server, completion: finish(_:))
|
||||
case .wired:
|
||||
guard let incomingConnectionsSemaphore = self.incomingConnectionsSemaphore else { return
|
||||
finish(.failure(ALTServerError(.connectionFailed))) }
|
||||
|
||||
print("Waiting for incoming connection...")
|
||||
|
||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||
|
||||
switch server.connectionType
|
||||
{
|
||||
case .wired: CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionStartRequest, nil, nil, true)
|
||||
case .local, .wireless, .manual: break
|
||||
}
|
||||
|
||||
_ = incomingConnectionsSemaphore.wait(timeout: .now() + 10.0)
|
||||
|
||||
if let connection = self.incomingConnections?.popLast()
|
||||
{
|
||||
self.connectToRemoteServer(server, connection: connection, completion: finish(_:))
|
||||
}
|
||||
else
|
||||
{
|
||||
finish(.failure(ALTServerError(.connectionFailed)))
|
||||
}
|
||||
|
||||
case .wireless:
|
||||
guard let service = server.service else { return finish(.failure(ALTServerError(.connectionFailed))) }
|
||||
|
||||
print("Connecting to mDNS service:", service)
|
||||
|
||||
let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: .tcp)
|
||||
self.connectToRemoteServer(server, connection: connection, completion: finish(_:))
|
||||
case .manual:
|
||||
guard let service = server.service else { return finish(.failure(ALTServerError(.connectionFailed))) }
|
||||
|
||||
connectNetmuxd()
|
||||
|
||||
print("Connecting to manual service:", service.domain)
|
||||
print("Port: ", String(service.port.description))
|
||||
|
||||
let connection = NWConnection(host: NWEndpoint.Host(service.domain), port: NWEndpoint.Port(String(service.port))!, using: .tcp)
|
||||
self.connectToRemoteServer(server, connection: connection, completion: finish(_:))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ServerManager
|
||||
{
|
||||
func addDiscoveredServer(_ server: Server)
|
||||
{
|
||||
var server = server
|
||||
server.isPreferred = true
|
||||
|
||||
guard !self.discoveredServers.contains(server) else { return }
|
||||
|
||||
self.discoveredServers.append(server)
|
||||
}
|
||||
|
||||
func makeListener() -> NWListener
|
||||
{
|
||||
let listener = try! NWListener(using: .tcp, on: NWEndpoint.Port(rawValue: ALTDeviceListeningSocket)!)
|
||||
listener.newConnectionHandler = { [weak self] (connection) in
|
||||
self?.incomingConnections?.append(connection)
|
||||
self?.incomingConnectionsSemaphore?.signal()
|
||||
}
|
||||
listener.stateUpdateHandler = { (state) in
|
||||
switch state
|
||||
{
|
||||
case .ready: break
|
||||
case .waiting, .setup: print("Listener socket waiting...")
|
||||
case .cancelled: print("Listener socket cancelled.")
|
||||
case .failed(let error): print("Listener socket failed:", error)
|
||||
@unknown default: break
|
||||
}
|
||||
}
|
||||
|
||||
return listener
|
||||
}
|
||||
|
||||
func startListeningForWiredConnections()
|
||||
{
|
||||
self.incomingConnections = []
|
||||
self.incomingConnectionsSemaphore = DispatchSemaphore(value: 0)
|
||||
|
||||
self.connectionListener = self.makeListener()
|
||||
self.connectionListener?.start(queue: self.dispatchQueue)
|
||||
}
|
||||
|
||||
func stopListeningForWiredConnection()
|
||||
{
|
||||
self.connectionListener?.cancel()
|
||||
self.connectionListener = nil
|
||||
|
||||
self.incomingConnections = nil
|
||||
self.incomingConnectionsSemaphore = nil
|
||||
}
|
||||
|
||||
func connectToRemoteServer(_ server: Server, connection: NWConnection, completion: @escaping (Result<Connection, Error>) -> Void)
|
||||
{
|
||||
connection.stateUpdateHandler = { [unowned connection] (state) in
|
||||
switch state
|
||||
{
|
||||
case .failed(let error):
|
||||
print("Failed to connect to service \(server.service?.name ?? "").", error)
|
||||
completion(.failure(ConnectionError.connectionFailed))
|
||||
|
||||
case .cancelled:
|
||||
completion(.failure(OperationError.cancelled))
|
||||
|
||||
case .ready:
|
||||
let connection = NetworkConnection(connection)
|
||||
completion(.success(connection))
|
||||
|
||||
case .waiting: break
|
||||
case .setup: break
|
||||
case .preparing: break
|
||||
@unknown default: break
|
||||
}
|
||||
}
|
||||
print("Connected to server!")
|
||||
connection.start(queue: self.dispatchQueue)
|
||||
}
|
||||
|
||||
func connectToLocalServer(_ server: Server, completion: @escaping (Result<Connection, Error>) -> Void)
|
||||
{
|
||||
guard let machServiceName = server.machServiceName else { return completion(.failure(ConnectionError.connectionFailed)) }
|
||||
|
||||
let xpcConnection = NSXPCConnection.makeConnection(machServiceName: machServiceName)
|
||||
|
||||
let connection = XPCConnection(xpcConnection)
|
||||
connection.connect { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
print("Could not connect to AltDaemon XPC service \(machServiceName).", error)
|
||||
completion(.failure(error))
|
||||
|
||||
case .success: completion(.success(connection))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ServerManager: NetServiceBrowserDelegate
|
||||
{
|
||||
func netServiceBrowserWillSearch(_ browser: NetServiceBrowser)
|
||||
{
|
||||
print("Discovering servers...")
|
||||
|
||||
// Send a post request to JitStreamer to deal with the devil
|
||||
connectNetmuxd()
|
||||
}
|
||||
|
||||
func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser)
|
||||
{
|
||||
print("Stopped discovering servers.")
|
||||
}
|
||||
|
||||
func netServiceBrowser(_ browser: NetServiceBrowser, didNotSearch errorDict: [String : NSNumber])
|
||||
{
|
||||
print("Failed to discovering servers.", errorDict)
|
||||
}
|
||||
|
||||
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool)
|
||||
{
|
||||
service.delegate = self
|
||||
|
||||
if let txtData = service.txtRecordData(), let server = Server(service: service, txtData: txtData)
|
||||
{
|
||||
self.addDiscoveredServer(server)
|
||||
}
|
||||
else
|
||||
{
|
||||
service.resolve(withTimeout: 3)
|
||||
self.services.insert(service)
|
||||
}
|
||||
}
|
||||
|
||||
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool)
|
||||
{
|
||||
if let index = self.discoveredServers.firstIndex(where: { $0.service == service })
|
||||
{
|
||||
self.discoveredServers.remove(at: index)
|
||||
}
|
||||
|
||||
self.services.remove(service)
|
||||
}
|
||||
}
|
||||
|
||||
extension ServerManager: NetServiceDelegate
|
||||
{
|
||||
func netServiceDidResolveAddress(_ service: NetService)
|
||||
{
|
||||
guard let data = service.txtRecordData(), let server = Server(service: service, txtData: data) else { return }
|
||||
self.addDiscoveredServer(server)
|
||||
}
|
||||
|
||||
func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber])
|
||||
{
|
||||
print("Error resolving net service \(sender).", errorDict)
|
||||
}
|
||||
|
||||
func netService(_ sender: NetService, didUpdateTXTRecord data: Data)
|
||||
{
|
||||
let txtDict = NetService.dictionary(fromTXTRecord: data)
|
||||
print("Service \(sender) updated TXT Record:", txtDict)
|
||||
}
|
||||
}
|
||||
|
||||
func connectNetmuxd() {
|
||||
|
||||
// declare the parameter as a dictionary that contains string as key and value combination. considering inputs are valid
|
||||
|
||||
let parameters: [String: Any] = ["nothing": 0]
|
||||
|
||||
// create the url with URL
|
||||
let url = URL(string: "http://69.69.0.1/netmuxd/")!
|
||||
|
||||
// create the session object
|
||||
let session = URLSession.shared
|
||||
|
||||
// now create the URLRequest object using the url object
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST" //set http method as POST
|
||||
|
||||
// add headers for the request
|
||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type") // change as per server requirements
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
||||
|
||||
do {
|
||||
// convert parameters to Data and assign dictionary to httpBody of request
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
|
||||
} catch let error {
|
||||
print(error.localizedDescription)
|
||||
return
|
||||
}
|
||||
|
||||
// create dataTask using the session object to send data to the server
|
||||
let task = session.dataTask(with: request) { data, response, error in
|
||||
|
||||
if let error = error {
|
||||
print("Post Request Error: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
// ensure there is valid response code returned from this HTTP response
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
(200...299).contains(httpResponse.statusCode)
|
||||
else {
|
||||
print("Invalid Response received from the server")
|
||||
return
|
||||
}
|
||||
|
||||
// ensure there is data returned
|
||||
guard let responseData = data else {
|
||||
print("nil Data received from the server")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
// create json object from data or use JSONDecoder to convert to Model stuct
|
||||
if let jsonResponse = try JSONSerialization.jsonObject(with: responseData, options: .mutableContainers) as? [String: Any] {
|
||||
print(jsonResponse)
|
||||
// handle json response
|
||||
} else {
|
||||
print("data maybe corrupted or in wrong format")
|
||||
throw URLError(.badServerResponse)
|
||||
}
|
||||
} catch let error {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
// perform the task
|
||||
task.resume()
|
||||
}
|
||||
Reference in New Issue
Block a user