From 0a65264b96ce0c69536edb0b9fad000d34dfbeaf Mon Sep 17 00:00:00 2001 From: Stephen <158498287+0-Blu@users.noreply.github.com> Date: Sun, 1 Jun 2025 23:14:44 -0400 Subject: [PATCH] PT 1 of Changes. --- StikDebug.xcodeproj/project.pbxproj | 4 +- .../xcschemes/DebugWidgetExtension.xcscheme | 113 ++++++++++++ .../xcshareddata/xcschemes/StikDebug.xcscheme | 78 ++++++++ .../xcschemes/TunnelProv.xcscheme | 97 ++++++++++ .../Extensions & Stuff/DebugViewModel.swift | 7 - .../Extensions & Stuff/FancyBlurView.swift | 19 -- StikJIT/Extensions & Stuff/Font.swift | 14 -- .../Extensions & Stuff/RoundedTextField.swift | 34 ---- .../Extensions & Stuff/VisualEffectBlur.swift | 21 --- StikJIT/StikJIT-Bridging-Header.h | 1 - StikJIT/Utilities/AppStoreIconFetcher.swift | 32 ++-- .../Color.swift | 0 .../HapticFeedbackHelper.swift | 0 StikJIT/Views/AppIconView.swift | 60 ------ StikJIT/Views/InstalledAppsListView.swift | 73 ++++++-- StikJIT/Views/SettingsView.swift | 16 +- StikJIT/idevice/applist.m | 117 +++++------- StikJIT/idevice/em_proxy.h | 40 ---- TunnelProv/PacketTunnelProvider.swift | 172 ++++-------------- 19 files changed, 459 insertions(+), 439 deletions(-) create mode 100644 StikDebug.xcodeproj/xcshareddata/xcschemes/DebugWidgetExtension.xcscheme create mode 100644 StikDebug.xcodeproj/xcshareddata/xcschemes/StikDebug.xcscheme create mode 100644 StikDebug.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme delete mode 100644 StikJIT/Extensions & Stuff/DebugViewModel.swift delete mode 100644 StikJIT/Extensions & Stuff/FancyBlurView.swift delete mode 100644 StikJIT/Extensions & Stuff/Font.swift delete mode 100644 StikJIT/Extensions & Stuff/RoundedTextField.swift delete mode 100644 StikJIT/Extensions & Stuff/VisualEffectBlur.swift rename StikJIT/{Extensions & Stuff => Utilities}/Color.swift (100%) rename StikJIT/{Extensions & Stuff => Utilities}/HapticFeedbackHelper.swift (100%) delete mode 100644 StikJIT/Views/AppIconView.swift delete mode 100644 StikJIT/idevice/em_proxy.h diff --git a/StikDebug.xcodeproj/project.pbxproj b/StikDebug.xcodeproj/project.pbxproj index 77442d0..f40f538 100644 --- a/StikDebug.xcodeproj/project.pbxproj +++ b/StikDebug.xcodeproj/project.pbxproj @@ -502,7 +502,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.3.7; PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj.DebugWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -532,7 +532,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.3.7; PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj.DebugWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/StikDebug.xcodeproj/xcshareddata/xcschemes/DebugWidgetExtension.xcscheme b/StikDebug.xcodeproj/xcshareddata/xcschemes/DebugWidgetExtension.xcscheme new file mode 100644 index 0000000..732e619 --- /dev/null +++ b/StikDebug.xcodeproj/xcshareddata/xcschemes/DebugWidgetExtension.xcscheme @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StikDebug.xcodeproj/xcshareddata/xcschemes/StikDebug.xcscheme b/StikDebug.xcodeproj/xcshareddata/xcschemes/StikDebug.xcscheme new file mode 100644 index 0000000..8948422 --- /dev/null +++ b/StikDebug.xcodeproj/xcshareddata/xcschemes/StikDebug.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StikDebug.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme b/StikDebug.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme new file mode 100644 index 0000000..43ab266 --- /dev/null +++ b/StikDebug.xcodeproj/xcshareddata/xcschemes/TunnelProv.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StikJIT/Extensions & Stuff/DebugViewModel.swift b/StikJIT/Extensions & Stuff/DebugViewModel.swift deleted file mode 100644 index f278a20..0000000 --- a/StikJIT/Extensions & Stuff/DebugViewModel.swift +++ /dev/null @@ -1,7 +0,0 @@ -// -// DebugViewModel.swift -// StikJIT -// -// Created by Stephen on 3/27/25. -// - diff --git a/StikJIT/Extensions & Stuff/FancyBlurView.swift b/StikJIT/Extensions & Stuff/FancyBlurView.swift deleted file mode 100644 index 9f95c31..0000000 --- a/StikJIT/Extensions & Stuff/FancyBlurView.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// FancyBlurView.swift -// StikJIT -// -// Created by Stephen on 3/27/25. -// - -import SwiftUI - -// Custom Blur Effect for macOS/iOS -struct FancyBlurView: View { - var body: some View { - #if os(macOS) - VisualEffectBlur(material: .sidebar, blendingMode: .behindWindow) - #else - VisualEffectBlur(style: .systemUltraThinMaterial) - #endif - } -} diff --git a/StikJIT/Extensions & Stuff/Font.swift b/StikJIT/Extensions & Stuff/Font.swift deleted file mode 100644 index 2e74d79..0000000 --- a/StikJIT/Extensions & Stuff/Font.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Font.swift -// StikJIT -// -// Created by Stephen on 3/27/25. -// - -import SwiftUI - -extension Font { - static let titleFont = Font.system(size: 24, weight: .bold) - static let bodyFont = Font.system(size: 18, weight: .regular) - static let captionFont = Font.system(size: 14, weight: .medium) -} diff --git a/StikJIT/Extensions & Stuff/RoundedTextField.swift b/StikJIT/Extensions & Stuff/RoundedTextField.swift deleted file mode 100644 index 04970a9..0000000 --- a/StikJIT/Extensions & Stuff/RoundedTextField.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// RoundedTextField.swift -// StikJIT -// -// Created by Stephen on 3/27/25. -// - -import SwiftUI - -struct RoundedTextField: View { - var placeholder: String - @Binding var text: String - - var body: some View { - HStack { - TextField(placeholder, text: $text) - .font(.system(.body, design: .rounded)) - .autocapitalization(.none) - .disableAutocorrection(true) - .foregroundColor(.primary) - - if !text.isEmpty { - Button(action: { text = "" }) { - Image(systemName: "xmark.circle.fill") - .foregroundColor(.gray) - } - .buttonStyle(.plain) - } - } - .padding() - .background(RoundedRectangle(cornerRadius: 12).fill(Color(.secondarySystemBackground))) - .overlay(RoundedRectangle(cornerRadius: 12).stroke(Color(.tertiaryLabel), lineWidth: 0.5)) - } -} diff --git a/StikJIT/Extensions & Stuff/VisualEffectBlur.swift b/StikJIT/Extensions & Stuff/VisualEffectBlur.swift deleted file mode 100644 index 6347fe5..0000000 --- a/StikJIT/Extensions & Stuff/VisualEffectBlur.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// VisualEffectBlur.swift -// StikJIT -// -// Created by Stephen on 3/27/25. -// - -import SwiftUI - -// Universal Blur Effect (for iOS) -struct VisualEffectBlur: UIViewRepresentable { - let style: UIBlurEffect.Style - - func makeUIView(context: Context) -> UIVisualEffectView { - let view = UIVisualEffectView(effect: UIBlurEffect(style: style)) - return view - } - - func updateUIView(_ uiView: UIVisualEffectView, context: Context) {} -} - diff --git a/StikJIT/StikJIT-Bridging-Header.h b/StikJIT/StikJIT-Bridging-Header.h index 7ce5a25..963bed2 100644 --- a/StikJIT/StikJIT-Bridging-Header.h +++ b/StikJIT/StikJIT-Bridging-Header.h @@ -4,5 +4,4 @@ #include "idevice/JITEnableContext.h" #include "idevice/idevice.h" -#include "idevice/em_proxy.h" #include "idevice/heartbeat.h" diff --git a/StikJIT/Utilities/AppStoreIconFetcher.swift b/StikJIT/Utilities/AppStoreIconFetcher.swift index a4e9829..e0cbf49 100644 --- a/StikJIT/Utilities/AppStoreIconFetcher.swift +++ b/StikJIT/Utilities/AppStoreIconFetcher.swift @@ -8,31 +8,23 @@ import UIKit -// Not AppStore -// Uses idevice class AppStoreIconFetcher { - static private var iconCache: [String: UIImage] = [:] - static private let iconFetchDispatchQueue = DispatchQueue(label: "com.stik.StikJIT.iconFetchQueue", attributes: .concurrent) - + private static var cache = [String: UIImage]() + private static let queue = DispatchQueue(label: "com.stik.StikJIT.iconFetchQueue", attributes: .concurrent) + static func getIcon(for bundleID: String, completion: @escaping (UIImage?) -> Void) { - // Check cache first - if let cachedIcon = iconCache[bundleID] { - completion(cachedIcon) + if let icon = cache[bundleID] { + completion(icon) return } - - iconFetchDispatchQueue.async { - do { - let ans = try JITEnableContext.shared.getAppIcon(withBundleId: bundleID) - DispatchQueue.main.async { - iconCache[bundleID] = ans - completion(ans) - } - } catch { - print("Failed to get icon: \(error)") - DispatchQueue.main.async { - completion(nil) + + queue.async { + let icon = try? JITEnableContext.shared.getAppIcon(withBundleId: bundleID) + DispatchQueue.main.async { + if let img = icon { + cache[bundleID] = img } + completion(icon) } } } diff --git a/StikJIT/Extensions & Stuff/Color.swift b/StikJIT/Utilities/Color.swift similarity index 100% rename from StikJIT/Extensions & Stuff/Color.swift rename to StikJIT/Utilities/Color.swift diff --git a/StikJIT/Extensions & Stuff/HapticFeedbackHelper.swift b/StikJIT/Utilities/HapticFeedbackHelper.swift similarity index 100% rename from StikJIT/Extensions & Stuff/HapticFeedbackHelper.swift rename to StikJIT/Utilities/HapticFeedbackHelper.swift diff --git a/StikJIT/Views/AppIconView.swift b/StikJIT/Views/AppIconView.swift deleted file mode 100644 index 8bbca3b..0000000 --- a/StikJIT/Views/AppIconView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// AppIconView.swift -// StikJIT -// -// Created by neoarz on 4/12/25. -// - -import SwiftUI - -struct AppIconView: View { - @Environment(\.dismiss) private var dismiss - @AppStorage("customAccentColor") private var customAccentColorHex: String = "" - - private var accentColor: Color { - if customAccentColorHex.isEmpty { - return .blue - } else { - return Color(hex: customAccentColorHex) ?? .blue - } - } - - var body: some View { - NavigationView { - ZStack { - Color(UIColor.systemBackground) - .ignoresSafeArea() - - VStack(spacing: 24) { - Image(systemName: "hammer.fill") - .font(.system(size: 60)) - .foregroundColor(accentColor) - .padding(.bottom, 8) - - Text("Work in Progress") - .font(.system(size: 24, weight: .bold)) - .multilineTextAlignment(.center) - - Text("App icon customization coming soon!") - .font(.system(size: 17)) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - } - .padding() - } - .navigationTitle("App Icon") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { - dismiss() - } - } - } - } - } -} - -#Preview { - AppIconView() -} diff --git a/StikJIT/Views/InstalledAppsListView.swift b/StikJIT/Views/InstalledAppsListView.swift index cf5eb00..fd06de6 100644 --- a/StikJIT/Views/InstalledAppsListView.swift +++ b/StikJIT/Views/InstalledAppsListView.swift @@ -28,6 +28,10 @@ struct InstalledAppsListView: View { @Environment(\.dismiss) private var dismiss var onSelectApp: (String) -> Void + private var filteredRecents: [String] { + recentApps.filter { !favoriteApps.contains($0) } + } + var body: some View { NavigationView { Group { @@ -47,7 +51,6 @@ struct InstalledAppsListView: View { .background(Color(UIColor.systemGroupedBackground).edgesIgnoringSafeArea(.all)) } - // MARK: - Empty State private var emptyState: some View { VStack(spacing: 16) { Image(systemName: "magnifyingglass") @@ -64,11 +67,10 @@ Please check if the app you want to connect to is signed with a **development** .background(Color(UIColor.systemBackground)) } - // MARK: - List of Apps private var appsList: some View { List { if !favoriteApps.isEmpty { - Section(header: Text("Favorites - (\(favoriteApps.count)/4)")) { + Section(header: Text("Favorites (\(favoriteApps.count)/4)")) { ForEach(favoriteApps, id: \.self) { bundleID in AppButton( bundleID: bundleID, @@ -83,9 +85,9 @@ Please check if the app you want to connect to is signed with a **development** } } - if !recentApps.isEmpty { + if !filteredRecents.isEmpty { Section(header: Text("Recents")) { - ForEach(recentApps, id: \.self) { bundleID in + ForEach(filteredRecents, id: \.self) { bundleID in AppButton( bundleID: bundleID, appName: viewModel.apps[bundleID] ?? bundleID, @@ -100,7 +102,6 @@ Please check if the app you want to connect to is signed with a **development** withAnimation { recentApps.removeAll { $0 == bundleID } sharedDefaults.set(recentApps, forKey: "recentApps") - sharedDefaults.set(favoriteApps, forKey: "favoriteApps") WidgetCenter.shared.reloadAllTimelines() } } label: { @@ -111,7 +112,7 @@ Please check if the app you want to connect to is signed with a **development** } } - Section(header: Text((favoriteApps.isEmpty && recentApps.isEmpty) ? "" : "All Applications")) { + Section(header: Text((favoriteApps.isEmpty && filteredRecents.isEmpty) ? "" : "All Applications")) { ForEach(viewModel.apps.sorted(by: { $0.key < $1.key }), id: \.key) { bundleID, appName in AppButton( bundleID: bundleID, @@ -168,14 +169,20 @@ struct AppButton: View { Group { if loadAppIconsOnJIT, let image = appIcons[bundleID] { Image(uiImage: image) - .resizable().aspectRatio(contentMode: .fit) - .frame(width: 60, height: 60).cornerRadius(12) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 60, height: 60) + .cornerRadius(12) .shadow(color: colorScheme == .dark ? .black.opacity(0.2) : .gray.opacity(0.2), radius: 3, x: 0, y: 1) } else { RoundedRectangle(cornerRadius: 12) .fill(Color(UIColor.systemGray5)) .frame(width: 60, height: 60) - .overlay(Image(systemName: "app").font(.system(size: 26)).foregroundColor(.gray)) + .overlay( + Image(systemName: "app") + .font(.system(size: 26)) + .foregroundColor(.gray) + ) .shadow(color: .black.opacity(0.1), radius: 2, x: 0, y: 1) .onAppear { loadAppIcon(for: bundleID) } } @@ -184,8 +191,13 @@ struct AppButton: View { private var appText: some View { VStack(alignment: .leading, spacing: 4) { - Text(appName).font(.system(size: 18, weight: .semibold)).foregroundColor(.primary) - Text(bundleID).font(.system(size: 15)).foregroundColor(.gray).lineLimit(1) + Text(appName) + .font(.system(size: 18, weight: .semibold)) + .foregroundColor(.primary) + Text(bundleID) + .font(.system(size: 15)) + .foregroundColor(.gray) + .lineLimit(1) } } @@ -204,6 +216,7 @@ struct AppButton: View { favoriteApps.removeAll { $0 == bundleID } } else if favoriteApps.count < 4 { favoriteApps.insert(bundleID, at: 0) + recentApps.removeAll { $0 == bundleID } } sharedDefaults.set(recentApps, forKey: "recentApps") sharedDefaults.set(favoriteApps, forKey: "favoriteApps") @@ -212,17 +225,39 @@ struct AppButton: View { private func loadAppIcon(for bundleID: String) { guard loadAppIconsOnJIT else { return } + + // 1) Check disk cache first + if let cachedImage = loadCachedIcon(bundleID: bundleID) { + DispatchQueue.main.async { + appIcons[bundleID] = cachedImage + } + return + } + + // 2) Otherwise fetch from network and then cache AppStoreIconFetcher.getIcon(for: bundleID) { image in - if let image = image { - DispatchQueue.main.async { - withAnimation(.easeIn(duration: 0.2)) { - appIcons[bundleID] = image - saveIconToGroup(image, bundleID: bundleID) - } + guard let image = image else { return } + DispatchQueue.main.async { + withAnimation(.easeIn(duration: 0.2)) { + appIcons[bundleID] = image } } + saveIconToGroup(image, bundleID: bundleID) } } + + private func loadCachedIcon(bundleID: String) -> UIImage? { + guard + let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.stik.sj") + else { return nil } + + let iconsDir = containerURL.appendingPathComponent("icons", isDirectory: true) + let fileURL = iconsDir.appendingPathComponent("\(bundleID).png") + if FileManager.default.fileExists(atPath: fileURL.path) { + return UIImage(contentsOfFile: fileURL.path) + } + return nil + } } fileprivate func saveIconToGroup(_ image: UIImage, bundleID: String) { @@ -230,6 +265,7 @@ fileprivate func saveIconToGroup(_ image: UIImage, bundleID: String) { let data = image.pngData(), let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.stik.sj") else { return } + let iconsDir = container.appendingPathComponent("icons", isDirectory: true) try? FileManager.default.createDirectory(at: iconsDir, withIntermediateDirectories: true) let fileURL = iconsDir.appendingPathComponent("\(bundleID).png") @@ -252,6 +288,7 @@ extension Array: @retroactive RawRepresentable where Element: Codable { } } +// Preview helper #Preview { InstalledAppsListView { _ in } } diff --git a/StikJIT/Views/SettingsView.swift b/StikJIT/Views/SettingsView.swift index 05a9ff1..518ee67 100644 --- a/StikJIT/Views/SettingsView.swift +++ b/StikJIT/Views/SettingsView.swift @@ -264,7 +264,21 @@ struct SettingsView: View { self.mounted = isMounted() } } - + SettingsCard { + VStack(alignment: .leading, spacing: 20) { + Text("Behavior") + .font(.headline) + .foregroundColor(.primary) + .padding(.bottom, 4) + + Toggle("Automatically Quit After Enabling JIT", isOn: $doAutoQuitAfterEnablingJIT) + .foregroundColor(.primary) + .padding(.vertical, 6) + } + .padding(.vertical, 20) + .padding(.horizontal, 16) + } + // About section SettingsCard { VStack(alignment: .leading, spacing: 20) { diff --git a/StikJIT/idevice/applist.m b/StikJIT/idevice/applist.m index 5d6e355..06d49a2 100644 --- a/StikJIT/idevice/applist.m +++ b/StikJIT/idevice/applist.m @@ -5,109 +5,90 @@ // Created by Stephen on 3/27/25. // -#include "idevice.h" +#import "idevice.h" #include -#include #include #include -#include - -#include "applist.h" +#import "applist.h" NSDictionary* list_installed_apps(TcpProviderHandle* provider, NSString** error) { - IdeviceErrorCode err = IdeviceSuccess; - InstallationProxyClientHandle *client = NULL; - err = installation_proxy_connect_tcp(provider, &client); - if (err != IdeviceSuccess) { + if (installation_proxy_connect_tcp(provider, &client) != IdeviceSuccess) { *error = @"Failed to connect to installation proxy"; return nil; } void *apps = NULL; - size_t apps_len = 0; - err = installation_proxy_get_apps(client, "User", NULL, 0, &apps, &apps_len); - if (err != IdeviceSuccess) { + size_t count = 0; + if (installation_proxy_get_apps(client, "User", NULL, 0, &apps, &count) != IdeviceSuccess) { installation_proxy_client_free(client); *error = @"Failed to get apps"; return nil; } - plist_t *app_list = (plist_t *)apps; - - NSMutableDictionary* ans = [[NSMutableDictionary alloc] init]; - - for (size_t i = 0; i < apps_len; i++) { - plist_t app = app_list[i]; - // Check if the app has an "Entitlements" dictionary. - plist_t entitlements = plist_dict_get_item(app, "Entitlements"); - if (entitlements) { - // Look for the "get-task-allow" key. - plist_t taskAllowNode = plist_dict_get_item(entitlements, "get-task-allow"); - if (taskAllowNode) { - uint8_t isAllowed = 0; - plist_get_bool_val(taskAllowNode, &isAllowed); - if (isAllowed) { - // Retrieve the bundle identifier if the entitlement is true. - plist_t bundle_id_node = plist_dict_get_item(app, "CFBundleIdentifier"); - if (bundle_id_node) { - char *bundle_id = NULL; - plist_get_string_val(bundle_id_node, &bundle_id); + NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:count]; - // Skip if bundle ID is empty - if (bundle_id == NULL || strlen(bundle_id) == 0) { - free(bundle_id); - continue; - } + for (size_t i = 0; i < count; i++) { + plist_t app = ((plist_t *)apps)[i]; + plist_t ent = plist_dict_get_item(app, "Entitlements"); + if (!ent) continue; - // Retrieve the app name - plist_t app_name_node = plist_dict_get_item(app, "CFBundleName"); - char *app_name = NULL; - if (app_name_node) { - plist_get_string_val(app_name_node, &app_name); - } else { - app_name = strdup("Unknown"); - } + plist_t tnode = plist_dict_get_item(ent, "get-task-allow"); + if (!tnode) continue; - ans[[NSString stringWithCString:bundle_id encoding:NSASCIIStringEncoding]] = [NSString stringWithCString:app_name encoding:NSASCIIStringEncoding]; + uint8_t isAllowed = 0; + plist_get_bool_val(tnode, &isAllowed); + if (!isAllowed) continue; - free(bundle_id); - free(app_name); - } - } - } + plist_t bidNode = plist_dict_get_item(app, "CFBundleIdentifier"); + if (!bidNode) continue; + + char *bidC = NULL; + plist_get_string_val(bidNode, &bidC); + if (!bidC || bidC[0] == '\0') { + free(bidC); + continue; } + NSString *bundleID = [NSString stringWithUTF8String:bidC]; + free(bidC); + + NSString *appName = @"Unknown"; + plist_t nameNode = plist_dict_get_item(app, "CFBundleName"); + if (nameNode) { + char *nameC = NULL; + plist_get_string_val(nameNode, &nameC); + if (nameC && nameC[0] != '\0') { + appName = [NSString stringWithUTF8String:nameC]; + } + free(nameC); + } + + result[bundleID] = appName; } - installation_proxy_client_free(client); - - return ans; + return result; } - UIImage* getAppIcon(TcpProviderHandle* provider, NSString* bundleID, NSString** error) { - IdeviceErrorCode err = IdeviceSuccess; - SpringBoardServicesClientHandle *client = NULL; - springboard_services_connect_tcp(provider, &client); - if (err != IdeviceSuccess) { + if (springboard_services_connect_tcp(provider, &client) != IdeviceSuccess) { *error = @"Failed to connect to SpringBoard Services"; return nil; } - + void *pngData = NULL; - size_t data_len = 0; - err = springboard_services_get_icon(client, [bundleID UTF8String], &pngData, &data_len); - if (err != IdeviceSuccess) { + size_t dataLen = 0; + if (springboard_services_get_icon(client, [bundleID UTF8String], &pngData, &dataLen) != IdeviceSuccess) { springboard_services_free(client); *error = @"Failed to get app icon"; return nil; } - - NSData* pngNSData = [NSData dataWithBytes:pngData length:data_len]; + + NSData *data = [NSData dataWithBytes:pngData length:dataLen]; free(pngData); - UIImage* ans = [UIImage imageWithData:pngNSData]; - return ans; - + UIImage *icon = [UIImage imageWithData:data]; + + springboard_services_free(client); + return icon; } diff --git a/StikJIT/idevice/em_proxy.h b/StikJIT/idevice/em_proxy.h deleted file mode 100644 index fd09f4f..0000000 --- a/StikJIT/idevice/em_proxy.h +++ /dev/null @@ -1,40 +0,0 @@ -// Jackson Coxson - -#include -#include -#include -#include -#include - - -/** - * Starts your emotional damage - * # Arguments - * * `bind_addr` - The UDP socket to listen to - * # Returns - * A handle to stop further emotional damage. - * Null on failure - * # Safety - * Don't be stupid - */ -int start_emotional_damage(const char *bind_addr); - -/** - * Stops further emotional damage - * # Arguments - * * `handle` - The coping mechanism generated by start_emotional_damage - * # Returns - * The knowledge of knowing that you couldn't handle failure - * # Safety - * Don't be stupid - */ -void stop_emotional_damage(void); - -/** - * Blocks until Wireguard is ready - * # Arguments - * * `timeout` - The timeout in miliseconds to wait for Wireguard - * # Returns - * 0 on success, -1 on failure - */ -int test_emotional_damage(int timeout); diff --git a/TunnelProv/PacketTunnelProvider.swift b/TunnelProv/PacketTunnelProvider.swift index 45feab3..d6e6c17 100644 --- a/TunnelProv/PacketTunnelProvider.swift +++ b/TunnelProv/PacketTunnelProvider.swift @@ -2,152 +2,56 @@ // PacketTunnelProvider.swift // TunnelProv // -// Created by Stossy11 on 28/03/2025. +// Created by Stephen on 06/01/2025. +// +// Inspired by Stossy & StosVPN // import NetworkExtension +import Darwin class PacketTunnelProvider: NEPacketTunnelProvider { - var tunnelDeviceIp: String = "10.7.0.0" - var tunnelFakeIp: String = "10.7.0.1" - var tunnelSubnetMask: String = "255.255.255.0" - - override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { - if let deviceIp = options?["TunnelDeviceIP"] as? String { - tunnelDeviceIp = deviceIp - } - if let fakeIp = options?["TunnelFakeIP"] as? String { - tunnelFakeIp = fakeIp - } + 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 settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: tunnelDeviceIp) - let ipv4 = NEIPv4Settings(addresses: [tunnelDeviceIp], subnetMasks: [tunnelSubnetMask]) - ipv4.includedRoutes = [NEIPv4Route(destinationAddress: tunnelDeviceIp, subnetMask: tunnelSubnetMask)] - ipv4.excludedRoutes = [.default()] + 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 - if error == nil { - self.readPackets() + 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() + } } - completionHandler(error) + process() + completionHandler(nil) } } - + override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - // Add code here to start the process of stopping the tunnel. completionHandler() } - - override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { - // Add code here to handle the message. - if let handler = completionHandler { - handler(messageData) - } - } - - override func sleep(completionHandler: @escaping () -> Void) { - completionHandler() - } - - override func wake() { - // Add code here to wake up. - } - - private func readPackets() { - packetFlow.readPackets { packets, protocols in - var output: [Data] = [] - for (i, packet) in packets.enumerated() { - var modifiedPacket = packet - if protocols[i].int32Value == AF_INET { - modifiedPacket = self.packetReplaceIp(packet, self.tunnelDeviceIp, self.tunnelFakeIp, self.tunnelFakeIp, self.tunnelDeviceIp) - } - - if modifiedPacket.count >= 20 { - var mutableBytes = [UInt8](modifiedPacket) - - // Swap bytes - (mutableBytes[12], mutableBytes[16]) = (mutableBytes[16], mutableBytes[12]) - (mutableBytes[13], mutableBytes[17]) = (mutableBytes[17], mutableBytes[13]) - (mutableBytes[14], mutableBytes[18]) = (mutableBytes[18], mutableBytes[14]) - (mutableBytes[15], mutableBytes[19]) = (mutableBytes[19], mutableBytes[15]) - - modifiedPacket = Data(mutableBytes) - } - - output.append(modifiedPacket) - } - self.packetFlow.writePackets(output, withProtocols: protocols) - self.readPackets() - } - } - - - private func packetReplaceIp(_ data: Data, _ sourceSearch: String, _ sourceReplace: String, _ destSearch: String, _ destReplace: String) -> Data { - // Check if packet is too small for IPv4 header - if data.count < 20 { - return data - } - - // Convert IP strings to Data with network byte order (big-endian) - func ipToUInt32(_ ipString: String) -> UInt32 { - let components = ipString.split(separator: ".") - var result: UInt32 = 0 - - if components.count == 4, - let byte1 = UInt32(components[0]), - let byte2 = UInt32(components[1]), - let byte3 = UInt32(components[2]), - let byte4 = UInt32(components[3]) { - result = (byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4 - } - - return result - } - - // Convert IP strings to UInt32 - let sourceSearchIP = ipToUInt32(sourceSearch) - let sourceReplaceIP = ipToUInt32(sourceReplace) - let destSearchIP = ipToUInt32(destSearch) - let destReplaceIP = ipToUInt32(destReplace) - - // Extract source and destination IPs from packet - var sourcePacketIP: UInt32 = 0 - var destPacketIP: UInt32 = 0 - - (data as NSData).getBytes(&sourcePacketIP, range: NSRange(location: 12, length: 4)) - (data as NSData).getBytes(&destPacketIP, range: NSRange(location: 16, length: 4)) - - if sourceSearchIP != sourcePacketIP && destSearchIP != destPacketIP { - return data - } - - let mutableData = NSMutableData(data: data) - - if sourceSearchIP == sourcePacketIP { - var sourceIP = sourceReplaceIP - mutableData.replaceBytes(in: NSRange(location: 12, length: 4), withBytes: &sourceIP) - } - - if destSearchIP == destPacketIP { - var destIP = destReplaceIP - mutableData.replaceBytes(in: NSRange(location: 16, length: 4), withBytes: &destIP) - } - - return mutableData as Data - } - - // Helper function to convert IP string to Data - private func ipToData(_ ip: String) -> Data { - let components = ip.split(separator: ".") - var data = Data(capacity: 4) - - for component in components { - if let byte = UInt8(component) { - data.append(byte) - } - } - - return data - } } -