PT 1 of Changes.

This commit is contained in:
Stephen 2025-06-01 23:14:44 -04:00
parent 47dfbbed6c
commit 0a65264b96
19 changed files with 459 additions and 439 deletions

View file

@ -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;

View file

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC139F6B2DE97EA400F63846"
BuildableName = "DebugWidgetExtension.appex"
BlueprintName = "DebugWidgetExtension"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC6F1D362D94EADD0071B2B6"
BuildableName = "StikDebug.app"
BlueprintName = "StikDebug"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC6F1D362D94EADD0071B2B6"
BuildableName = "StikDebug.app"
BlueprintName = "StikDebug"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = ""
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
value = "timeline"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "systemMedium"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC6F1D362D94EADD0071B2B6"
BuildableName = "StikDebug.app"
BlueprintName = "StikDebug"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC6F1D362D94EADD0071B2B6"
BuildableName = "StikDebug.app"
BlueprintName = "StikDebug"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC6F1D362D94EADD0071B2B6"
BuildableName = "StikDebug.app"
BlueprintName = "StikDebug"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC6F1D362D94EADD0071B2B6"
BuildableName = "StikDebug.app"
BlueprintName = "StikDebug"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DCA6900F2DAF660E007C91A8"
BuildableName = "TunnelProv.appex"
BlueprintName = "TunnelProv"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC6F1D362D94EADD0071B2B6"
BuildableName = "StikDebug.app"
BlueprintName = "StikDebug"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC6F1D362D94EADD0071B2B6"
BuildableName = "StikDebug.app"
BlueprintName = "StikDebug"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DC6F1D362D94EADD0071B2B6"
BuildableName = "StikDebug.app"
BlueprintName = "StikDebug"
ReferencedContainer = "container:StikDebug.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -1,7 +0,0 @@
//
// DebugViewModel.swift
// StikJIT
//
// Created by Stephen on 3/27/25.
//

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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))
}
}

View file

@ -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) {}
}

View file

@ -4,5 +4,4 @@
#include "idevice/JITEnableContext.h"
#include "idevice/idevice.h"
#include "idevice/em_proxy.h"
#include "idevice/heartbeat.h"

View file

@ -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)
}
}
}

View file

@ -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()
}

View file

@ -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 }
}

View file

@ -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) {

View file

@ -5,109 +5,90 @@
// Created by Stephen on 3/27/25.
//
#include "idevice.h"
#import "idevice.h"
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "applist.h"
#import "applist.h"
NSDictionary<NSString*, NSString*>* 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<NSString*, NSString*>* 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<NSString*, NSString*> *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;
}

View file

@ -1,40 +0,0 @@
// Jackson Coxson
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
/**
* 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);

View file

@ -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
}
}