mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 04:20:30 +00:00
PT 1 of Changes.
This commit is contained in:
parent
47dfbbed6c
commit
0a65264b96
19 changed files with 459 additions and 439 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
//
|
||||
// DebugViewModel.swift
|
||||
// StikJIT
|
||||
//
|
||||
// Created by Stephen on 3/27/25.
|
||||
//
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
|
||||
|
|
@ -4,5 +4,4 @@
|
|||
|
||||
#include "idevice/JITEnableContext.h"
|
||||
#include "idevice/idevice.h"
|
||||
#include "idevice/em_proxy.h"
|
||||
#include "idevice/heartbeat.h"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue