Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion StikJIT/StikJIT-Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#include "idevice/JITEnableContext.h"
#import "Utilities/ProcessInspectorBridge.h"
#include "idevice/idevice.h"
#include "idevice/heartbeat.h"
#include "JSSupport/JSSupport.h"
#include "idevice/ideviceinfo.h"
#include "idevice/location_simulation.h"
Expand Down
66 changes: 33 additions & 33 deletions StikJIT/StikJITApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,17 @@ class DNSChecker: ObservableObject {

// MARK: - Main App

// Global state variable for the heartbeat response.
var pubHeartBeat = false
private var heartbeatStartPending = false
private var heartbeatStartInProgress = false
private var heartbeatPendingShowUI = true
// Global state variable for the tunnel connection.
var pubTunnelConnected = false
private var tunnelStartPending = false
private var tunnelStartInProgress = false
private var tunnelPendingShowUI = true

@main
struct HeartbeatApp: App {
@StateObject private var mount = MountingProgress.shared
@Environment(\.scenePhase) private var scenePhase // Observe scene lifecycle
@State private var shouldAttemptHeartbeatRestart = false
@State private var shouldAttemptTunnelReconnect = false

init() {
registerAdvancedOptionsDefault()
Expand All @@ -144,11 +144,11 @@ struct HeartbeatApp: App {
private func handleScenePhaseChange(_ newPhase: ScenePhase) {
switch newPhase {
case .background:
shouldAttemptHeartbeatRestart = true
shouldAttemptTunnelReconnect = true
case .active:
if shouldAttemptHeartbeatRestart {
shouldAttemptHeartbeatRestart = false
startHeartbeatInBackground(showErrorUI: false)
if shouldAttemptTunnelReconnect {
shouldAttemptTunnelReconnect = false
startTunnelInBackground(showErrorUI: false)
}
default:
break
Expand Down Expand Up @@ -270,42 +270,42 @@ class MountingProgress: ObservableObject {

func isPairing() -> Bool {
let pairingpath = URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path
var pairingFile: IdevicePairingFile?
let err = idevice_pairing_file_read(pairingpath, &pairingFile)
var pairingFile: RpPairingFileHandle?
let err = rp_pairing_file_read(pairingpath, &pairingFile)
if err != nil { return false }
idevice_pairing_file_free(pairingFile)
rp_pairing_file_free(pairingFile)
return true
}

func startHeartbeatInBackground(showErrorUI: Bool = true) {
assert(Thread.isMainThread, "startHeartbeatInBackground must be called on the main thread")
func startTunnelInBackground(showErrorUI: Bool = true) {
assert(Thread.isMainThread, "startTunnelInBackground must be called on the main thread")
let pairingFileURL = URL.documentsDirectory.appendingPathComponent("pairingFile.plist")

guard FileManager.default.fileExists(atPath: pairingFileURL.path) else {
heartbeatStartPending = false
heartbeatPendingShowUI = true
tunnelStartPending = false
tunnelPendingShowUI = true
return
}
guard !heartbeatStartInProgress else {

guard !tunnelStartInProgress else {
return
}
heartbeatStartPending = false
heartbeatPendingShowUI = true
heartbeatStartInProgress = true

tunnelStartPending = false
tunnelPendingShowUI = true
tunnelStartInProgress = true

DispatchQueue.global(qos: .userInteractive).async {
defer {
DispatchQueue.main.async {
heartbeatStartInProgress = false
tunnelStartInProgress = false
}
}
do {
try JITEnableContext.shared.startHeartbeat()
LogManager.shared.addInfoLog("Heartbeat started successfully")
pubHeartBeat = true
try JITEnableContext.shared.startTunnel()
LogManager.shared.addInfoLog("Tunnel connected successfully")
pubTunnelConnected = true

DispatchQueue.main.async {
let trustcachePath = URL.documentsDirectory.appendingPathComponent("DDI/Image.dmg.trustcache").path
guard FileManager.default.fileExists(atPath: trustcachePath),
Expand All @@ -326,7 +326,7 @@ func startHeartbeatInBackground(showErrorUI: Bool = true) {
} catch {
LogManager.shared.addErrorLog("Failed to remove invalid pairing file: \(error.localizedDescription)")
}

showAlert(
title: "Invalid Pairing File",
message: "The pairing file is invalid or expired. Please select a new pairing file.",
Expand All @@ -338,14 +338,14 @@ func startHeartbeatInBackground(showErrorUI: Bool = true) {
}
} else {
showAlert(
title: "Heartbeat Error",
title: "Connection Error",
message: "\(error.localizedDescription)\n\nMake sure Wi‑Fi and LocalDevVPN are connected and that the device is reachable.",
showOk: false,
showTryAgain: true
) { shouldTryAgain in
if shouldTryAgain {
DispatchQueue.main.async {
startHeartbeatInBackground()
startTunnelInBackground()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion StikJIT/Utilities/DeviceInfoManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class DeviceInfoManager: ObservableObject {
busy = true
Task.detached {
do {
try JITEnableContext.shared.ensureHeartbeat()
try JITEnableContext.shared.ensureTunnel()
} catch {
await MainActor.run {
self.error = ("Initialization Failed", error.localizedDescription)
Expand Down
20 changes: 10 additions & 10 deletions StikJIT/Utilities/Intents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct InstalledAppQuery: EntityStringQuery {
}

func suggestedEntities() async throws -> [InstalledAppEntity] {
await ensureHeartbeat()
await ensureTunnel()
let allApps = (try? JITEnableContext.shared.getAppList()) ?? [:]
return allApps.map { InstalledAppEntity(id: $0.key, displayName: $0.value) }
.sorted { $0.displayName.localizedCaseInsensitiveCompare($1.displayName) == .orderedAscending }
Expand Down Expand Up @@ -94,7 +94,7 @@ struct RunningProcessEntity: AppEntity {
struct RunningProcessQuery: EntityStringQuery {
func entities(for identifiers: [String]) async throws -> [RunningProcessEntity] {
// Always fetch fresh so PIDs are current
await ensureHeartbeat()
await ensureTunnel()
let all = try fetchProcessEntities()
let idSet = Set(identifiers)
return all.filter { idSet.contains($0.id) }
Expand All @@ -112,7 +112,7 @@ struct RunningProcessQuery: EntityStringQuery {
}

func suggestedEntities() async throws -> [RunningProcessEntity] {
await ensureHeartbeat()
await ensureTunnel()
return try fetchProcessEntities()
}

Expand Down Expand Up @@ -172,7 +172,7 @@ struct EnableJITIntent: AppIntent, ForegroundContinuableIntent {
return .result(value: "Select an app to enable JIT for.")
}

await ensureHeartbeat()
await ensureTunnel()

var scriptData: Data? = nil
var scriptName: String? = nil
Expand Down Expand Up @@ -250,9 +250,9 @@ struct KillProcessIntent: AppIntent {
if let pid {
targetPID = pid
targetName = "PID \(pid)"
await ensureHeartbeat()
await ensureTunnel()
} else if let process {
await ensureHeartbeat()
await ensureTunnel()

// Always re-resolve to get the current PID — the stored one may be stale
guard let resolved = process.resolveCurrentPID() else {
Expand Down Expand Up @@ -313,12 +313,12 @@ struct StikDebugShortcuts: AppShortcutsProvider {
}
}

// MARK: - Shared Heartbeat Helper
// MARK: - Shared Tunnel Helper

func ensureHeartbeat() async {
func ensureTunnel() async {
await MainActor.run {
pubHeartBeat = false
startHeartbeatInBackground(showErrorUI: false)
pubTunnelConnected = false
startTunnelInBackground(showErrorUI: false)
}
try? await Task.sleep(nanoseconds: 1_000_000_000)
}
Expand Down
5 changes: 2 additions & 3 deletions StikJIT/Utilities/mountDDI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@

import Foundation

typealias IdevicePairingFile = OpaquePointer
typealias TcpProviderHandle = OpaquePointer
typealias CoreDeviceProxyHandle = OpaquePointer
typealias RpPairingFileHandle = OpaquePointer
typealias AdapterHandle = OpaquePointer
typealias RsdHandshakeHandle = OpaquePointer
typealias ImageMounterHandle = OpaquePointer
typealias LockdowndClientHandle = OpaquePointer

Expand Down
16 changes: 8 additions & 8 deletions StikJIT/Views/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct HomeView: View {
startJITInBackground(bundleID: selectedBundle)
}, showDoneButton: false, onImportPairingFile: { isShowingPairingFilePicker = true })
.onAppear {
startHeartbeatInBackground()
startTunnelInBackground()
MountingProgress.shared.checkforMounted()
viewDidAppeared = true
if let config = pendingJITEnableConfiguration {
Expand Down Expand Up @@ -94,8 +94,8 @@ struct HomeView: View {
}
case "kill-process":
if let pidStr = components?.queryItems?.first(where: { $0.name == "pid" })?.value, let pid = Int(pidStr) {
pubHeartBeat = false
startHeartbeatInBackground(showErrorUI: false)
pubTunnelConnected = false
startTunnelInBackground(showErrorUI: false)
DispatchQueue.global(qos: .userInitiated).async {
sleep(1)
do {
Expand Down Expand Up @@ -133,10 +133,10 @@ struct HomeView: View {
try fileManager.removeItem(at: dest)
}
try fileManager.copyItem(at: url, to: dest)
pubHeartBeat = false
startHeartbeatInBackground()
pubTunnelConnected = false
startTunnelInBackground()
NotificationCenter.default.post(name: .pairingFileImported, object: nil)
// Dismiss any existing heartbeat error alert
// Dismiss any existing connection error alert
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let root = scene.windows.first?.rootViewController {
var top = root
Expand Down Expand Up @@ -268,8 +268,8 @@ struct HomeView: View {
BackgroundLocationManager.shared.requestStart()

if triggeredByURLScheme {
pubHeartBeat = false
startHeartbeatInBackground(showErrorUI: false)
pubTunnelConnected = false
startTunnelInBackground(showErrorUI: false)
}

DispatchQueue.global(qos: .background).async {
Expand Down
2 changes: 1 addition & 1 deletion StikJIT/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ struct SettingsView: View {

RunLoop.current.add(progressTimer, forMode: .common)
DispatchQueue.main.async {
startHeartbeatInBackground()
startTunnelInBackground()
}

} catch { }
Expand Down
18 changes: 9 additions & 9 deletions StikJIT/idevice/JITEnableContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,35 @@
@import UIKit;
#include "idevice.h"
#include "jit.h"
#include "heartbeat.h"
#include "mount.h"

typedef void (^HeartbeatCompletionHandler)(int result, NSString *message);
typedef void (^LogFuncC)(const char* message, ...);
typedef void (^LogFunc)(NSString *message);
typedef void (^SyslogLineHandler)(NSString *line);
typedef void (^SyslogErrorHandler)(NSError *error);

@interface JITEnableContext : NSObject {
// tunnel
@protected AdapterHandle *adapter;
@protected RsdHandshakeHandle *handshake;

// process
@protected dispatch_queue_t processInspectorQueue;
@protected IdeviceProviderHandle* provider;


// syslog
@protected dispatch_queue_t syslogQueue;
@protected BOOL syslogStreaming;
@protected SyslogRelayClientHandle *syslogClient;
@protected SyslogLineHandler syslogLineHandler;
@protected SyslogErrorHandler syslogErrorHandler;

// ideviceInfo
@protected LockdowndClientHandle * g_client;
}
@property (class, readonly)JITEnableContext* shared;
- (IdevicePairingFile*)getPairingFileWithError:(NSError**)error;
- (IdeviceProviderHandle*)getTcpProviderHandle;
- (BOOL)ensureHeartbeatWithError:(NSError**)err;
- (BOOL)startHeartbeat:(NSError**)err;
- (RpPairingFileHandle*)getPairingFileWithError:(NSError**)error;
- (BOOL)ensureTunnelWithError:(NSError**)err;
- (BOOL)startTunnel:(NSError**)err;

@end

Expand Down
Loading
Loading