mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 12:30:29 +00:00
2.1.0 - Adds Something
This commit is contained in:
parent
0791ace28c
commit
167e0362c3
8 changed files with 919 additions and 3 deletions
|
|
@ -13,8 +13,11 @@
|
|||
DC139F6E2DE97EA400F63846 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC139F6D2DE97EA400F63846 /* WidgetKit.framework */; };
|
||||
DC139F702DE97EA400F63846 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC139F6F2DE97EA400F63846 /* SwiftUI.framework */; };
|
||||
DC139F812DE97EA600F63846 /* DebugWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DC139F6C2DE97EA400F63846 /* DebugWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
DC7E2F1C2E38D01E00725B2C /* ZSignApple in Frameworks */ = {isa = PBXBuildFile; productRef = DC7E2F1B2E38D01E00725B2C /* ZSignApple */; };
|
||||
DCA690122DAF660E007C91A8 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCA690112DAF660E007C91A8 /* NetworkExtension.framework */; };
|
||||
DCA6901A2DAF660E007C91A8 /* TunnelProv.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DCA690102DAF660E007C91A8 /* TunnelProv.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
DCBA85192E388CD400E88C06 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = DCBA85182E388CD400E88C06 /* ZIPFoundation */; };
|
||||
DCBA85862E3897BD00E88C06 /* StikImporter in Frameworks */ = {isa = PBXBuildFile; productRef = DCBA85852E3897BD00E88C06 /* StikImporter */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -150,9 +153,12 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DCBA85862E3897BD00E88C06 /* StikImporter in Frameworks */,
|
||||
17C744F02E20BED000834F17 /* Pipify in Frameworks */,
|
||||
68D569C02E1B415700A5BA36 /* LanguageSupport in Frameworks */,
|
||||
68D569BE2E1B415700A5BA36 /* CodeEditorView in Frameworks */,
|
||||
DCBA85192E388CD400E88C06 /* ZIPFoundation in Frameworks */,
|
||||
DC7E2F1C2E38D01E00725B2C /* ZSignApple in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -265,6 +271,9 @@
|
|||
68D569BD2E1B415700A5BA36 /* CodeEditorView */,
|
||||
68D569BF2E1B415700A5BA36 /* LanguageSupport */,
|
||||
17C744EF2E20BED000834F17 /* Pipify */,
|
||||
DCBA85182E388CD400E88C06 /* ZIPFoundation */,
|
||||
DCBA85852E3897BD00E88C06 /* StikImporter */,
|
||||
DC7E2F1B2E38D01E00725B2C /* ZSignApple */,
|
||||
);
|
||||
productName = StikJIT;
|
||||
productReference = DC6F1D372D94EADD0071B2B6 /* StikDebug.app */;
|
||||
|
|
@ -381,6 +390,9 @@
|
|||
packageReferences = (
|
||||
68D569BC2E1B415700A5BA36 /* XCRemoteSwiftPackageReference "CodeEditorView" */,
|
||||
17C744EE2E20BED000834F17 /* XCRemoteSwiftPackageReference "swiftui-pipify" */,
|
||||
DCBA85172E388CD400E88C06 /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
|
||||
DCBA85842E3897BD00E88C06 /* XCRemoteSwiftPackageReference "StikImporter" */,
|
||||
DC7E2F1A2E38D01E00725B2C /* XCRemoteSwiftPackageReference "zsign-ios" */,
|
||||
);
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = DC6F1D382D94EADD0071B2B6 /* Products */;
|
||||
|
|
@ -684,6 +696,10 @@
|
|||
DEVELOPMENT_TEAM = SZ977XLF24;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/StikJIT/Sources",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = StikJIT/Info.plist;
|
||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||
|
|
@ -706,7 +722,7 @@
|
|||
"$(PROJECT_DIR)/StikJIT/idevice",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 2.0.3;
|
||||
MARKETING_VERSION = 2.1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
|
|
@ -733,6 +749,10 @@
|
|||
DEVELOPMENT_TEAM = SZ977XLF24;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/StikJIT/Sources",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = StikJIT/Info.plist;
|
||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||
|
|
@ -755,7 +775,7 @@
|
|||
"$(PROJECT_DIR)/StikJIT/idevice",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 2.0.3;
|
||||
MARKETING_VERSION = 2.1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
|
|
@ -991,6 +1011,30 @@
|
|||
minimumVersion = 0.15.4;
|
||||
};
|
||||
};
|
||||
DC7E2F1A2E38D01E00725B2C /* XCRemoteSwiftPackageReference "zsign-ios" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/StephenDev0/zsign-ios";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
DCBA85172E388CD400E88C06 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/weichsel/ZIPFoundation";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.9.19;
|
||||
};
|
||||
};
|
||||
DCBA85842E3897BD00E88C06 /* XCRemoteSwiftPackageReference "StikImporter" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/StephenDev0/StikImporter";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.2;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
|
|
@ -1009,6 +1053,21 @@
|
|||
package = 68D569BC2E1B415700A5BA36 /* XCRemoteSwiftPackageReference "CodeEditorView" */;
|
||||
productName = LanguageSupport;
|
||||
};
|
||||
DC7E2F1B2E38D01E00725B2C /* ZSignApple */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DC7E2F1A2E38D01E00725B2C /* XCRemoteSwiftPackageReference "zsign-ios" */;
|
||||
productName = ZSignApple;
|
||||
};
|
||||
DCBA85182E388CD400E88C06 /* ZIPFoundation */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DCBA85172E388CD400E88C06 /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
|
||||
productName = ZIPFoundation;
|
||||
};
|
||||
DCBA85852E3897BD00E88C06 /* StikImporter */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DCBA85842E3897BD00E88C06 /* XCRemoteSwiftPackageReference "StikImporter" */;
|
||||
productName = StikImporter;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = DC6F1D2F2D94EADD0071B2B6 /* Project object */;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"originHash" : "4df9bfc6bef31f016f92a5a4a2b67290e6fd98a0453ec6e1981c9a8fb5222170",
|
||||
"originHash" : "a7d8c65e2fd86cffbcb641147283c4520fb198ba8311739d1d27b74ef46eee96",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "codeeditorview",
|
||||
|
|
@ -19,6 +19,15 @@
|
|||
"version" : "1.8.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "stikimporter",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/StephenDev0/StikImporter",
|
||||
"state" : {
|
||||
"revision" : "4d80fca1334493889501d5b5a9fdcbb262e11f24",
|
||||
"version" : "1.0.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftui-pipify",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
|
@ -27,6 +36,24 @@
|
|||
"branch" : "main",
|
||||
"revision" : "a1ec2fd1781c8289bff1a8b3f664dcf21c910efb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "zipfoundation",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/weichsel/ZIPFoundation",
|
||||
"state" : {
|
||||
"revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0",
|
||||
"version" : "0.9.19"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "zsign-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/StephenDev0/zsign-ios",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "a7f275fb4830ea48adf536fcddee7d05471970d0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
|
|
|
|||
44
StikJIT/Utilities/KeychainHelper.swift
Normal file
44
StikJIT/Utilities/KeychainHelper.swift
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// KeychainHelper.swift
|
||||
// StikDebug
|
||||
//
|
||||
// Created by Stephen on 7/29/25.
|
||||
//
|
||||
|
||||
import Security
|
||||
|
||||
final class KeychainHelper {
|
||||
static let shared = KeychainHelper()
|
||||
private init() {}
|
||||
|
||||
func save(password: String, forKey key: String) {
|
||||
let pwData = password.data(using: .utf8)!
|
||||
|
||||
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key]
|
||||
SecItemDelete(query as CFDictionary)
|
||||
|
||||
let add: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecValueData as String: pwData,
|
||||
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock]
|
||||
SecItemAdd(add as CFDictionary, nil)
|
||||
}
|
||||
|
||||
func readPassword(forKey key: String) -> String? {
|
||||
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecReturnData as String: true,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne]
|
||||
var out: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &out)
|
||||
guard status == errSecSuccess, let data = out as? Data else { return nil }
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
|
||||
func deletePassword(forKey key: String) {
|
||||
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key]
|
||||
SecItemDelete(query as CFDictionary)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import SwiftUI
|
|||
struct MainTabView: View {
|
||||
@AppStorage("customAccentColor") private var customAccentColorHex: String = ""
|
||||
@AppStorage("enableAdvancedOptions") private var enableAdvancedOptions = false
|
||||
@AppStorage("enableTesting") private var enableTesting = false
|
||||
|
||||
private var accentColor: Color {
|
||||
if customAccentColorHex.isEmpty {
|
||||
|
|
@ -31,6 +32,12 @@ struct MainTabView: View {
|
|||
Label("Scripts", systemImage: "scroll")
|
||||
}
|
||||
}
|
||||
if enableTesting {
|
||||
IPAAppManagerView()
|
||||
.tabItem {
|
||||
Label("Testing", systemImage: "square.grid.2x2")
|
||||
}
|
||||
}
|
||||
SettingsView()
|
||||
.tabItem {
|
||||
Label("Settings", systemImage: "gearshape.fill")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ struct SettingsView: View {
|
|||
@AppStorage("selectedAppIcon") private var selectedAppIcon: String = "AppIcon"
|
||||
@AppStorage("useDefaultScript") private var useDefaultScript = false
|
||||
@AppStorage("enableAdvancedOptions") private var enableAdvancedOptions = false
|
||||
@AppStorage("enableAdvancedBetaOptions") private var enableAdvancedBetaOptions = false
|
||||
@AppStorage("enableTesting") private var enableTesting = false
|
||||
@AppStorage("enablePiP") private var enablePiP = false
|
||||
|
||||
@State private var isShowingPairingFilePicker = false
|
||||
|
|
@ -284,6 +286,14 @@ struct SettingsView: View {
|
|||
Toggle("Picture in Picture", isOn: $enablePiP)
|
||||
.foregroundColor(.primary)
|
||||
.padding(.vertical, 6)
|
||||
Toggle("Enable Advanced Beta Options", isOn: $enableAdvancedBetaOptions)
|
||||
.foregroundColor(.primary)
|
||||
.padding(.vertical, 6)
|
||||
if enableAdvancedBetaOptions {
|
||||
Toggle("Enable Self Developed Application Testing", isOn: $enableTesting)
|
||||
.foregroundColor(.primary)
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 20)
|
||||
|
|
|
|||
571
StikJIT/Views/Something.swift
Normal file
571
StikJIT/Views/Something.swift
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
//
|
||||
// Something.swift
|
||||
// StikDebug
|
||||
//
|
||||
// Created by Stephen on 7/29/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
import ZIPFoundation
|
||||
import ZSign
|
||||
import StikImporter
|
||||
|
||||
@_silgen_name("install_ipa")
|
||||
private func c_install_ipa(
|
||||
_ ip: UnsafePointer<CChar>,
|
||||
_ pairing: UnsafePointer<CChar>,
|
||||
_ udid: UnsafePointer<CChar>?,
|
||||
_ ipa: UnsafePointer<CChar>
|
||||
) -> Int32
|
||||
|
||||
private func installErrorMessage(_ code: Int32) -> String {
|
||||
switch code {
|
||||
case 0: return "Success"
|
||||
case 1: return "Pairing file unreadable"
|
||||
case 2: return "TCP provider error"
|
||||
case 3: return "AFC connect error"
|
||||
case 4: return "IPA unreadable"
|
||||
case 5: return "AFC open error"
|
||||
case 6: return "AFC write error"
|
||||
case 7: return "Install-proxy error"
|
||||
case 8: return "Device refused IPA"
|
||||
case 9: return "Invalid IP address"
|
||||
default: return "Unknown (\(code))"
|
||||
}
|
||||
}
|
||||
|
||||
private let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
private let appsJSON = docs.appendingPathComponent("signed_apps.json")
|
||||
private let certsJSON = docs.appendingPathComponent("certs.json")
|
||||
|
||||
struct SignedApp: Identifiable, Codable, Hashable {
|
||||
let id: UUID
|
||||
let name: String
|
||||
let bundleID: String
|
||||
let version: String
|
||||
let ipaPath: String
|
||||
let iconPath: String?
|
||||
|
||||
var ipaURL: URL { docs.appendingPathComponent(ipaPath) }
|
||||
var iconURL: URL? { iconPath.map { docs.appendingPathComponent($0) } }
|
||||
}
|
||||
|
||||
struct Certificate: Identifiable, Codable, Hashable {
|
||||
let id: UUID
|
||||
let name: String
|
||||
let p12Path: String
|
||||
let mobPath: String?
|
||||
|
||||
var p12URL: URL { docs.appendingPathComponent(p12Path) }
|
||||
var mobURL: URL? { mobPath.map { docs.appendingPathComponent($0) } }
|
||||
}
|
||||
|
||||
private enum FSX {
|
||||
static func mkdir(_ url: URL) throws {
|
||||
if !FileManager.default.fileExists(atPath: url.path) {
|
||||
try FileManager.default.createDirectory(at: url,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: [.posixPermissions: 0o755])
|
||||
}
|
||||
}
|
||||
static func copySecure(from src: URL, to dst: URL) throws {
|
||||
let ok = src.startAccessingSecurityScopedResource()
|
||||
defer { if ok { src.stopAccessingSecurityScopedResource() } }
|
||||
if FileManager.default.fileExists(atPath: dst.path) { try FileManager.default.removeItem(at: dst) }
|
||||
try FileManager.default.copyItem(at: src, to: dst)
|
||||
try FileManager.default.setAttributes([.posixPermissions: 0o644], ofItemAtPath: dst.path)
|
||||
}
|
||||
static func perms(app: URL) throws {
|
||||
let keys: [URLResourceKey] = [.isDirectoryKey, .isExecutableKey]
|
||||
guard let e = FileManager.default.enumerator(at: app, includingPropertiesForKeys: keys) else { return }
|
||||
for case let f as URL in e {
|
||||
let rv = try f.resourceValues(forKeys: Set(keys))
|
||||
if rv.isDirectory == true {
|
||||
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: f.path)
|
||||
} else {
|
||||
let exec = rv.isExecutable == true ||
|
||||
f.lastPathComponent == app.lastPathComponent.replacingOccurrences(of: ".app", with: "") ||
|
||||
(f.pathExtension.isEmpty && !f.lastPathComponent.contains("."))
|
||||
try FileManager.default.setAttributes([.posixPermissions: exec ? 0o755 : 0o644],
|
||||
ofItemAtPath: f.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class AppSignerManager: ObservableObject {
|
||||
|
||||
private var didFinishInitialLoad = false
|
||||
|
||||
@Published var apps: [SignedApp] = [] {
|
||||
didSet { if didFinishInitialLoad { saveApps() } }
|
||||
}
|
||||
@Published var certs: [Certificate] = [] {
|
||||
didSet { if didFinishInitialLoad { saveCerts() } }
|
||||
}
|
||||
@Published var selectedCertID: UUID?
|
||||
@Published var busy = false
|
||||
@Published var ipaURL: URL?
|
||||
@Published var ipaName: String?
|
||||
|
||||
private let imports = docs.appendingPathComponent("Imports", isDirectory: true)
|
||||
private let certsFolder = docs.appendingPathComponent("Certs", isDirectory: true)
|
||||
|
||||
init() {
|
||||
try? FSX.mkdir(imports)
|
||||
try? FSX.mkdir(certsFolder)
|
||||
migrateAndLoadApps()
|
||||
migrateAndLoadCerts()
|
||||
didFinishInitialLoad = true
|
||||
}
|
||||
|
||||
private func migrateAndLoadApps() {
|
||||
guard let data = try? Data(contentsOf: appsJSON),
|
||||
let stored = try? JSONDecoder().decode([SignedApp].self, from: data) else { return }
|
||||
|
||||
var fixed: [SignedApp] = []
|
||||
for app in stored {
|
||||
if FileManager.default.fileExists(atPath: app.ipaURL.path) {
|
||||
fixed.append(app)
|
||||
continue
|
||||
}
|
||||
let expectedRel = "SignedApps/\(app.name)/\(app.name).ipa"
|
||||
let newAbs = docs.appendingPathComponent(expectedRel).path
|
||||
if FileManager.default.fileExists(atPath: newAbs) {
|
||||
let newIconRel = "SignedApps/\(app.name)/icon.png"
|
||||
fixed.append(SignedApp(
|
||||
id: app.id, name: app.name, bundleID: app.bundleID,
|
||||
version: app.version, ipaPath: expectedRel,
|
||||
iconPath: FileManager.default.fileExists(atPath: docs.appendingPathComponent(newIconRel).path) ? newIconRel : nil))
|
||||
}
|
||||
}
|
||||
apps = fixed
|
||||
}
|
||||
private func saveApps() {
|
||||
guard let data = try? JSONEncoder().encode(apps) else { return }
|
||||
try? data.write(to: appsJSON)
|
||||
}
|
||||
|
||||
private func migrateAndLoadCerts() {
|
||||
guard let data = try? Data(contentsOf: certsJSON),
|
||||
let stored = try? JSONDecoder().decode([Certificate].self, from: data) else { return }
|
||||
|
||||
var fixed: [Certificate] = []
|
||||
for cert in stored {
|
||||
if FileManager.default.fileExists(atPath: cert.p12URL.path) &&
|
||||
(cert.mobURL == nil || FileManager.default.fileExists(atPath: cert.mobURL!.path)) {
|
||||
fixed.append(cert) // already fine
|
||||
continue
|
||||
}
|
||||
let dir = certsFolder.appendingPathComponent(cert.id.uuidString)
|
||||
let newP12Rel = "Certs/\(cert.id.uuidString)/cert.p12"
|
||||
let newMobRel = "Certs/\(cert.id.uuidString)/profile.mobileprovision"
|
||||
if FileManager.default.fileExists(atPath: dir.appendingPathComponent("cert.p12").path) {
|
||||
fixed.append(Certificate(
|
||||
id: cert.id, name: cert.name,
|
||||
p12Path: newP12Rel,
|
||||
mobPath: FileManager.default.fileExists(atPath: dir.appendingPathComponent("profile.mobileprovision").path) ? newMobRel : nil))
|
||||
}
|
||||
}
|
||||
certs = fixed
|
||||
if selectedCertID == nil { selectedCertID = certs.first?.id }
|
||||
}
|
||||
private func saveCerts() {
|
||||
guard let data = try? JSONEncoder().encode(certs) else { return }
|
||||
try? data.write(to: certsJSON)
|
||||
}
|
||||
|
||||
func deleteApp(_ app: SignedApp) {
|
||||
if let idx = apps.firstIndex(of: app) {
|
||||
try? FileManager.default.removeItem(at: app.ipaURL.deletingLastPathComponent())
|
||||
apps.remove(at: idx)
|
||||
}
|
||||
}
|
||||
|
||||
func importIPA(from url: URL) throws {
|
||||
let dst = imports.appendingPathComponent("current.ipa")
|
||||
try FSX.copySecure(from: url, to: dst)
|
||||
ipaURL = dst
|
||||
ipaName = try parseIPAName(at: dst)
|
||||
}
|
||||
private func parseIPAName(at ipa: URL) throws -> String? {
|
||||
let tmp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FSX.mkdir(tmp)
|
||||
try FileManager.default.unzipItem(at: ipa, to: tmp)
|
||||
let payload = tmp.appendingPathComponent("Payload")
|
||||
let appDir = try FileManager.default.contentsOfDirectory(at: payload, includingPropertiesForKeys: nil)
|
||||
.first { $0.pathExtension == "app" }
|
||||
defer { try? FileManager.default.removeItem(at: tmp) }
|
||||
guard let dir = appDir,
|
||||
let dict = NSDictionary(contentsOf: dir.appendingPathComponent("Info.plist")) else { return nil }
|
||||
return (dict["CFBundleDisplayName"] ?? dict["CFBundleName"]) as? String
|
||||
}
|
||||
|
||||
func addCertificate(name: String, p12: URL, mob: URL, password: String,
|
||||
onErr: (String,String)->Void, onOK: (String,String)->Void) {
|
||||
let id = UUID()
|
||||
let dir = certsFolder.appendingPathComponent(id.uuidString, isDirectory: true)
|
||||
do {
|
||||
try FSX.mkdir(dir)
|
||||
try FSX.copySecure(from: p12, to: dir.appendingPathComponent("cert.p12"))
|
||||
try FSX.copySecure(from: mob, to: dir.appendingPathComponent("profile.mobileprovision"))
|
||||
KeychainHelper.shared.save(password: password, forKey: id.uuidString)
|
||||
|
||||
certs.append(Certificate(
|
||||
id: id, name: name,
|
||||
p12Path: "Certs/\(id.uuidString)/cert.p12",
|
||||
mobPath: "Certs/\(id.uuidString)/profile.mobileprovision"))
|
||||
selectedCertID = id
|
||||
onOK("Certificate added", name)
|
||||
} catch { onErr("Cert error", error.localizedDescription) }
|
||||
}
|
||||
|
||||
func signIPA(onErr: @escaping (String,String)->Void,
|
||||
onOK: @escaping (String,String)->Void) {
|
||||
guard !busy else { return }
|
||||
guard let ipa = ipaURL,
|
||||
let cert = certs.first(where: { $0.id == selectedCertID }) else {
|
||||
onErr("Missing data", "Import IPA and select certificate first"); return
|
||||
}
|
||||
busy = true
|
||||
Task.detached {
|
||||
do {
|
||||
let tmp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FSX.mkdir(tmp)
|
||||
_ = ipa.startAccessingSecurityScopedResource(); defer { ipa.stopAccessingSecurityScopedResource() }
|
||||
|
||||
try FileManager.default.unzipItem(at: ipa, to: tmp)
|
||||
let payload = tmp.appendingPathComponent("Payload")
|
||||
guard let appFolder = try FileManager.default.contentsOfDirectory(at: payload, includingPropertiesForKeys: nil)
|
||||
.first(where: { $0.pathExtension == "app" }) else {
|
||||
throw NSError(domain: "Signer", code: 1, userInfo: [NSLocalizedDescriptionKey: "No .app found"])
|
||||
}
|
||||
try FSX.perms(app: appFolder)
|
||||
guard
|
||||
let dict = NSDictionary(contentsOf: appFolder.appendingPathComponent("Info.plist")),
|
||||
let name = (dict["CFBundleDisplayName"] ?? dict["CFBundleName"]) as? String,
|
||||
let bundle = dict["CFBundleIdentifier"] as? String,
|
||||
let ver = dict["CFBundleShortVersionString"] as? String
|
||||
else {
|
||||
throw NSError(domain: "Signer", code: 2, userInfo: [NSLocalizedDescriptionKey: "Info.plist parse error"])
|
||||
}
|
||||
|
||||
let pw = KeychainHelper.shared.readPassword(forKey: cert.id.uuidString) ?? ""
|
||||
let rc = zsign(appFolder.path,
|
||||
cert.p12URL.path,
|
||||
cert.p12URL.path,
|
||||
cert.mobURL?.path ?? "",
|
||||
pw, "", "")
|
||||
guard rc == 0 else {
|
||||
throw NSError(domain: "zsign", code: Int(rc),
|
||||
userInfo: [NSLocalizedDescriptionKey: "zsign returned \(rc)"])
|
||||
}
|
||||
|
||||
let outDir = docs.appendingPathComponent("SignedApps/\(name)", isDirectory: true)
|
||||
if FileManager.default.fileExists(atPath: outDir.path) { try FileManager.default.removeItem(at: outDir) }
|
||||
try FSX.mkdir(outDir)
|
||||
let final = outDir.appendingPathComponent("\(name).ipa")
|
||||
try FileManager.default.zipItem(at: payload,
|
||||
to: outDir.appendingPathComponent("\(name).ipa"),
|
||||
shouldKeepParent: true)
|
||||
try FileManager.default.setAttributes([.posixPermissions: 0o644], ofItemAtPath: final.path)
|
||||
|
||||
var iconRel: String?
|
||||
if let iconURL = try? FileManager.default.contentsOfDirectory(at: appFolder, includingPropertiesForKeys: nil)
|
||||
.first(where: { $0.lastPathComponent.lowercased().contains("appicon") && $0.pathExtension == "png" }) {
|
||||
let dst = outDir.appendingPathComponent("icon.png")
|
||||
try? FileManager.default.copyItem(at: iconURL, to: dst)
|
||||
iconRel = "SignedApps/\(name)/icon.png"
|
||||
}
|
||||
|
||||
let relIPA = "SignedApps/\(name)/\(name).ipa"
|
||||
let newApp = SignedApp(
|
||||
id: UUID(), name: name, bundleID: bundle,
|
||||
version: ver, ipaPath: relIPA, iconPath: iconRel)
|
||||
|
||||
await MainActor.run {
|
||||
self.apps.removeAll { $0.name == newApp.name }
|
||||
self.apps.append(newApp)
|
||||
self.busy = false
|
||||
onOK("\(name) is ready for testing", "Saved to \(relIPA)")
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
self.busy = false
|
||||
onErr("Sign error", error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func install(app: SignedApp, ip: String, pairing: String,
|
||||
onErr: @escaping (String,String)->Void,
|
||||
onOK: @escaping (String,String)->Void) {
|
||||
guard !busy else { return }
|
||||
guard FileManager.default.fileExists(atPath: app.ipaURL.path) else { onErr("IPA missing", app.ipaURL.path); return }
|
||||
guard FileManager.default.fileExists(atPath: pairing) else { onErr("Pairing missing", pairing); return }
|
||||
busy = true
|
||||
Task.detached {
|
||||
let rc = ip.withCString { ipPtr in
|
||||
pairing.withCString { pairPtr in
|
||||
app.ipaURL.path.withCString { ipaPtr in
|
||||
c_install_ipa(ipPtr, pairPtr, nil, ipaPtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
await MainActor.run {
|
||||
self.busy = false
|
||||
rc == 0 ? onOK("Installed for testing", app.name)
|
||||
: onErr("Install failed", installErrorMessage(rc))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct IPAAppManagerView: View {
|
||||
@AppStorage("customAccentColor") private var customAccentColorHex: String = ""
|
||||
private var accent: Color {
|
||||
customAccentColorHex.isEmpty ? .blue : Color(hex: customAccentColorHex) ?? .blue
|
||||
}
|
||||
|
||||
@StateObject private var mgr = AppSignerManager()
|
||||
@State private var pickerShown = false
|
||||
@State private var showAddCert = false
|
||||
@State private var alert = false
|
||||
@State private var alertTitle = ""
|
||||
@State private var alertMsg = ""
|
||||
@State private var alertSuccess = false
|
||||
@AppStorage("deviceIP") private var deviceIP = "10.7.0.2"
|
||||
private var pairingPath: String { docs.appendingPathComponent("pairingFile.plist").path }
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
Color(UIColor.systemBackground).ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 14) {
|
||||
signCard
|
||||
appsCard
|
||||
versionInfo
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
|
||||
if mgr.busy {
|
||||
Color.black.opacity(0.35).ignoresSafeArea()
|
||||
ProgressView()
|
||||
.padding()
|
||||
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
if alert {
|
||||
CustomErrorView(title: alertTitle,
|
||||
message: alertMsg,
|
||||
onDismiss: { alert = false },
|
||||
messageType: alertSuccess ? .success : .error)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Testing")
|
||||
.stikImporter(isPresented: $pickerShown,
|
||||
selectedURLs: .constant([]),
|
||||
allowedContentTypes: [.item],
|
||||
allowsMultipleSelection: false) { urls in
|
||||
if let u = urls.first {
|
||||
do { try mgr.importIPA(from: u); notify("File imported", "File saved to Imports") }
|
||||
catch { fail("Import error", error.localizedDescription) }
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showAddCert) {
|
||||
AddCertView(accent: accent) { n, p12, mob, pw in
|
||||
mgr.addCertificate(name: n, p12: p12, mob: mob, password: pw,
|
||||
onErr: fail, onOK: notify)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var signCard: some View {
|
||||
SettingsCard {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
Text("Prepare for testing")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
VStack(spacing: 10) {
|
||||
importButton(label: mgr.ipaName ?? "Self Developed Application Archive File",
|
||||
icon: "arrow.down.doc",
|
||||
imported: mgr.ipaURL != nil) { pickerShown = true }
|
||||
|
||||
Picker("Certificate", selection: $mgr.selectedCertID) {
|
||||
ForEach(mgr.certs) { Text($0.name).tag(Optional($0.id)) }
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Button("Add Developer Certificate") { showAddCert = true }
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 14)
|
||||
.background(Color.gray.opacity(0.4))
|
||||
.foregroundColor(accent.contrastText())
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
|
||||
Button("Prepare") { mgr.signIPA(onErr: fail, onOK: notify) }
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 14)
|
||||
.background(accent)
|
||||
.foregroundColor(accent.contrastText())
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
.disabled(mgr.busy || mgr.ipaURL == nil || mgr.selectedCertID == nil)
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
}
|
||||
}
|
||||
|
||||
private var appsCard: some View {
|
||||
SettingsCard {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
Text("Ready to test apps")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
if mgr.apps.isEmpty {
|
||||
Text("No ready to test apps yet")
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
ForEach(mgr.apps) { app in
|
||||
HStack(spacing: 12) {
|
||||
if let img = app.iconURL.flatMap({ UIImage(contentsOfFile: $0.path) }) {
|
||||
Image(uiImage: img)
|
||||
.resizable()
|
||||
.frame(width: 44, height: 44)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Text(app.name).bold()
|
||||
Text("v\(app.version) • \(app.bundleID)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Button("Test") {
|
||||
mgr.install(app: app, ip: deviceIP, pairing: pairingPath,
|
||||
onErr: fail, onOK: notify)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.disabled(mgr.busy)
|
||||
|
||||
Button(role: .destructive) {
|
||||
mgr.deleteApp(app)
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
}
|
||||
}
|
||||
|
||||
private var versionInfo: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("iOS \(UIDevice.current.systemVersion)")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 6)
|
||||
}
|
||||
|
||||
private func importButton(label: String, icon: String,
|
||||
imported: Bool, action: @escaping () -> Void) -> some View {
|
||||
Button(action: action) {
|
||||
HStack {
|
||||
Image(systemName: icon)
|
||||
Text(label)
|
||||
Spacer()
|
||||
Image(systemName: imported ? "checkmark.circle.fill" : "chevron.right")
|
||||
}
|
||||
.foregroundColor(imported ? .primary : .secondary)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 12)
|
||||
.background(Color(UIColor.secondarySystemBackground))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
.disabled(mgr.busy)
|
||||
}
|
||||
|
||||
private func fail(_ title: String, _ msg: String) {
|
||||
alertTitle = title; alertMsg = msg; alertSuccess = false; alert = true
|
||||
}
|
||||
private func notify(_ title: String, _ msg: String) {
|
||||
alertTitle = title; alertMsg = msg; alertSuccess = true; alert = true
|
||||
}
|
||||
}
|
||||
|
||||
private struct AddCertView: View {
|
||||
let accent: Color
|
||||
var onSave: (String, URL, URL, String) -> Void
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var name = ""
|
||||
@State private var p12URL: URL?
|
||||
@State private var mobURL: URL?
|
||||
@State private var password = ""
|
||||
|
||||
@State private var pickP12 = false
|
||||
@State private var pickMob = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
TextField("Certificate name", text: $name)
|
||||
importRow(label: ".mobileprovision", picked: mobURL != nil) { pickMob = true }
|
||||
.stikImporter(isPresented: $pickMob,
|
||||
selectedURLs: .constant([]),
|
||||
allowedContentTypes: [UTType.item],
|
||||
allowsMultipleSelection: false) { mobURL = $0.first }
|
||||
importRow(label: ".p12 file", picked: p12URL != nil) { pickP12 = true }
|
||||
.stikImporter(isPresented: $pickP12,
|
||||
selectedURLs: .constant([]),
|
||||
allowedContentTypes: [UTType.item],
|
||||
allowsMultipleSelection: false) { p12URL = $0.first }
|
||||
SecureField("p12 password", text: $password)
|
||||
}
|
||||
.navigationTitle("New Certificate")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Save") {
|
||||
if let p = p12URL, let m = mobURL, !name.isEmpty {
|
||||
onSave(name, p, m, password); dismiss()
|
||||
}
|
||||
}
|
||||
.disabled(p12URL == nil || mobURL == nil || name.isEmpty)
|
||||
}
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") { dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func importRow(label: String, picked: Bool,
|
||||
action: @escaping () -> Void) -> some View {
|
||||
Button(action: action) {
|
||||
HStack {
|
||||
Text(label)
|
||||
Spacer()
|
||||
Image(systemName: picked ? "checkmark.circle.fill" : "chevron.right")
|
||||
.foregroundColor(picked ? .green : accent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
166
StikJIT/idevice/something.c
Normal file
166
StikJIT/idevice/something.c
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
//
|
||||
// something.c
|
||||
// StikDebug
|
||||
//
|
||||
// Created by Stephen on 7/29/25.
|
||||
//
|
||||
|
||||
#include "something.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int mmap_file(const char *path, uint8_t **data, size_t *len)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
return 0;
|
||||
}
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0) {
|
||||
perror("fstat");
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
*len = (size_t)st.st_size;
|
||||
*data = mmap(NULL, *len, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
close(fd);
|
||||
if (*data == MAP_FAILED) {
|
||||
perror("mmap");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void munmap_file(uint8_t *data, size_t len)
|
||||
{
|
||||
if (data && data != MAP_FAILED) {
|
||||
munmap(data, len);
|
||||
}
|
||||
}
|
||||
|
||||
int install_ipa(const char *ip,
|
||||
const char *pairing_file_path,
|
||||
const char *udid,
|
||||
const char *ipa_path)
|
||||
{
|
||||
(void)udid;
|
||||
idevice_init_logger(Debug, Disabled, NULL);
|
||||
|
||||
struct sockaddr_in addr = {0};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(LOCKDOWN_PORT);
|
||||
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
|
||||
fprintf(stderr, "Invalid IP address: %s\n", ip);
|
||||
return IPA_ERR_INVALID_IP;
|
||||
}
|
||||
|
||||
IdevicePairingFile *pairing_file = NULL;
|
||||
IdeviceFfiError *err = idevice_pairing_file_read(pairing_file_path,
|
||||
&pairing_file);
|
||||
if (err) {
|
||||
fprintf(stderr, "Pairing file read failed: [%d] %s\n",
|
||||
err->code, err->message);
|
||||
idevice_error_free(err);
|
||||
return IPA_ERR_PAIRING_READ;
|
||||
}
|
||||
|
||||
IdeviceProviderHandle *provider = NULL;
|
||||
err = idevice_tcp_provider_new((struct sockaddr *)&addr,
|
||||
pairing_file,
|
||||
"IPAInstaller",
|
||||
&provider);
|
||||
if (err) {
|
||||
fprintf(stderr, "Provider create failed: [%d] %s\n",
|
||||
err->code, err->message);
|
||||
idevice_pairing_file_free(pairing_file);
|
||||
idevice_error_free(err);
|
||||
return IPA_ERR_PROVIDER_CREATE;
|
||||
}
|
||||
|
||||
AfcClientHandle *afc = NULL;
|
||||
err = afc_client_connect(provider, &afc);
|
||||
if (err) {
|
||||
fprintf(stderr, "AFC connect failed: [%d] %s\n",
|
||||
err->code, err->message);
|
||||
idevice_provider_free(provider);
|
||||
idevice_pairing_file_free(pairing_file);
|
||||
idevice_error_free(err);
|
||||
return IPA_ERR_AFC_CONNECT;
|
||||
}
|
||||
|
||||
uint8_t *ipa_data = NULL;
|
||||
size_t ipa_len = 0;
|
||||
if (!mmap_file(ipa_path, &ipa_data, &ipa_len)) {
|
||||
fprintf(stderr, "Unable to read IPA file\n");
|
||||
afc_client_free(afc);
|
||||
idevice_provider_free(provider);
|
||||
idevice_pairing_file_free(pairing_file);
|
||||
return IPA_ERR_IPA_READ;
|
||||
}
|
||||
|
||||
const char *slash = strrchr(ipa_path, '/');
|
||||
const char *fname = slash ? slash + 1 : ipa_path;
|
||||
char dest[256];
|
||||
snprintf(dest, sizeof(dest), "/PublicStaging/%s", fname);
|
||||
|
||||
AfcFileHandle *remote = NULL;
|
||||
err = afc_file_open(afc, dest, AfcWrOnly, &remote);
|
||||
if (err) {
|
||||
fprintf(stderr, "AFC open failed: [%d] %s\n",
|
||||
err->code, err->message);
|
||||
munmap_file(ipa_data, ipa_len);
|
||||
afc_client_free(afc);
|
||||
idevice_provider_free(provider);
|
||||
idevice_pairing_file_free(pairing_file);
|
||||
idevice_error_free(err);
|
||||
return IPA_ERR_AFC_OPEN;
|
||||
}
|
||||
|
||||
err = afc_file_write(remote, ipa_data, ipa_len);
|
||||
afc_file_close(remote);
|
||||
munmap_file(ipa_data, ipa_len);
|
||||
if (err) {
|
||||
fprintf(stderr, "AFC write failed: [%d] %s\n",
|
||||
err->code, err->message);
|
||||
afc_client_free(afc);
|
||||
idevice_provider_free(provider);
|
||||
idevice_pairing_file_free(pairing_file);
|
||||
idevice_error_free(err);
|
||||
return IPA_ERR_AFC_WRITE;
|
||||
}
|
||||
|
||||
InstallationProxyClientHandle *ipc = NULL;
|
||||
err = installation_proxy_connect_tcp(provider, &ipc);
|
||||
if (err) {
|
||||
fprintf(stderr, "installation_proxy connect failed: [%d] %s\n",
|
||||
err->code, err->message);
|
||||
afc_client_free(afc);
|
||||
idevice_provider_free(provider);
|
||||
idevice_pairing_file_free(pairing_file);
|
||||
idevice_error_free(err);
|
||||
return IPA_ERR_INSTALLPROXY_CONNECT;
|
||||
}
|
||||
|
||||
err = installation_proxy_install(ipc, dest, NULL);
|
||||
installation_proxy_client_free(ipc);
|
||||
afc_client_free(afc);
|
||||
idevice_provider_free(provider);
|
||||
|
||||
if (err) {
|
||||
fprintf(stderr, "IPA install failed: [%d] %s\n",
|
||||
err->code, err->message);
|
||||
idevice_error_free(err);
|
||||
return IPA_ERR_INSTALL;
|
||||
}
|
||||
|
||||
fprintf(stderr, "IPA installed successfully\n");
|
||||
return IPA_OK;
|
||||
}
|
||||
32
StikJIT/idevice/something.h
Normal file
32
StikJIT/idevice/something.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// something.h
|
||||
// StikDebug
|
||||
//
|
||||
// Created by Stephen on 7/29/25.
|
||||
//
|
||||
|
||||
#ifndef SOMETHING_H
|
||||
#define SOMETHING_H
|
||||
|
||||
#include "idevice.h"
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
IPA_OK = 0, /* success */
|
||||
IPA_ERR_PAIRING_READ = 1, /* could not read pairing file */
|
||||
IPA_ERR_PROVIDER_CREATE = 2, /* idevice_tcp_provider_new fail */
|
||||
IPA_ERR_AFC_CONNECT = 3, /* afc_client_connect fail */
|
||||
IPA_ERR_IPA_READ = 4, /* failed to mmap or read IPA */
|
||||
IPA_ERR_AFC_OPEN = 5, /* afc_file_open fail */
|
||||
IPA_ERR_AFC_WRITE = 6, /* afc_file_write fail */
|
||||
IPA_ERR_INSTALLPROXY_CONNECT = 7, /* installation_proxy connect */
|
||||
IPA_ERR_INSTALL = 8, /* installation_proxy_install */
|
||||
IPA_ERR_INVALID_IP = 9, /* inet_pton failed */
|
||||
} ipa_installer_error_t;
|
||||
|
||||
int install_ipa(const char *ip,
|
||||
const char *pairing_file_path,
|
||||
const char *udid,
|
||||
const char *ipa_path);
|
||||
|
||||
#endif /* SOMETHING_H */
|
||||
Loading…
Add table
Add a link
Reference in a new issue