mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-17 10:43:30 +01:00
Supports installing/refreshings apps w/o computer on jailbroken devices
AltStore will use AltDaemon as a local AltServer if it’s installed and running. AltStore remains a regular sandboxed app, but AltDaemon has private entitlements necessary to perform AltServer operations without a computer.
This commit is contained in:
@@ -1225,9 +1225,9 @@ private extension AppManager
|
|||||||
switch error.code
|
switch error.code
|
||||||
{
|
{
|
||||||
case .deviceNotFound, .lostConnection:
|
case .deviceNotFound, .lostConnection:
|
||||||
if let server = group.context.server, server.isPreferred || server.isWiredConnection
|
if let server = group.context.server, server.isPreferred || server.connectionType != .wireless
|
||||||
{
|
{
|
||||||
// Preferred server (or wired connection), so report errors normally.
|
// Preferred server (or not random wireless connection), so report errors normally.
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -10,14 +10,12 @@ import Foundation
|
|||||||
import AltKit
|
import AltKit
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
private extension Notification.Name
|
private let ReceivedServerConnectionResponse: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
||||||
{
|
|
||||||
static let didReceiveWiredServerConnectionResponse = Notification.Name("io.altstore.didReceiveWiredServerConnectionResponse")
|
|
||||||
}
|
|
||||||
|
|
||||||
private let ReceivedWiredServerConnectionResponse: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void =
|
|
||||||
{ (center, observer, name, object, userInfo) in
|
{ (center, observer, name, object, userInfo) in
|
||||||
NotificationCenter.default.post(name: .didReceiveWiredServerConnectionResponse, object: nil)
|
guard let name = name, let observer = observer else { return }
|
||||||
|
|
||||||
|
let operation = unsafeBitCast(observer, to: FindServerOperation.self)
|
||||||
|
operation.handle(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc(FindServerOperation)
|
@objc(FindServerOperation)
|
||||||
@@ -26,6 +24,7 @@ class FindServerOperation: ResultOperation<Server>
|
|||||||
let context: OperationContext
|
let context: OperationContext
|
||||||
|
|
||||||
private var isWiredServerConnectionAvailable = false
|
private var isWiredServerConnectionAvailable = false
|
||||||
|
private var isLocalServerConnectionAvailable = false
|
||||||
|
|
||||||
init(context: OperationContext = OperationContext())
|
init(context: OperationContext = OperationContext())
|
||||||
{
|
{
|
||||||
@@ -49,48 +48,68 @@ class FindServerOperation: ResultOperation<Server>
|
|||||||
}
|
}
|
||||||
|
|
||||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||||
|
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||||
|
|
||||||
// Prepare observers to receive callback from wired server (if connected).
|
// Prepare observers to receive callback from wired connection or background daemon (if available).
|
||||||
CFNotificationCenterAddObserver(notificationCenter, nil, ReceivedWiredServerConnectionResponse, CFNotificationName.wiredServerConnectionAvailableResponse.rawValue, nil, .deliverImmediately)
|
CFNotificationCenterAddObserver(notificationCenter, observer, ReceivedServerConnectionResponse, CFNotificationName.wiredServerConnectionAvailableResponse.rawValue, nil, .deliverImmediately)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(FindServerOperation.didReceiveWiredServerConnectionResponse(_:)), name: .didReceiveWiredServerConnectionResponse, object: nil)
|
CFNotificationCenterAddObserver(notificationCenter, observer, ReceivedServerConnectionResponse, CFNotificationName.localServerConnectionAvailableResponse.rawValue, nil, .deliverImmediately)
|
||||||
|
|
||||||
// Post notification.
|
// Post notifications.
|
||||||
CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionAvailableRequest, nil, nil, true)
|
CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionAvailableRequest, nil, nil, true)
|
||||||
|
CFNotificationCenterPostNotification(notificationCenter, .localServerConnectionAvailableRequest, nil, nil, true)
|
||||||
|
|
||||||
// Wait for either callback or timeout.
|
// Wait for either callback or timeout.
|
||||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
||||||
if self.isWiredServerConnectionAvailable
|
if self.isLocalServerConnectionAvailable
|
||||||
{
|
{
|
||||||
let server = Server(isWiredConnection: true)
|
// Prefer background daemon, if it exists and is running.
|
||||||
|
let server = Server(connectionType: .local)
|
||||||
|
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))
|
self.finish(.success(server))
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if let server = ServerManager.shared.discoveredServers.first(where: { $0.isPreferred })
|
// No servers.
|
||||||
{
|
self.finish(.failure(ConnectionError.serverNotFound))
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private extension FindServerOperation
|
override func finish(_ result: Result<Server, Error>)
|
||||||
{
|
|
||||||
@objc func didReceiveWiredServerConnectionResponse(_ notification: Notification)
|
|
||||||
{
|
{
|
||||||
self.isWiredServerConnectionAvailable = true
|
super.finish(result)
|
||||||
|
|
||||||
|
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||||
|
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||||
|
|
||||||
|
CFNotificationCenterRemoveObserver(notificationCenter, observer, .wiredServerConnectionAvailableResponse, nil)
|
||||||
|
CFNotificationCenterRemoveObserver(notificationCenter, observer, .localServerConnectionAvailableResponse, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate extension FindServerOperation
|
||||||
|
{
|
||||||
|
func handle(_ notification: CFNotificationName)
|
||||||
|
{
|
||||||
|
switch notification
|
||||||
|
{
|
||||||
|
case .wiredServerConnectionAvailableResponse: self.isWiredServerConnectionAvailable = true
|
||||||
|
case .localServerConnectionAvailableResponse: self.isLocalServerConnectionAvailable = true
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = BeginInstallationRequest(activeProfiles: activeProfiles)
|
let request = BeginInstallationRequest(activeProfiles: activeProfiles, bundleIdentifier: installedApp.resignedBundleIdentifier)
|
||||||
connection.send(request) { (result) in
|
connection.send(request) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
@@ -173,6 +173,21 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
{
|
{
|
||||||
self.cleanUp()
|
self.cleanUp()
|
||||||
|
|
||||||
|
// Only remove refreshed IPA when finished.
|
||||||
|
if let app = self.context.app
|
||||||
|
{
|
||||||
|
let fileURL = InstalledApp.refreshedIPAURL(for: app)
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Failed to remove refreshed .ipa:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
super.finish(result)
|
super.finish(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,12 +238,6 @@ private extension InstallAppOperation
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
try FileManager.default.removeItem(at: self.context.temporaryDirectory)
|
try FileManager.default.removeItem(at: self.context.temporaryDirectory)
|
||||||
|
|
||||||
if let app = self.context.app
|
|
||||||
{
|
|
||||||
let fileURL = InstalledApp.refreshedIPAURL(for: app)
|
|
||||||
try FileManager.default.removeItem(at: fileURL)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -76,26 +76,41 @@ private extension SendAppOperation
|
|||||||
guard let appData = try? Data(contentsOf: fileURL) else { throw OperationError.invalidApp }
|
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 }
|
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||||
|
|
||||||
let request = PrepareAppRequest(udid: udid, contentSize: appData.count)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
print("Sending request \(request)")
|
|
||||||
connection.send(request) { (result) in
|
connection.send(request) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure(let error): completionHandler(.failure(error))
|
||||||
case .success:
|
case .success:
|
||||||
|
|
||||||
print("Sending app data (\(appData.count) bytes)")
|
if connection.server.connectionType == .local
|
||||||
connection.send(appData, prependSize: false) { (result) in
|
{
|
||||||
switch result
|
// Sent file URL, so don't need to send any more.
|
||||||
{
|
completionHandler(.success(()))
|
||||||
case .failure(let error):
|
}
|
||||||
print("Failed to send app data (\(appData.count) bytes)")
|
else
|
||||||
completionHandler(.failure(error))
|
{
|
||||||
|
print("Sending app data (\(appData.count) bytes)...")
|
||||||
|
|
||||||
case .success:
|
connection.send(appData, prependSize: false) { (result) in
|
||||||
print("Successfully sent app data (\(appData.count) bytes)")
|
switch result
|
||||||
completionHandler(.success(()))
|
{
|
||||||
|
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(()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,22 +10,6 @@ import Network
|
|||||||
|
|
||||||
import AltKit
|
import AltKit
|
||||||
|
|
||||||
extension ALTServerError
|
|
||||||
{
|
|
||||||
init<E: Error>(_ error: E)
|
|
||||||
{
|
|
||||||
switch error
|
|
||||||
{
|
|
||||||
case let error as ALTServerError: self = error
|
|
||||||
case is DecodingError: self = ALTServerError(.invalidResponse)
|
|
||||||
case is EncodingError: self = ALTServerError(.invalidRequest)
|
|
||||||
default:
|
|
||||||
assertionFailure("Caught unknown error type")
|
|
||||||
self = ALTServerError(.unknown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ConnectionError: LocalizedError
|
enum ConnectionError: LocalizedError
|
||||||
{
|
{
|
||||||
case serverNotFound
|
case serverNotFound
|
||||||
@@ -42,13 +26,23 @@ enum ConnectionError: LocalizedError
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Server
|
||||||
|
{
|
||||||
|
enum ConnectionType
|
||||||
|
{
|
||||||
|
case wireless
|
||||||
|
case wired
|
||||||
|
case local
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Server: Equatable
|
struct Server: Equatable
|
||||||
{
|
{
|
||||||
var identifier: String? = nil
|
var identifier: String? = nil
|
||||||
var service: NetService? = nil
|
var service: NetService? = nil
|
||||||
|
|
||||||
var isPreferred = false
|
var isPreferred = false
|
||||||
var isWiredConnection = false
|
var connectionType: ConnectionType = .wireless
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Server
|
extension Server
|
||||||
|
|||||||
@@ -94,12 +94,18 @@ extension ServerManager
|
|||||||
connection.start(queue: self.dispatchQueue)
|
connection.start(queue: self.dispatchQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let incomingConnectionsSemaphore = self.incomingConnectionsSemaphore, server.isWiredConnection
|
if let incomingConnectionsSemaphore = self.incomingConnectionsSemaphore, server.connectionType != .wireless
|
||||||
{
|
{
|
||||||
print("Waiting for new wired connection...")
|
print("Waiting for incoming connection...")
|
||||||
|
|
||||||
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
||||||
CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionStartRequest, nil, nil, true)
|
|
||||||
|
switch server.connectionType
|
||||||
|
{
|
||||||
|
case .wired: CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionStartRequest, nil, nil, true)
|
||||||
|
case .local: CFNotificationCenterPostNotification(notificationCenter, .localServerConnectionStartRequest, nil, nil, true)
|
||||||
|
case .wireless: break
|
||||||
|
}
|
||||||
|
|
||||||
_ = incomingConnectionsSemaphore.wait(timeout: .now() + 10.0)
|
_ = incomingConnectionsSemaphore.wait(timeout: .now() + 10.0)
|
||||||
|
|
||||||
@@ -114,7 +120,17 @@ extension ServerManager
|
|||||||
}
|
}
|
||||||
else if let service = server.service
|
else if let service = server.service
|
||||||
{
|
{
|
||||||
let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: .tcp)
|
print("Connecting to service:", service)
|
||||||
|
|
||||||
|
let parameters = NWParameters.tcp
|
||||||
|
|
||||||
|
if server.connectionType == .local
|
||||||
|
{
|
||||||
|
// Prevent AltStore from initiating connections over multiple interfaces simultaneously 🤷♂️
|
||||||
|
parameters.requiredInterfaceType = .loopback
|
||||||
|
}
|
||||||
|
|
||||||
|
let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: parameters)
|
||||||
start(connection)
|
start(connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user