From 6a6411a1cf18b05f7ffac4ecd1b5a683826c8854 Mon Sep 17 00:00:00 2001 From: Stephen B <158498287+StephenDev0@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:21:47 -0500 Subject: [PATCH] 2.3.6 --- DebugWidgetExtension.entitlements | 2 +- README.md | 3 +- StikDebug.xcodeproj/project.pbxproj | 174 +---- .../xcschemes/TunnelProv.xcscheme | 121 ---- StikJIT/StikJIT.entitlements | 8 +- StikJIT/StikJITApp.swift | 289 +-------- .../Utilities/DeviceConnectionContext.swift | 4 +- StikJIT/Utilities/DeviceLibraryStore.swift | 2 +- StikJIT/Utilities/FeatureFlags.swift | 6 +- StikJIT/Utilities/mountDDI.swift | 6 - StikJIT/Views/DeviceLibraryView.swift | 8 +- StikJIT/Views/HomeView.swift | 592 ++++++------------ StikJIT/Views/MapSelectionView.swift | 2 +- StikJIT/Views/ProcessInspectorView.swift | 15 +- StikJIT/Views/ProfileView.swift | 36 +- StikJIT/Views/SettingsView.swift | 2 +- StikJIT/idevice/heartbeat.m | 2 +- TunnelProv/Info.plist | 13 - TunnelProv/PacketTunnelProvider.swift | 57 -- TunnelProv/TunnelProv.entitlements | 14 - 20 files changed, 253 insertions(+), 1103 deletions(-) delete mode 100644 StikDebug.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme delete mode 100644 TunnelProv/Info.plist delete mode 100644 TunnelProv/PacketTunnelProvider.swift delete mode 100644 TunnelProv/TunnelProv.entitlements diff --git a/DebugWidgetExtension.entitlements b/DebugWidgetExtension.entitlements index cfb4d0e..6e54518 100644 --- a/DebugWidgetExtension.entitlements +++ b/DebugWidgetExtension.entitlements @@ -4,7 +4,7 @@ com.apple.security.application-groups - group.com.stik.sj + group.com.stik.stikdebug diff --git a/README.md b/README.md index c16ac58..5f7d2c0 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,9 @@ ## Features - On-device debugging/Just-In-Time (JIT) compilation for supported apps via [`idevice`](https://github.com/jkcoxson/idevice). -- Seamless integration with our custom-built loopback vpn. +- No special VPN/Network Extension entitlements required. - Native UI for managing debugging/JIT-enabling. - No data collection—ensuring full privacy. ## License StikDebug is licensed under **AGPL-3.0**. See [`LICENSE`](LICENSE) for details. - diff --git a/StikDebug.xcodeproj/project.pbxproj b/StikDebug.xcodeproj/project.pbxproj index 5f373b4..f6e98cd 100644 --- a/StikDebug.xcodeproj/project.pbxproj +++ b/StikDebug.xcodeproj/project.pbxproj @@ -8,15 +8,12 @@ /* Begin PBXBuildFile section */ 17C744F02E20BED000834F17 /* Pipify in Frameworks */ = {isa = PBXBuildFile; productRef = 17C744EF2E20BED000834F17 /* Pipify */; }; - 68D1FA402E847E4A0028A0EA /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68D1FA3F2E847E4A0028A0EA /* StoreKit.framework */; }; 68D569BE2E1B415700A5BA36 /* CodeEditorView in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BD2E1B415700A5BA36 /* CodeEditorView */; }; 68D569C02E1B415700A5BA36 /* LanguageSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BF2E1B415700A5BA36 /* LanguageSupport */; }; 68E714E62E6AA2B00025610F /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 68E714E52E6AA2B00025610F /* ZIPFoundation */; }; DC139F6E2DE97EA400F63846 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC139F6D2DE97EA400F63846 /* WidgetKit.framework */; }; DC139F702DE97EA400F63846 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC139F6F2DE97EA400F63846 /* SwiftUI.framework */; }; DC139F812DE97EA600F63846 /* DebugWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DC139F6C2DE97EA400F63846 /* DebugWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - DCA690122DAF660E007C91A8 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCA690112DAF660E007C91A8 /* NetworkExtension.framework */; }; - DCA6901A2DAF660E007C91A8 /* TunnelProv.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DCA690102DAF660E007C91A8 /* TunnelProv.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DCBA85862E3897BD00E88C06 /* StikImporter in Frameworks */ = {isa = PBXBuildFile; productRef = DCBA85852E3897BD00E88C06 /* StikImporter */; }; /* End PBXBuildFile section */ @@ -42,13 +39,6 @@ remoteGlobalIDString = DC6F1D362D94EADD0071B2B6; remoteInfo = StikJIT; }; - DCA690182DAF660E007C91A8 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DC6F1D2F2D94EADD0071B2B6 /* Project object */; - proxyType = 1; - remoteGlobalIDString = DCA6900F2DAF660E007C91A8; - remoteInfo = TunnelProv; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -59,7 +49,6 @@ dstSubfolderSpec = 13; files = ( DC139F812DE97EA600F63846 /* DebugWidgetExtension.appex in Embed Foundation Extensions */, - DCA6901A2DAF660E007C91A8 /* TunnelProv.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -67,7 +56,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 68D1FA3F2E847E4A0028A0EA /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; DC139F6C2DE97EA400F63846 /* DebugWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = DebugWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; DC139F6D2DE97EA400F63846 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; DC139F6F2DE97EA400F63846 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; @@ -75,8 +63,6 @@ DC6F1D372D94EADD0071B2B6 /* StikDebug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StikDebug.app; sourceTree = BUILT_PRODUCTS_DIR; }; DC6F1D482D94EADF0071B2B6 /* StikDebugTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StikDebugTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StikDebugUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - DCA690102DAF660E007C91A8 /* TunnelProv.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TunnelProv.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - DCA690112DAF660E007C91A8 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -94,13 +80,6 @@ ); target = DC139F6B2DE97EA400F63846 /* DebugWidgetExtension */; }; - DCA6901E2DAF660E007C91A8 /* Exceptions for "TunnelProv" folder in "TunnelProv" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - Info.plist, - ); - target = DCA6900F2DAF660E007C91A8 /* TunnelProv */; - }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -130,14 +109,6 @@ path = StikJITUITests; sourceTree = ""; }; - DCA690132DAF660E007C91A8 /* TunnelProv */ = { - isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - DCA6901E2DAF660E007C91A8 /* Exceptions for "TunnelProv" folder in "TunnelProv" target */, - ); - path = TunnelProv; - sourceTree = ""; - }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -157,7 +128,6 @@ 17C744F02E20BED000834F17 /* Pipify in Frameworks */, DCBA85862E3897BD00E88C06 /* StikImporter in Frameworks */, 68D569C02E1B415700A5BA36 /* LanguageSupport in Frameworks */, - 68D1FA402E847E4A0028A0EA /* StoreKit.framework in Frameworks */, 68D569BE2E1B415700A5BA36 /* CodeEditorView in Frameworks */, 68E714E62E6AA2B00025610F /* ZIPFoundation in Frameworks */, ); @@ -177,14 +147,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DCA6900D2DAF660E007C91A8 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DCA690122DAF660E007C91A8 /* NetworkExtension.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -195,7 +157,6 @@ DC6F1D392D94EADD0071B2B6 /* StikJIT */, DC6F1D4B2D94EADF0071B2B6 /* StikJITTests */, DC6F1D552D94EADF0071B2B6 /* StikJITUITests */, - DCA690132DAF660E007C91A8 /* TunnelProv */, DC139F712DE97EA400F63846 /* DebugWidget */, DC6F1D752D94EB620071B2B6 /* Frameworks */, DC6F1D382D94EADD0071B2B6 /* Products */, @@ -208,7 +169,6 @@ DC6F1D372D94EADD0071B2B6 /* StikDebug.app */, DC6F1D482D94EADF0071B2B6 /* StikDebugTests.xctest */, DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */, - DCA690102DAF660E007C91A8 /* TunnelProv.appex */, DC139F6C2DE97EA400F63846 /* DebugWidgetExtension.appex */, ); name = Products; @@ -217,8 +177,6 @@ DC6F1D752D94EB620071B2B6 /* Frameworks */ = { isa = PBXGroup; children = ( - 68D1FA3F2E847E4A0028A0EA /* StoreKit.framework */, - DCA690112DAF660E007C91A8 /* NetworkExtension.framework */, DC139F6D2DE97EA400F63846 /* WidgetKit.framework */, DC139F6F2DE97EA400F63846 /* SwiftUI.framework */, ); @@ -262,7 +220,6 @@ buildRules = ( ); dependencies = ( - DCA690192DAF660E007C91A8 /* PBXTargetDependency */, DC139F802DE97EA600F63846 /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( @@ -326,28 +283,6 @@ productReference = DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; - DCA6900F2DAF660E007C91A8 /* TunnelProv */ = { - isa = PBXNativeTarget; - buildConfigurationList = DCA6901B2DAF660E007C91A8 /* Build configuration list for PBXNativeTarget "TunnelProv" */; - buildPhases = ( - DCA6900C2DAF660E007C91A8 /* Sources */, - DCA6900D2DAF660E007C91A8 /* Frameworks */, - DCA6900E2DAF660E007C91A8 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - DCA690132DAF660E007C91A8 /* TunnelProv */, - ); - name = TunnelProv; - packageProductDependencies = ( - ); - productName = TunnelProv; - productReference = DCA690102DAF660E007C91A8 /* TunnelProv.appex */; - productType = "com.apple.product-type.app-extension"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -373,9 +308,6 @@ CreatedOnToolsVersion = 16.2; TestTargetID = DC6F1D362D94EADD0071B2B6; }; - DCA6900F2DAF660E007C91A8 = { - CreatedOnToolsVersion = 16.2; - }; }; }; buildConfigurationList = DC6F1D322D94EADD0071B2B6 /* Build configuration list for PBXProject "StikDebug" */; @@ -403,7 +335,6 @@ DC6F1D362D94EADD0071B2B6 /* StikDebug */, DC6F1D472D94EADF0071B2B6 /* StikDebugTests */, DC6F1D512D94EADF0071B2B6 /* StikDebugUITests */, - DCA6900F2DAF660E007C91A8 /* TunnelProv */, DC139F6B2DE97EA400F63846 /* DebugWidgetExtension */, ); }; @@ -438,13 +369,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DCA6900E2DAF660E007C91A8 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -476,13 +400,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DCA6900C2DAF660E007C91A8 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -501,11 +418,6 @@ target = DC6F1D362D94EADD0071B2B6 /* StikDebug */; targetProxy = DC6F1D532D94EADF0071B2B6 /* PBXContainerItemProxy */; }; - DCA690192DAF660E007C91A8 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DCA6900F2DAF660E007C91A8 /* TunnelProv */; - targetProxy = DCA690182DAF660E007C91A8 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -517,7 +429,7 @@ CODE_SIGN_ENTITLEMENTS = DebugWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = SZ977XLF24; + DEVELOPMENT_TEAM = 2743MYJ5UC; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DebugWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = DebugWidget; @@ -529,7 +441,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj.DebugWidget; + PRODUCT_BUNDLE_IDENTIFIER = com.stik.stikdebug.DebugWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -547,7 +459,7 @@ CODE_SIGN_ENTITLEMENTS = DebugWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = SZ977XLF24; + DEVELOPMENT_TEAM = 2743MYJ5UC; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DebugWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = DebugWidget; @@ -559,7 +471,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj.DebugWidget; + PRODUCT_BUNDLE_IDENTIFIER = com.stik.stikdebug.DebugWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -695,7 +607,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"StikJIT/Preview Content\""; - DEVELOPMENT_TEAM = SZ977XLF24; + DEVELOPMENT_TEAM = 2743MYJ5UC; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -726,8 +638,8 @@ "$(PROJECT_DIR)/StikJIT/idevice", ); MACOSX_DEPLOYMENT_TARGET = 15.1; - MARKETING_VERSION = 2.3.5; - PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj; + MARKETING_VERSION = 2.3.6; + PRODUCT_BUNDLE_IDENTIFIER = com.stik.stikdebug; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -753,7 +665,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"StikJIT/Preview Content\""; - DEVELOPMENT_TEAM = SZ977XLF24; + DEVELOPMENT_TEAM = 2743MYJ5UC; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -784,8 +696,8 @@ "$(PROJECT_DIR)/StikJIT/idevice", ); MACOSX_DEPLOYMENT_TARGET = 15.1; - MARKETING_VERSION = 2.3.5; - PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj; + MARKETING_VERSION = 2.3.6; + PRODUCT_BUNDLE_IDENTIFIER = com.stik.stikdebug; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -889,63 +801,6 @@ }; name = Release; }; - DCA6901C2DAF660E007C91A8 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = TunnelProv/TunnelProv.entitlements; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = SZ977XLF24; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = TunnelProv/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = TunnelProv; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj.TunnelProv; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - DCA6901D2DAF660E007C91A8 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = TunnelProv/TunnelProv.entitlements; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = SZ977XLF24; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = TunnelProv/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = TunnelProv; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj.TunnelProv; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -994,15 +849,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCA6901B2DAF660E007C91A8 /* Build configuration list for PBXNativeTarget "TunnelProv" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DCA6901C2DAF660E007C91A8 /* Debug */, - DCA6901D2DAF660E007C91A8 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ diff --git a/StikDebug.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme b/StikDebug.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme deleted file mode 100644 index 3a4cbbf..0000000 --- a/StikDebug.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/StikJIT/StikJIT.entitlements b/StikJIT/StikJIT.entitlements index 5fe9b26..3dabfcc 100644 --- a/StikJIT/StikJIT.entitlements +++ b/StikJIT/StikJIT.entitlements @@ -2,17 +2,11 @@ - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - com.apple.developer.networking.vpn.api - allow-vpn com.apple.security.app-sandbox com.apple.security.application-groups - group.com.stik.sj + group.com.stik.stikdebug com.apple.security.files.user-selected.read-only diff --git a/StikJIT/StikJITApp.swift b/StikJIT/StikJITApp.swift index dcc208b..9079659 100644 --- a/StikJIT/StikJITApp.swift +++ b/StikJIT/StikJITApp.swift @@ -8,7 +8,6 @@ import SwiftUI import Network import UniformTypeIdentifiers -import NetworkExtension // Register default settings before the app starts private func registerAdvancedOptionsDefault() { @@ -64,17 +63,6 @@ struct WelcomeSheetView: View { .fixedSize(horizontal: false, vertical: true) } - // VPN explanation - VStack(alignment: .leading, spacing: 6) { - Label("Why VPN permission?", systemImage: "lock.shield.fill") - .foregroundColor(accent) - .font(.headline) - Text("The next step will prompt you to allow VPN permissions. This is necessary for the app to function properly. The VPN configuration allows your device to securely connect to itself — nothing more. No data is collected or sent externally; everything stays on your device.") - .font(.callout) - .foregroundColor(.secondary) - .fixedSize(horizontal: false, vertical: true) - } - // Continue button Button(action: { onDismiss?() }) { Text("Continue") @@ -124,221 +112,6 @@ struct WelcomeSheetView: View { } } -// MARK: - VPN Logger - -class VPNLogger: ObservableObject { - @Published var logs: [String] = [] - static var shared = VPNLogger() - private init() {} - - func log(_ message: Any, file: String = #file, function: String = #function, line: Int = #line) { - #if DEBUG - let fileName = (file as NSString).lastPathComponent - print("[\(fileName):\(line)] \(function): \(message)") - #endif - logs.append("\(message)") - } -} - -// MARK: - Tunnel Manager - -class TunnelManager: ObservableObject { - @Published var tunnelStatus: TunnelStatus = .disconnected - static var shared = TunnelManager() - - private var vpnManager: NETunnelProviderManager? - private var pendingStopRequest = false - private var tunnelDeviceIp: String { - UserDefaults.standard.string(forKey: "TunnelDeviceIP") ?? "10.7.0.0" - } - private var tunnelFakeIp: String { - UserDefaults.standard.string(forKey: "TunnelFakeIP") ?? "10.7.0.1" - } - private var tunnelSubnetMask: String { - UserDefaults.standard.string(forKey: "TunnelSubnetMask") ?? "255.255.255.0" - } - private var tunnelBundleId: String { - Bundle.main.bundleIdentifier!.appending(".TunnelProv") - } - - enum TunnelStatus: String { - case disconnected = "Disconnected" - case connecting = "Connecting" - case connected = "Connected" - case disconnecting = "Disconnecting" - case error = "Error" - } - - private init() { - loadTunnelPreferences() - NotificationCenter.default.addObserver(self, selector: #selector(statusDidChange(_:)), name: .NEVPNStatusDidChange, object: nil) - } - - private func loadTunnelPreferences() { - NETunnelProviderManager.loadAllFromPreferences { [weak self] (managers, error) in - guard let self = self else { return } - DispatchQueue.main.async { - if let error = error { - VPNLogger.shared.log("Error loading preferences: \(error.localizedDescription)") - self.tunnelStatus = .error - return - } - if let managers = managers, !managers.isEmpty { - for manager in managers { - if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol, - proto.providerBundleIdentifier == self.tunnelBundleId { - self.vpnManager = manager - self.updateTunnelStatus(from: manager.connection.status) - VPNLogger.shared.log("Loaded existing tunnel configuration") - break - } - } - if self.vpnManager == nil, let firstManager = managers.first { - self.vpnManager = firstManager - self.updateTunnelStatus(from: firstManager.connection.status) - VPNLogger.shared.log("Using existing tunnel configuration") - } - if self.pendingStopRequest, let manager = self.vpnManager { - VPNLogger.shared.log("Pending stop request detected during preference load") - self.stopVPN(with: manager) - } - } else { - VPNLogger.shared.log("No existing tunnel configuration found") - } - } - } - } - - @objc private func statusDidChange(_ notification: Notification) { - if let connection = notification.object as? NEVPNConnection { - updateTunnelStatus(from: connection.status) - } - } - - private func updateTunnelStatus(from connectionStatus: NEVPNStatus) { - DispatchQueue.main.async { - switch connectionStatus { - case .invalid, .disconnected: - self.tunnelStatus = .disconnected - case .connecting: - self.tunnelStatus = .connecting - case .connected: - self.tunnelStatus = .connected - case .disconnecting: - self.tunnelStatus = .disconnecting - case .reasserting: - self.tunnelStatus = .connecting - @unknown default: - self.tunnelStatus = .error - } - VPNLogger.shared.log("VPN status updated: \(self.tunnelStatus.rawValue)") - if connectionStatus == .connected && heartbeatStartPending { - startHeartbeatInBackground(showErrorUI: heartbeatPendingShowUI) - } - } - } - - private func createOrUpdateTunnelConfiguration(completion: @escaping (Bool) -> Void) { - NETunnelProviderManager.loadAllFromPreferences { [weak self] (managers, error) in - guard let self = self else { return completion(false) } - if let error = error { - VPNLogger.shared.log("Error loading preferences: \(error.localizedDescription)") - return completion(false) - } - - let manager: NETunnelProviderManager - if let existingManagers = managers, !existingManagers.isEmpty { - if let matchingManager = existingManagers.first(where: { - ($0.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.tunnelBundleId - }) { - manager = matchingManager - VPNLogger.shared.log("Updating existing tunnel configuration") - } else { - manager = existingManagers[0] - VPNLogger.shared.log("Using first available tunnel configuration") - } - } else { - manager = NETunnelProviderManager() - VPNLogger.shared.log("Creating new tunnel configuration") - } - - manager.localizedDescription = "StikDebug" - let proto = NETunnelProviderProtocol() - proto.providerBundleIdentifier = self.tunnelBundleId - proto.serverAddress = "StikDebug's Local Network Tunnel" - manager.protocolConfiguration = proto - manager.isOnDemandEnabled = true - manager.isEnabled = true - - manager.saveToPreferences { [weak self] error in - guard let self = self else { return completion(false) } - DispatchQueue.main.async { - if let error = error { - VPNLogger.shared.log("Error saving tunnel configuration: \(error.localizedDescription)") - completion(false) - return - } - self.vpnManager = manager - VPNLogger.shared.log("Tunnel configuration saved successfully") - completion(true) - } - } - } - } - - func startVPN() { - if let manager = vpnManager { - startExistingVPN(manager: manager) - } else { - createOrUpdateTunnelConfiguration { [weak self] success in - guard let self = self, success else { return } - self.loadTunnelPreferences() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - if let manager = self.vpnManager { - self.startExistingVPN(manager: manager) - } - } - } - } - } - - private func startExistingVPN(manager: NETunnelProviderManager) { - guard tunnelStatus == .disconnected || tunnelStatus == .error else { - VPNLogger.shared.log("Ignoring VPN start; current status: \(tunnelStatus.rawValue)") - return - } - tunnelStatus = .connecting - let options: [String: NSObject] = [ - "TunnelDeviceIP": tunnelDeviceIp as NSObject, - "TunnelFakeIP": tunnelFakeIp as NSObject, - "TunnelSubnetMask": tunnelSubnetMask as NSObject - ] - do { - try manager.connection.startVPNTunnel(options: options) - VPNLogger.shared.log("Network tunnel start initiated") - } catch { - tunnelStatus = .error - VPNLogger.shared.log("Failed to start tunnel: \(error.localizedDescription)") - } - } - - func stopVPN() { - guard let manager = vpnManager else { - pendingStopRequest = true - loadTunnelPreferences() - return - } - pendingStopRequest = false - stopVPN(with: manager) - } - - private func stopVPN(with manager: NETunnelProviderManager) { - tunnelStatus = .disconnecting - manager.connection.stopVPNTunnel() - VPNLogger.shared.log("Network tunnel stop initiated") - } -} - // MARK: - AccentColor Environment Key (leave available but unused) struct AccentColorKey: EnvironmentKey { @@ -509,7 +282,6 @@ struct HeartbeatApp: App { @AppStorage("hasLaunchedBefore") var hasLaunchedBefore: Bool = false @AppStorage("customAccentColor") private var customAccentColorHex: String = "" @AppStorage("appTheme") private var appThemeRaw: String = AppTheme.system.rawValue - @AppStorage("autoStartVPN") private var autoStartVPN = true @State private var showWelcomeSheet: Bool = false @State private var show_alert = false @State private var alert_string = "" @@ -559,14 +331,6 @@ struct HeartbeatApp: App { } } - private func triggerAutoVPNStartIfNeeded() { - guard autoStartVPN, DeviceConnectionContext.requiresLoopbackVPN else { return } - let manager = TunnelManager.shared - if manager.tunnelStatus == .disconnected || manager.tunnelStatus == .error { - manager.startVPN() - } - } - private func handleScenePhaseChange(_ newPhase: ScenePhase) { switch newPhase { case .background: @@ -629,11 +393,8 @@ struct HeartbeatApp: App { .tint(globalAccent) .onAppear { // On first launch, present the welcome sheet. - // Otherwise, start the VPN automatically. if !hasLaunchedBefore { showWelcomeSheet = true - } else { - triggerAutoVPNStartIfNeeded() } HeartbeatApp.updateUIKitTint(customHex: customAccentColorHex, hasAccess: themeExpansionManager.hasThemeExpansion) @@ -650,10 +411,9 @@ struct HeartbeatApp: App { } .sheet(isPresented: $showWelcomeSheet) { WelcomeSheetView { - // When the user taps "Continue", mark the app as launched and start the VPN if allowed. + // When the user taps "Continue", mark the app as launched. hasLaunchedBefore = true showWelcomeSheet = false - triggerAutoVPNStartIfNeeded() } } } @@ -706,16 +466,6 @@ class MountingProgress: ObservableObject { } private func mount() { - if DeviceConnectionContext.requiresLoopbackVPN { - guard TunnelManager.shared.tunnelStatus == .connected else { - DispatchQueue.main.async { - self.coolisMounted = false - self.mountingThread = nil - } - return - } - } - let currentlyMounted = isMounted() DispatchQueue.main.async { self.coolisMounted = currentlyMounted @@ -772,7 +522,7 @@ func isPairing() -> Bool { return true } -func startHeartbeatInBackground(requireVPNConnection: Bool? = nil, showErrorUI: Bool = true) { +func startHeartbeatInBackground(showErrorUI: Bool = true) { assert(Thread.isMainThread, "startHeartbeatInBackground must be called on the main thread") let pairingFileURL = URL.documentsDirectory.appendingPathComponent("pairingFile.plist") @@ -782,17 +532,6 @@ func startHeartbeatInBackground(requireVPNConnection: Bool? = nil, showErrorUI: return } - let shouldRequireVPN = requireVPNConnection ?? DeviceConnectionContext.requiresLoopbackVPN - let vpnConnected = TunnelManager.shared.tunnelStatus == .connected - if shouldRequireVPN && !vpnConnected { - if !heartbeatStartPending { - print("Heartbeat start deferred until VPN connects") - } - heartbeatPendingShowUI = showErrorUI - heartbeatStartPending = true - return - } - guard !heartbeatStartInProgress else { return } @@ -845,7 +584,7 @@ func startHeartbeatInBackground(requireVPNConnection: Bool? = nil, showErrorUI: } else { showAlert( title: "Heartbeat Error", - message: "Failed to connect to Heartbeat (\(code)). Are you connected to WiFi or is Airplane Mode enabled? Cellular data isn’t supported. Please launch the app at least once with WiFi enabled. After that, you can switch to cellular data to turn on the VPN, and once the VPN is active you can use Airplane Mode.", + message: "Failed to connect to Heartbeat (\(code)). Make sure Wi‑Fi is enabled and the device is reachable. Launch the app at least once while online before trying again.", showOk: false, showTryAgain: true ) { shouldTryAgain in @@ -863,7 +602,7 @@ func startHeartbeatInBackground(requireVPNConnection: Bool? = nil, showErrorUI: } -func checkVPNConnection(callback: @escaping (Bool, String?) -> Void) { +func checkDeviceConnection(callback: @escaping (Bool, String?) -> Void) { let targetIP = DeviceConnectionContext.targetIPAddress let host = NWEndpoint.Host(targetIP) let port = NWEndpoint.Port(rawValue: 62078)! @@ -875,12 +614,7 @@ func checkVPNConnection(callback: @escaping (Bool, String?) -> Void) { connection?.cancel() DispatchQueue.main.async { if timeoutWorkItem?.isCancelled == false { - let message: String - if DeviceConnectionContext.requiresLoopbackVPN { - message = "[TIMEOUT] The loopback VPN is not connected. Try closing this app, turn it off and back on." - } else { - message = "[TIMEOUT] Could not reach the device at \(targetIP). Make sure it’s online and on the same network." - } + let message = "[TIMEOUT] Could not reach the device at \(targetIP). Make sure it’s online and on the same network." callback(false, message) } } @@ -899,18 +633,7 @@ func checkVPNConnection(callback: @escaping (Bool, String?) -> Void) { timeoutWorkItem?.cancel() connection?.cancel() DispatchQueue.main.async { - let message: String - if DeviceConnectionContext.requiresLoopbackVPN { - if error == NWError.posix(.ETIMEDOUT) { - message = "The loopback VPN is not connected. Try closing the app, turn it off and back on." - } else if error == NWError.posix(.ECONNREFUSED) { - message = "Wi-Fi is not connected. StikDebug can't connect over cellular data while in loopback mode." - } else { - message = "VPN check error: \(error.localizedDescription)" - } - } else { - message = "Could not reach the device at \(targetIP): \(error.localizedDescription)" - } + let message = "Could not reach the device at \(targetIP): \(error.localizedDescription)" callback(false, message) } default: diff --git a/StikJIT/Utilities/DeviceConnectionContext.swift b/StikJIT/Utilities/DeviceConnectionContext.swift index 3397e2f..a0499d7 100644 --- a/StikJIT/Utilities/DeviceConnectionContext.swift +++ b/StikJIT/Utilities/DeviceConnectionContext.swift @@ -13,13 +13,13 @@ enum DeviceConnectionContext { } static var requiresLoopbackVPN: Bool { - !isUsingExternalDevice + false } static var targetIPAddress: String { if let device = DeviceLibraryStore.shared.activeDevice { return device.ipAddress } - return UserDefaults.standard.string(forKey: "TunnelDeviceIP") ?? "10.7.0.2" + return "127.0.0.1" } } diff --git a/StikJIT/Utilities/DeviceLibraryStore.swift b/StikJIT/Utilities/DeviceLibraryStore.swift index b282c14..609b99d 100644 --- a/StikJIT/Utilities/DeviceLibraryStore.swift +++ b/StikJIT/Utilities/DeviceLibraryStore.swift @@ -103,7 +103,7 @@ final class DeviceLibraryStore: ObservableObject { DeviceProfileEntry( id: localLoopbackID, name: "This Device", - ipAddress: "10.7.0.2", + ipAddress: "10.7.0.1", pairingRelativePath: "", pairingFilename: "pairingFile.plist", dateAdded: Date.distantPast, diff --git a/StikJIT/Utilities/FeatureFlags.swift b/StikJIT/Utilities/FeatureFlags.swift index c2f1428..b1c63c1 100644 --- a/StikJIT/Utilities/FeatureFlags.swift +++ b/StikJIT/Utilities/FeatureFlags.swift @@ -2,11 +2,11 @@ import Foundation enum FeatureFlags { /// Global toggle for exposing location spoofing UI/logic. - static let isLocationSpoofingEnabled = false + static let isLocationSpoofingEnabled = true /// Controls visibility of beta-quality tabs and UI. - static let showBetaTabs = false + static let showBetaTabs = true /// Forces the Theme Expansion to be unlocked and visible everywhere. - static let alwaysUnlockThemeExpansion = false + static let alwaysUnlockThemeExpansion = true /// Controls visibility of the Mini Tools tab and related UI. static let isMiniToolsEnabled = false } diff --git a/StikJIT/Utilities/mountDDI.swift b/StikJIT/Utilities/mountDDI.swift index e941306..9eee5ea 100644 --- a/StikJIT/Utilities/mountDDI.swift +++ b/StikJIT/Utilities/mountDDI.swift @@ -19,12 +19,6 @@ func progressCallback(progress: size_t, total: size_t, context: UnsafeMutableRaw } func isMounted() -> Bool { - if DeviceConnectionContext.requiresLoopbackVPN { - guard TunnelManager.shared.tunnelStatus == .connected else { - return false - } - } - do { let result = try JITEnableContext.shared.getMountedDeviceCount() return result > 0 diff --git a/StikJIT/Views/DeviceLibraryView.swift b/StikJIT/Views/DeviceLibraryView.swift index 8a5f27c..e4e7362 100644 --- a/StikJIT/Views/DeviceLibraryView.swift +++ b/StikJIT/Views/DeviceLibraryView.swift @@ -115,7 +115,7 @@ struct DeviceLibraryView: View { if let refreshedDevice = store.devices.first(where: { $0.id == originalDevice.id }) { do { try store.activate(device: refreshedDevice) - startHeartbeatInBackground(requireVPNConnection: false) + startHeartbeatInBackground() alert = DeviceAlert(title: "Pairing Updated", message: "\(refreshedDevice.name)'s pairing file was refreshed.", isError: false) @@ -164,10 +164,10 @@ struct DeviceLibraryView: View { let activatingDefault = store.isDefaultDevice(device) do { try store.activate(device: device) - startHeartbeatInBackground(requireVPNConnection: false) - let title = activatingDefault ? "Loopback Active" : "Device Activated" + startHeartbeatInBackground() + let title = activatingDefault ? "Local Device Active" : "Device Activated" let message = activatingDefault - ? "Switched back to debugging this device over the loopback VPN." + ? "Switched back to debugging on this device." : "\(device.name) is now active. The heartbeat will refresh automatically." alert = DeviceAlert(title: title, message: message, isError: false) } catch { diff --git a/StikJIT/Views/HomeView.swift b/StikJIT/Views/HomeView.swift index 5fbed5b..caee387 100644 --- a/StikJIT/Views/HomeView.swift +++ b/StikJIT/Views/HomeView.swift @@ -33,8 +33,8 @@ struct HomeView: View { @State private var isProcessing = false @State private var isShowingInstalledApps = false @State private var isShowingPairingFilePicker = false - @State private var pairingFileExists: Bool = false - @State private var pairingFilePresentOnDisk: Bool = false + @State private var pairingFileExists: Bool = true + @State private var pairingFilePresentOnDisk: Bool = true @State private var isValidatingPairingFile = false @State private var lastValidatedPairingSignature: PairingFileSignature? = nil @State private var showPairingFileMessage = false @@ -56,7 +56,6 @@ struct HomeView: View { @AppStorage("DefaultScriptName") var selectedScript = "attachDetach.js" @State var jsModel: RunJSViewModel? - @StateObject private var tunnel = TunnelManager.shared @ObservedObject private var mounting = MountingProgress.shared @ObservedObject private var deviceStore = DeviceLibraryStore.shared @State private var heartbeatOK = false @@ -75,8 +74,6 @@ struct HomeView: View { @State private var cellularMonitor: NWPathMonitor? = nil @State private var isSchedulingInitialSetup = false @AppStorage("cachedAppNamesData") private var cachedAppNamesData: Data? - @AppStorage("autoStartVPN") private var autoStartVPN = true - @State private var lastDDIIndicatorStatus: StartupIndicatorStatus = .idle @AppStorage("appTheme") private var appThemeRaw: String = AppTheme.system.rawValue @Environment(\.themeExpansionManager) private var themeExpansion @@ -87,15 +84,10 @@ struct HomeView: View { themeExpansion?.resolvedAccentColor(from: customAccentColorHex) ?? .blue } - private var ddiMounted: Bool { mounting.coolisMounted } + private var ddiMounted: Bool { true } private var canConnectByApp: Bool { pairingFileExists && ddiMounted } - private var requiresLoopbackVPN: Bool { !deviceStore.isUsingExternalDevice } - private var pairingFileLikelyInvalid: Bool { - (pairingFileExists || pairingFilePresentOnDisk) && - !isValidatingPairingFile && - !ddiMounted && - !heartbeatOK - } + private var requiresLoopbackVPN: Bool { DeviceConnectionContext.requiresLoopbackVPN } + private var pairingFileLikelyInvalid: Bool { false } private var sanitizedUsername: String { let trimmed = username.trimmingCharacters(in: .whitespacesAndNewlines) return trimmed.isEmpty ? "there" : trimmed @@ -122,9 +114,7 @@ struct HomeView: View { default: return "Hello" } } - private var shouldPromptForWiFi: Bool { - pairingFileLikelyInvalid && !wifiConnected && isCellularActive - } + private var shouldPromptForWiFi: Bool { false } private let pairingFileURL = URL.documentsDirectory.appendingPathComponent("pairingFile.plist") @@ -132,7 +122,7 @@ struct HomeView: View { private var homeContent: some View { VStack(spacing: 20) { welcomeCard - setupCard + heartbeatCard connectCard // if pairingFileExists { // quickConnectCard @@ -189,14 +179,11 @@ struct HomeView: View { scheduleInitialSetupWork() startWiFiMonitoring() startCellularMonitoring() - if requiresLoopbackVPN && autoStartVPN && tunnel.tunnelStatus == .disconnected { - TunnelManager.shared.startVPN() - } if !hasAutoStartedConnectionCheck { hasAutoStartedConnectionCheck = true runConnectionDiagnostics(autoStart: true) } - lastDDIIndicatorStatus = ddiIndicatorStatus + startHeartbeatInBackground() NotificationCenter.default.addObserver( forName: NSNotification.Name("ShowPairingFilePicker"), object: nil, @@ -226,21 +213,10 @@ struct HomeView: View { cachedAppNames = [:] } } - .onChange(of: tunnel.tunnelStatus) { _, newStatus in - guard requiresLoopbackVPN else { return } - if newStatus == .connected { - loadAppListIfNeeded(force: cachedAppNames.isEmpty) - runConnectionDiagnostics() - MountingProgress.shared.checkforMounted() - } - } .onChange(of: favoriteApps) { _, _ in loadAppListIfNeeded() syncFavoriteAppNamesWithCache() } - .onChange(of: ddiIndicatorStatus) { _, newStatus in - handleDDIIndicatorChange(newStatus) - } .onChange(of: recentApps) { _, _ in loadAppListIfNeeded() } @@ -265,7 +241,7 @@ struct HomeView: View { } DispatchQueue.main.async { - startHeartbeatInBackground(requireVPNConnection: false) + startHeartbeatInBackground() } let progressTimer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { t in @@ -420,20 +396,31 @@ struct HomeView: View { } } - private var setupCard: some View { + private var heartbeatCard: some View { homeCard { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("Setup") + HStack(alignment: .top, spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + Text("Heartbeat") .font(.headline.weight(.semibold)) - Spacer() - connectionStatusBadge + Text(heartbeatSubtitle) + .font(.footnote) + .foregroundStyle(.secondary) + HStack(spacing: 6) { + pillIconButton(icon: "play.fill", title: "Start") { + startHeartbeatInBackground() + } + pillIconButton(icon: "arrow.clockwise", title: "Restart") { + pubHeartBeat = false + heartbeatOK = false + startHeartbeatInBackground() + } + } + } + Spacer(minLength: 0) + VStack(alignment: .trailing, spacing: 6) { + heartbeatStatusBadge + statusLightsRow } - - statusLightsRow - - vpnControls - } } } @@ -451,12 +438,6 @@ struct HomeView: View { text: "Wi-Fi required", color: .orange ) - } else if pairingFileLikelyInvalid { - statusBadge( - icon: "xmark.octagon.fill", - text: "Pairing file expired", - color: .red - ) } primaryActionControls @@ -499,6 +480,22 @@ struct HomeView: View { } } + @ViewBuilder + private var heartbeatStatusBadge: some View { + switch heartbeatIndicatorStatus { + case .success: + statusBadge(icon: "checkmark.circle.fill", text: "Connected", color: .green) + case .running: + statusBadge(icon: "clock.arrow.circlepath", text: "Starting…", color: .orange) + case .warning: + statusBadge(icon: "exclamationmark.triangle.fill", text: "Waiting", color: .yellow) + case .error: + statusBadge(icon: "xmark.circle.fill", text: "Error", color: .red) + case .idle: + statusBadge(icon: "pause.circle", text: "Idle", color: .secondary) + } + } + private func statusBadge(icon: String, text: String, color: Color) -> some View { Label(text, systemImage: icon) .font(.footnote.weight(.semibold)) @@ -524,15 +521,9 @@ struct HomeView: View { .labelStyle(.iconOnly) } - private var connectionHasError: Bool { - if case .failure = connectionCheckState { return true } - if case .timeout = connectionCheckState { return true } - return false - } + private var connectionHasError: Bool { false } private var allStatusIndicatorsGreen: Bool { - ddiIndicatorStatus == .success && - wifiIndicatorStatus == .success && heartbeatIndicatorStatus == .success } @@ -554,20 +545,6 @@ struct HomeView: View { private var statusLights: [StatusLightData] { [ - StatusLightData( - type: .ddi, - title: "DDI", - icon: "externaldrive", - status: ddiIndicatorStatus, - detail: ddiDetailText - ), - StatusLightData( - type: .wifi, - title: "Wi-Fi", - icon: "wifi", - status: wifiIndicatorStatus, - detail: wifiDetailText - ), StatusLightData( type: .heartbeat, title: "Heartbeat", @@ -578,47 +555,16 @@ struct HomeView: View { ] } - private var ddiDetailText: String { - if ddiMounted { return "Mounted" } - if pairingFileLikelyInvalid { return "Error" } - return pairingFileExists ? "Mount required" : "Not ready" - } - - private var wifiDetailText: String { - if isConnectionCheckRunning { return "Checking…" } - return wifiConnected ? "Connected" : "Offline" - } + private var wifiDetailText: String { "Connected" } private var heartbeatDetailText: String { if heartbeatOK { return "Active" } - if pairingFileExists { - if requiresLoopbackVPN { - return tunnel.tunnelStatus == .connected ? "Waiting" : "VPN required" - } else { - return "Waiting" - } - } + if pairingFileExists { return "Waiting" } return "Pair first" } private var refreshIndicatorStatus: StartupIndicatorStatus { - switch connectionCheckState { - case .running: - return .running - case .success: - return .success - case .failure, .timeout: - return .warning - case .idle: - return .idle - } - } - - private var ddiIndicatorStatus: StartupIndicatorStatus { - if ddiMounted { return .success } - if pairingFileLikelyInvalid { return .warning } - if pairingFileExists { return .warning } - return .idle + .success } private func color(for indicator: StartupIndicatorStatus) -> Color { @@ -660,12 +606,6 @@ struct HomeView: View { isCellularActive = false } - private func handleDDIIndicatorChange(_ newStatus: StartupIndicatorStatus) { - defer { lastDDIIndicatorStatus = newStatus } - guard lastDDIIndicatorStatus == .success, newStatus == .warning else { return } - startHeartbeatInBackground(showErrorUI: false) - } - private var pairingStatusDescription: String { if isValidatingPairingFile { return "Validating pairing file…" } if pairingFileExists { @@ -678,62 +618,19 @@ struct HomeView: View { } private var wifiStatusDescription: String { - if isConnectionCheckRunning { return "Checking Wi-Fi status…" } - return wifiConnected ? "Wi-Fi connected and ready." : "Connect to Wi-Fi." + "Wi-Fi connected and ready." } - private var isConnectionCheckRunning: Bool { - if case .running = connectionCheckState { return true } - return false - } + private var isConnectionCheckRunning: Bool { false } - private var vpnStatusSubtitle: String { - if !requiresLoopbackVPN { - return "External device selected. VPN tunnel not required." - } - if isConnectionCheckRunning { - return "Checking the VPN/loopback tunnel…" - } - switch tunnel.tunnelStatus { - case .connected: - return "VPN is connected; loopback traffic is ready." - case .connecting: - return "Connecting… allow the VPN prompt if it appears." - case .disconnecting: - return "Disconnecting from the VPN." - case .disconnected: - return "VPN is off. Connect before running diagnostics." - case .error: - return "VPN configuration error. Try reconnecting." - } - } - - private var vpnIndicatorStatus: StartupIndicatorStatus { - if !requiresLoopbackVPN { return .success } - if isConnectionCheckRunning { return .running } - switch tunnel.tunnelStatus { - case .connected: - return .success - case .connecting, .disconnecting: - return .running - case .disconnected: - return pairingFileExists ? .warning : .idle - case .error: - return .error - } - } - - private var wifiIndicatorStatus: StartupIndicatorStatus { - if isConnectionCheckRunning { return .running } - return wifiConnected ? .success : .warning - } + private var wifiIndicatorStatus: StartupIndicatorStatus { .success } private var heartbeatSubtitle: String { if heartbeatOK { return "Heartbeat is responding." } if !requiresLoopbackVPN && pairingFileExists { - return "Waiting for the remote device to respond." + return "Waiting for a response." } if pairingFileLikelyInvalid { return "Heartbeat is blocked because the pairing file looks invalid." @@ -752,11 +649,7 @@ struct HomeView: View { private var heartbeatIndicatorStatus: StartupIndicatorStatus { if heartbeatOK { return .success } - if pairingFileLikelyInvalid { return .warning } - if !pairingFileExists { return .idle } - if case .running = connectionCheckState { return .running } - if case .success = connectionCheckState { return .warning } - return .idle + return .warning } private var connectionCheckButtonLabel: some View { @@ -787,144 +680,27 @@ struct HomeView: View { } } - @ViewBuilder - private var vpnControls: some View { - if !requiresLoopbackVPN { - VStack(alignment: .leading, spacing: 10) { - HStack { - Text("VPN Tunnel") - .font(.subheadline.weight(.semibold)) - .foregroundStyle(.primary) - Spacer() - statusBadge(icon: "checkmark.circle.fill", text: "Not needed", color: .green) - } - Text("You’re targeting an external device. StikDebug connects directly to \(DeviceConnectionContext.targetIPAddress).") - .font(.footnote) - .foregroundStyle(.secondary) - } - } else { - VStack(alignment: .leading, spacing: 10) { - HStack { - Text("VPN Tunnel") - .font(.subheadline.weight(.semibold)) - .foregroundStyle(.primary) - Spacer() - statusBadge(icon: "shield.lefthalf.filled", text: tunnel.tunnelStatus.rawValue, color: vpnStatusColor) - } - - Text(vpnStatusSubtitle) - .font(.footnote) - .foregroundStyle(.secondary) - - HStack(spacing: 8) { - Button(action: { TunnelManager.shared.startVPN() }) { - compactControlButton(icon: "lock.open", title: "Connect") - } - .buttonStyle(.plain) - .disabled(!canStartVPN) - - Button(action: { TunnelManager.shared.stopVPN() }) { - compactControlButton(icon: "lock.fill", title: "Disconnect") - } - .buttonStyle(.plain) - .disabled(!canStopVPN) - } - - Button { - autoStartVPN.toggle() - if requiresLoopbackVPN && autoStartVPN && tunnel.tunnelStatus == .disconnected { - TunnelManager.shared.startVPN() - } - } label: { - compactControlButton( - icon: autoStartVPN ? "lock.circle" : "lock.slash", - title: autoStartVPN ? "Disable Auto VPN" : "Enable Auto VPN" - ) - } - .buttonStyle(.plain) - } - } - } - - private var vpnStatusColor: Color { - if !requiresLoopbackVPN { return .green } - switch tunnel.tunnelStatus { - case .connected: return .green - case .connecting, .disconnecting: return .orange - case .error: return .red - case .disconnected: return .yellow - } - } - - private var canStartVPN: Bool { - guard requiresLoopbackVPN else { return false } - switch tunnel.tunnelStatus { - case .disconnected, .error: - return true - default: - return false - } - } - - private var canStopVPN: Bool { - guard requiresLoopbackVPN else { return false } - switch tunnel.tunnelStatus { - case .connected, .connecting, .disconnecting: - return true - default: - return false - } - } - private func refreshStatusTapped() { runConnectionDiagnostics() if pairingFileExists { - startHeartbeatInBackground(requireVPNConnection: requiresLoopbackVPN) + startHeartbeatInBackground() } } private func runConnectionDiagnostics(autoStart: Bool = false) { - guard !isConnectionCheckRunning else { return } - connectionTimeoutTask?.cancel() - connectionTimeoutTask = nil - connectionInfoMessage = autoStart ? "Checking connection…" : nil - connectionCheckState = .running - let timeout = DispatchWorkItem { - DispatchQueue.main.async { - if case .running = connectionCheckState { - connectionCheckState = .timeout - connectionInfoMessage = requiresLoopbackVPN - ? "Connection timed out. Check the VPN and pairing file, then try again." - : "Connection timed out. Make sure the device at \(DeviceConnectionContext.targetIPAddress) is reachable." - connectionTimeoutTask = nil - } - } - } - connectionTimeoutTask = timeout - DispatchQueue.main.asyncAfter(deadline: .now() + 7, execute: timeout) - - checkVPNConnection { success, error in - connectionTimeoutTask?.cancel() - connectionTimeoutTask = nil - if success { - connectionCheckState = .success - connectionInfoMessage = nil - if pairingFileExists && !heartbeatOK { - startHeartbeatInBackground() - } - } else { - let fallback = requiresLoopbackVPN ? "VPN tunnel is not connected." : "Unable to reach the device." - connectionCheckState = .failure(error ?? fallback) - connectionInfoMessage = error ?? fallback - } - } + connectionTimeoutTask?.cancel() + connectionTimeoutTask = nil + connectionCheckState = .success + connectionInfoMessage = nil + if pairingFileExists && !heartbeatOK { + startHeartbeatInBackground() } - + } + private var primaryActionTitle: String { if isValidatingPairingFile { return "Validating…" } if !pairingFileExists { return pairingFilePresentOnDisk ? "Import New Pairing File" : "Import Pairing File" } if shouldPromptForWiFi { return "Connect to Wi-Fi" } - if pairingFileLikelyInvalid { return "New Pairing File Needed" } if !ddiMounted { return "Mount Developer Disk Image" } return "Connect by App" } @@ -933,97 +709,120 @@ struct HomeView: View { if isValidatingPairingFile { return "hourglass" } if !pairingFileExists { return pairingFilePresentOnDisk ? "arrow.clockwise" : "doc.badge.plus" } if shouldPromptForWiFi { return "wifi.slash" } - if pairingFileLikelyInvalid { return "arrow.clockwise" } if !ddiMounted { return "externaldrive" } return "cable.connector.horizontal" } - private var pairingImportProgressView: some View { - VStack(spacing: 8) { - HStack { - Text("Processing pairing file…") - .font(.system(.caption, design: .rounded)) - .foregroundStyle(.secondary) - Spacer() - Text("\(Int(importProgress * 100))%") - .font(.system(.caption, design: .rounded)) - .foregroundStyle(.secondary) - } - - GeometryReader { geo in - ZStack(alignment: .leading) { - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(Color(UIColor.tertiarySystemFill)) - .frame(height: 8) - - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(accentColor) - .frame(width: geo.size.width * CGFloat(importProgress), height: 8) - .animation(.linear(duration: 0.25), value: importProgress) - } - } - .frame(height: 8) - } - .accessibilityElement(children: .combine) - } - - private var pairingSuccessMessage: some View { - HStack(spacing: 10) { - StatusDot(color: .green) - Text("Pairing file successfully imported") - .font(.system(.callout, design: .rounded)) - .foregroundStyle(.green) - Spacer(minLength: 0) - } - .padding(.top, 4) - .transition(.opacity) - } - - private func whiteCardButtonLabel(icon: String, title: String, isLoading: Bool = false) -> some View { - HStack(spacing: 10) { - if isLoading { - ProgressView() - .progressViewStyle(.circular) - .tint(accentColor.contrastText()) - .frame(width: 20, height: 20) - } else { - Image(systemName: icon) - .font(.system(size: 20, weight: .semibold, design: .rounded)) - } - - Text(title) - .font(.system(.title3, design: .rounded).weight(.semibold)) - } - .frame(maxWidth: .infinity) - .padding(.vertical, 14) - .background(accentColor, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) - .foregroundColor(accentColor.contrastText()) - .overlay( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.white.opacity(0.2), lineWidth: 0.5) - ) - .animation(.easeInOut(duration: 0.2), value: isLoading) - } - - private func secondaryButtonLabel(icon: String, title: String) -> some View { + private var pairingImportProgressView: some View { + VStack(spacing: 8) { HStack { - Image(systemName: icon) - .font(.system(size: 18, weight: .semibold, design: .rounded)) - Text(title) - .font(.system(.title3, design: .rounded).weight(.semibold)) + Text("Processing pairing file…") + .font(.system(.caption, design: .rounded)) + .foregroundStyle(.secondary) + Spacer() + Text("\(Int(importProgress * 100))%") + .font(.system(.caption, design: .rounded)) + .foregroundStyle(.secondary) } - .frame(maxWidth: .infinity) - .padding(.vertical, 12) - .background( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color(UIColor.secondarySystemBackground).opacity(0.6)) - ) - .overlay( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.white.opacity(0.12), lineWidth: 1) - ) - .foregroundStyle(.primary) + + GeometryReader { geo in + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color(UIColor.tertiarySystemFill)) + .frame(height: 8) + + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(accentColor) + .frame(width: geo.size.width * CGFloat(importProgress), height: 8) + .animation(.linear(duration: 0.25), value: importProgress) + } + } + .frame(height: 8) } + .accessibilityElement(children: .combine) + } + + private var pairingSuccessMessage: some View { + HStack(spacing: 10) { + StatusDot(color: .green) + Text("Pairing file successfully imported") + .font(.system(.callout, design: .rounded)) + .foregroundStyle(.green) + Spacer(minLength: 0) + } + .padding(.top, 4) + .transition(.opacity) + } + + private func whiteCardButtonLabel(icon: String, title: String, isLoading: Bool = false) -> some View { + HStack(spacing: 10) { + if isLoading { + ProgressView() + .progressViewStyle(.circular) + .tint(accentColor.contrastText()) + .frame(width: 20, height: 20) + } else { + Image(systemName: icon) + .font(.system(size: 20, weight: .semibold, design: .rounded)) + } + + Text(title) + .font(.system(.title3, design: .rounded).weight(.semibold)) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(accentColor, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) + .foregroundColor(accentColor.contrastText()) + .overlay( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .stroke(Color.white.opacity(0.2), lineWidth: 0.5) + ) + .animation(.easeInOut(duration: 0.2), value: isLoading) + } + + private func secondaryButtonLabel(icon: String, title: String) -> some View { + HStack { + Image(systemName: icon) + .font(.system(size: 18, weight: .semibold, design: .rounded)) + Text(title) + .font(.system(.title3, design: .rounded).weight(.semibold)) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 12) + .background( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color(UIColor.secondarySystemBackground).opacity(0.6)) + ) + .overlay( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .stroke(Color.white.opacity(0.12), lineWidth: 1) + ) + .foregroundStyle(.primary) + } + + private func pillIconButton(icon: String, title: String, action: @escaping () -> Void) -> some View { + Button(action: action) { + HStack(spacing: 6) { + Image(systemName: icon) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + Text(title) + .font(.system(.footnote, design: .rounded).weight(.semibold)) + .lineLimit(1) + } + .padding(.horizontal, 12) + .padding(.vertical, 10) + .frame(minWidth: 92, alignment: .center) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(Color(UIColor.secondarySystemBackground)) + .overlay( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .stroke(Color.white.opacity(0.12), lineWidth: 1) + ) + ) + } + .buttonStyle(.plain) + } private var quickConnectCard: some View { homeCard { @@ -1193,9 +992,7 @@ struct HomeView: View { refreshBackground() checkPairingFileExists() loadAppListIfNeeded() - if tunnel.tunnelStatus == .connected || !requiresLoopbackVPN { - MountingProgress.shared.checkforMounted() - } + MountingProgress.shared.checkforMounted() } } } @@ -1207,8 +1004,6 @@ struct HomeView: View { return } - guard tunnel.tunnelStatus == .connected else { return } - if !force && !cachedAppNames.isEmpty { return } DispatchQueue.global(qos: .userInitiated).async { @@ -1390,15 +1185,11 @@ struct HomeView: View { } return } - if pairingFileExists { - if !ddiMounted { - showAlert(title: "Device Not Mounted".localized, message: "The Developer Disk Image has not been mounted yet. Check in settings for more information.".localized, showOk: true) { _ in } - return - } - isShowingInstalledApps = true - } else { - isShowingPairingFilePicker = true + if !ddiMounted { + showAlert(title: "Device Not Mounted".localized, message: "The Developer Disk Image has not been mounted yet. Check in settings for more information.".localized, showOk: true) { _ in } + return } + isShowingInstalledApps = true } private func showCopiedToast() { @@ -1425,31 +1216,10 @@ struct HomeView: View { } private func checkPairingFileExists() { - let fileExists = FileManager.default.fileExists(atPath: pairingFileURL.path) - pairingFilePresentOnDisk = fileExists - - guard fileExists else { - pairingFileExists = false - lastValidatedPairingSignature = nil - isValidatingPairingFile = false - return - } - - let signature = pairingFileSignature(for: pairingFileURL) - - guard needsValidation(for: signature) else { return } - guard !isValidatingPairingFile else { return } - - isValidatingPairingFile = true - - DispatchQueue.global(qos: .utility).async { - let valid = isPairing() - DispatchQueue.main.async { - pairingFileExists = valid - lastValidatedPairingSignature = signature - isValidatingPairingFile = false - } - } + // Home screen no longer blocks on pairing file checks; assume available. + pairingFileExists = true + pairingFilePresentOnDisk = true + isValidatingPairingFile = false } private func needsValidation(for signature: PairingFileSignature) -> Bool { diff --git a/StikJIT/Views/MapSelectionView.swift b/StikJIT/Views/MapSelectionView.swift index 8b5d88e..70bf159 100644 --- a/StikJIT/Views/MapSelectionView.swift +++ b/StikJIT/Views/MapSelectionView.swift @@ -122,7 +122,7 @@ struct LocationSimulationView: View { } private var deviceIP: String { - UserDefaults.standard.string(forKey: "TunnelDeviceIP") ?? "10.7.0.2" + UserDefaults.standard.string(forKey: "TunnelDeviceIP") ?? "10.7.0.1" } var body: some View { diff --git a/StikJIT/Views/ProcessInspectorView.swift b/StikJIT/Views/ProcessInspectorView.swift index b92aa2a..f275e5b 100644 --- a/StikJIT/Views/ProcessInspectorView.swift +++ b/StikJIT/Views/ProcessInspectorView.swift @@ -127,7 +127,7 @@ private extension ProcessInspectorView { isConfirming: killCandidate?.pid == process.pid, onKillTap: { handleKillTap(for: $0) } ) - .padding(.vertical, 10) + .padding(.vertical, 6) if process.id != viewModel.filteredProcesses.last?.id { Divider() @@ -170,23 +170,23 @@ private struct ProcessRow: View { let onKillTap: (ProcessInfoEntry) -> Void var body: some View { - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: 4) { HStack { Text(process.displayName) - .font(.headline) + .font(.subheadline.weight(.semibold)) Spacer() Text("PID \(process.pid)") - .font(.caption) + .font(.caption2) .foregroundStyle(.secondary) } if let bundle = process.bundleID, !bundle.isEmpty { Text(bundle) - .font(.caption) + .font(.caption2) .foregroundStyle(.secondary) .textSelection(.enabled) } Text(process.executablePath) - .font(.caption) + .font(.caption2) .foregroundStyle(.secondary) .textSelection(.enabled) HStack { @@ -209,12 +209,13 @@ private struct ProcessRow: View { } } .buttonStyle(.bordered) + .controlSize(.small) .tint(isConfirming ? .green : .red) .labelStyle(.iconOnly) } } } - .padding(.vertical, 8) + .padding(.vertical, 4) } } diff --git a/StikJIT/Views/ProfileView.swift b/StikJIT/Views/ProfileView.swift index 87d4b1a..92b33f7 100644 --- a/StikJIT/Views/ProfileView.swift +++ b/StikJIT/Views/ProfileView.swift @@ -127,6 +127,7 @@ struct ProfileView: View { @State private var alertSuccess = false @State private var profiles: [Profile] = [] + @AppStorage("cachedProfilesData") private var cachedProfilesData: Data? @State private var confirmRemove = false @State private var removeTargetName: String = "" @State private var removeTargetUUID: String = "" @@ -208,7 +209,7 @@ struct ProfileView: View { } Button { - Task {await loadProfiles()} + Task { await loadProfiles(force: true) } } label: { Label("Reload", systemImage: "arrow.clockwise") } @@ -348,15 +349,42 @@ struct ProfileView: View { confirmRemove = true } - func loadProfiles() async { + private func restoreCachedProfilesIfNeeded() async -> Bool { + guard profiles.isEmpty, let cachedProfilesData else { return false } + + let cachedDatas: [Data]? = try? await Task.detached(priority: .userInitiated) { + try? JSONDecoder().decode([Data].self, from: cachedProfilesData) + }.value + + guard let cachedDatas, !cachedDatas.isEmpty else { return false } + let restoredProfiles = cachedDatas.map { Profile(data: $0) } + await MainActor.run { + guard self.profiles.isEmpty else { return } + self.profiles = restoredProfiles + self.working = false + } + return true + } + + func loadProfiles(force: Bool = false) async { + if !force { + if !profiles.isEmpty { + await MainActor.run { working = false } + return + } + if await restoreCachedProfilesIfNeeded() { return } + } + await MainActor.run { working = true } do { let profileDatas = try await Task.detached(priority: .userInitiated) { try JITEnableContext.shared.fetchAllProfiles() }.value let parsedProfiles = profileDatas.map { Profile(data: $0) } + let encodedProfiles = try? JSONEncoder().encode(profileDatas) await MainActor.run { self.profiles = parsedProfiles + self.cachedProfilesData = encodedProfiles self.working = false } } catch { @@ -385,7 +413,7 @@ struct ProfileView: View { alertSuccess = true alert = true // Reload profiles after removal - await loadProfiles() + await loadProfiles(force: true) } catch { alertMsg = error.localizedDescription alertTitle = "Failed to Remove Profile" @@ -420,7 +448,7 @@ struct ProfileView: View { alert = true // Reload profiles after adding - await loadProfiles() + await loadProfiles(force: true) } catch { alertMsg = error.localizedDescription alertTitle = "Failed to Add Profile" diff --git a/StikJIT/Views/SettingsView.swift b/StikJIT/Views/SettingsView.swift index 2df0502..c75b8aa 100644 --- a/StikJIT/Views/SettingsView.swift +++ b/StikJIT/Views/SettingsView.swift @@ -231,7 +231,7 @@ struct SettingsView: View { RunLoop.current.add(progressTimer, forMode: .common) DispatchQueue.main.async { - startHeartbeatInBackground(requireVPNConnection: false) + startHeartbeatInBackground() } } catch { diff --git a/StikJIT/idevice/heartbeat.m b/StikJIT/idevice/heartbeat.m index dd029e8..4bbaee3 100644 --- a/StikJIT/idevice/heartbeat.m +++ b/StikJIT/idevice/heartbeat.m @@ -27,7 +27,7 @@ void startHeartbeat(IdevicePairingFile* pairing_file, IdeviceProviderHandle** pr addr.sin_port = htons(LOCKDOWN_PORT); NSString* deviceIP = [[NSUserDefaults standardUserDefaults] stringForKey:@"TunnelDeviceIP"]; - inet_pton(AF_INET, deviceIP ? [deviceIP UTF8String] : "10.7.0.2", &addr.sin_addr); + inet_pton(AF_INET, deviceIP ? [deviceIP UTF8String] : "10.7.0.1", &addr.sin_addr); err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, diff --git a/TunnelProv/Info.plist b/TunnelProv/Info.plist deleted file mode 100644 index 3059459..0000000 --- a/TunnelProv/Info.plist +++ /dev/null @@ -1,13 +0,0 @@ - - - - - NSExtension - - NSExtensionPointIdentifier - com.apple.networkextension.packet-tunnel - NSExtensionPrincipalClass - $(PRODUCT_MODULE_NAME).PacketTunnelProvider - - - diff --git a/TunnelProv/PacketTunnelProvider.swift b/TunnelProv/PacketTunnelProvider.swift deleted file mode 100644 index d6e6c17..0000000 --- a/TunnelProv/PacketTunnelProvider.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// PacketTunnelProvider.swift -// TunnelProv -// -// Created by Stephen on 06/01/2025. -// -// Inspired by Stossy & StosVPN -// - -import NetworkExtension -import Darwin - -class PacketTunnelProvider: NEPacketTunnelProvider { - override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { - let deviceIP = options?["TunnelDeviceIP"] as? String ?? "10.7.0.0" - let fakeIP = options?["TunnelFakeIP"] as? String ?? "10.7.0.1" - - let toNetwork: (String) -> UInt32 = { address in - var addr = in_addr() - inet_pton(AF_INET, address, &addr) - return addr.s_addr.bigEndian - } - let deviceNet = toNetwork(deviceIP), fakeNet = toNetwork(fakeIP) - - let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: deviceIP) - let ipv4 = NEIPv4Settings(addresses: [deviceIP], subnetMasks: ["255.255.255.0"]) - ipv4.includedRoutes = [NEIPv4Route(destinationAddress: deviceIP, subnetMask: "255.255.255.0")] - ipv4.excludedRoutes = [NEIPv4Route.default()] - settings.ipv4Settings = ipv4 - - setTunnelNetworkSettings(settings) { error in - guard error == nil else { return completionHandler(error) } - - func process() { - self.packetFlow.readPackets { packets, protocols in - var modified = packets - for i in modified.indices where protocols[i].int32Value == AF_INET && modified[i].count >= 20 { - modified[i].withUnsafeMutableBytes { buffer in - let ptr = buffer.baseAddress!.assumingMemoryBound(to: UInt32.self) - let src = ptr[3], dst = ptr[4] - ptr[3] = src == deviceNet ? fakeNet : dst - ptr[4] = dst == fakeNet ? deviceNet : src - } - } - self.packetFlow.writePackets(modified, withProtocols: protocols) - process() - } - } - process() - completionHandler(nil) - } - } - - override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - completionHandler() - } -} diff --git a/TunnelProv/TunnelProv.entitlements b/TunnelProv/TunnelProv.entitlements deleted file mode 100644 index bbcb3da..0000000 --- a/TunnelProv/TunnelProv.entitlements +++ /dev/null @@ -1,14 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - com.apple.security.application-groups - - group.com.stik.sj - - -