Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions StikJIT.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,22 @@
DC6F1D522D94EADF0071B2B6 /* StikJITUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StikJITUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
1775D3612D9644FD00DFA8E0 /* Exceptions for "StikJIT" folder in "StikJIT" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = DC6F1D362D94EADD0071B2B6 /* StikJIT */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
DC6F1D392D94EADD0071B2B6 /* StikJIT */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
1775D3612D9644FD00DFA8E0 /* Exceptions for "StikJIT" folder in "StikJIT" target */,
);
path = StikJIT;
sourceTree = "<group>";
};
Expand Down Expand Up @@ -429,6 +442,7 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = StikJIT/Info.plist;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand All @@ -439,6 +453,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
Expand Down Expand Up @@ -475,6 +490,7 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = StikJIT/Info.plist;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand All @@ -485,6 +501,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
Expand Down
19 changes: 19 additions & 0 deletions StikJIT/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.stik.StikJIT.enableJIT</string>
<key>CFBundleURLSchemes</key>
<array>
<string>stikjit</string>
</array>
</dict>
</array>
</dict>
</plist>
6 changes: 2 additions & 4 deletions StikJIT/StikJIT-Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,5 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//

#include "idevice.h"
#include "heartbeat.h"
#include "jit.h"
#include "applist.h"
#include "idevice/JITEnableContext.h"
#include "idevice/idevice.h"
10 changes: 4 additions & 6 deletions StikJIT/StikJITApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ struct HeartbeatApp: App {

func startHeartbeatInBackground() {
let heartBeat = Thread {
let cCompletionHandler: @convention(block) (Int32, UnsafePointer<CChar>?) -> Void = { result, messagePointer in
let message: String? = messagePointer != nil ? String(cString: messagePointer!) : nil
let completionHandler: @convention(block) (Int32, String?) -> Void = { result, message in

if result == 0 {
print("Heartbeat started successfully: \(message ?? "")")
Expand All @@ -122,7 +121,7 @@ struct HeartbeatApp: App {
}
}

startHeartbeat(cCompletionHandler)
JITEnableContext.shared().startHeartbeat(completionHandler: completionHandler, logger: nil)
}

heartBeat.qualityOfService = .background
Expand All @@ -135,8 +134,7 @@ struct HeartbeatApp: App {

func startHeartbeatInBackground() {
let heartBeat = Thread {
let cCompletionHandler: @convention(block) (Int32, UnsafePointer<CChar>?) -> Void = { result, messagePointer in
let message: String? = messagePointer != nil ? String(cString: messagePointer!) : nil
let completionHandler: @convention(block) (Int32, String?) -> Void = { result, message in

if result == 0 {
print("Heartbeat started successfully: \(message ?? "")")
Expand All @@ -149,7 +147,7 @@ func startHeartbeatInBackground() {
}
}

startHeartbeat(cCompletionHandler)
JITEnableContext.shared().startHeartbeat(completionHandler: completionHandler, logger: nil)
}

heartBeat.qualityOfService = .background
Expand Down
66 changes: 32 additions & 34 deletions StikJIT/Views/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ struct HomeView: View {
@State private var pairingFileIsValid = false
@State private var isImportingFile = false
@State private var importProgress: Float = 0.0

@State private var viewDidAppeared = false
@State private var pendingBundleIdToEnableJIT : String? = nil

var body: some View {
ZStack {
Expand Down Expand Up @@ -209,8 +212,33 @@ struct HomeView: View {
startJITInBackground(with: selectedBundle)
}
}
.onOpenURL { url in
print(url.path())
if url.host() != "enable-jit" {
return
}

let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
if let bundleId = components?.queryItems?.first(where: { $0.name == "bundle-id" })?.value {
if viewDidAppeared {
startJITInBackground(with: bundleId)
} else {
pendingBundleIdToEnableJIT = bundleId
}
}

}
.onAppear() {
viewDidAppeared = true
if let pendingBundleIdToEnableJIT {
startJITInBackground(with: pendingBundleIdToEnableJIT)
self.pendingBundleIdToEnableJIT = nil
}
}
}



private func checkPairingFileExists() {
pairingFileExists = FileManager.default.fileExists(atPath: URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path)
}
Expand All @@ -222,14 +250,9 @@ struct HomeView: View {
private func startJITInBackground(with bundleID: String) {
isProcessing = true
DispatchQueue.global(qos: .background).async {
guard let cBundleID = strdup(bundleID) else {
DispatchQueue.main.async { isProcessing = false }
return
}

_ = debug_app(cBundleID)
JITEnableContext.shared().debugApp(withBundleID: bundleID, logger: nil)

free(cBundleID)
DispatchQueue.main.async {
isProcessing = false
}
Expand All @@ -245,38 +268,13 @@ class InstalledAppsViewModel: ObservableObject {
}

func loadApps() {
guard let rawPointer = list_installed_apps() else {
self.apps = [:]
return
}

let output = String(cString: rawPointer)
free(rawPointer)

guard let jsonData = output.data(using: .utf8) else {
print("Error: Failed to convert string to data")
self.apps = [:]
return
}

print(output)

// Decode the JSON into a Swift dictionary
do {
let decoder = JSONDecoder()
let apps = try decoder.decode([String: String].self, from: jsonData)
if let app = apps.first, app.key == "error" {
self.apps = [:]
} else {
self.apps = apps
}
return
self.apps = try JITEnableContext.shared().getAppList()
} catch {
print("Error: Failed to decode JSON - \(error)")
print(error)
self.apps = [:]
return
}

}
}

Expand Down
2 changes: 1 addition & 1 deletion StikJIT/Views/InstalledAppsListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct InstalledAppsListView: View {
NavigationView {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(viewModel.apps.sorted(by: { $0.key < $1.key }), id: \.key) { appName, bundleID in
ForEach(viewModel.apps.sorted(by: { $0.key < $1.key }), id: \.key) { bundleID, appName in
Button(action: {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
onSelectApp(bundleID)
Expand Down
20 changes: 20 additions & 0 deletions StikJIT/idevice/JITEnableContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// JITEnableContext.h
// StikJIT
//
// Created by s s on 2025/3/28.
//
@import Foundation;
#include "idevice.h"

typedef void (^HeartbeatCompletionHandler)(int result, NSString *message);
typedef void (^LogFuncC)(const char* message, ...);
typedef void (^LogFunc)(NSString *message);

@interface JITEnableContext : NSObject
+ (instancetype)shared;
- (IdevicePairingFile*)getPairingFileWithError:(NSError**)error;
- (void)startHeartbeatWithCompletionHandler:(HeartbeatCompletionHandler)completionHandler logger:(LogFunc)logger;
- (void)debugAppWithBundleID:(NSString*)bundleID logger:(LogFunc)logger;
- (NSDictionary<NSString*, NSString*>*)getAppListWithError:(NSError**)error;
@end
125 changes: 125 additions & 0 deletions StikJIT/idevice/JITEnableContext.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// JITEnableContext.m
// StikJIT
//
// Created by s s on 2025/3/28.
//
#include "idevice.h"
#include <arpa/inet.h>
#include <stdlib.h>

#include "heartbeat.h"
#include "jit.h"
#include "applist.h"

#include "JITEnableContext.h"

JITEnableContext* sharedJITContext = nil;

@implementation JITEnableContext {
int heartbeatSessionId;
TcpProviderHandle* provider;
}

+ (instancetype)shared {
if(!sharedJITContext) {
sharedJITContext = [[JITEnableContext alloc] init];
}
return sharedJITContext;
}

- (NSError*)errorWithStr:(NSString*)str code:(int)code {
return [NSError errorWithDomain:@"StikJIT" code:code userInfo:@{NSLocalizedDescriptionKey: str}];
}

- (LogFuncC)createCLogger:(LogFunc)logger {
return ^(const char* format, ...) {
va_list args;
va_start(args, format);
NSString* formatStr = [NSString stringWithCString:format encoding:NSASCIIStringEncoding];

NSString *message = [[NSString alloc] initWithFormat:formatStr arguments:args];
NSLog(@"%@", message);

if(logger) {
logger(message);
}

va_end(args);
};
}

- (IdevicePairingFile*)getPairingFileWithError:(NSError**)error {
NSFileManager* fm = [NSFileManager defaultManager];
NSURL* docPathUrl = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
NSURL* pairingFileURL = [docPathUrl URLByAppendingPathComponent:@"pairingFile.plist"];
if(![fm fileExistsAtPath:pairingFileURL.path]) {
NSLog(@"Pairing file not found!");
*error = [self errorWithStr:@"Pairing file not found!" code:-17];
return false;
}

IdevicePairingFile* pairingFile = NULL;
IdeviceErrorCode err = idevice_pairing_file_read(pairingFileURL.fileSystemRepresentation, &pairingFile);
if (err != IdeviceSuccess) {
*error = [self errorWithStr:@"Failed to read pairing file!" code:err];
return nil;
}
return pairingFile;
}

- (void)startHeartbeatWithCompletionHandler:(HeartbeatCompletionHandler)completionHandler logger:(LogFunc)logger {
NSError* err = nil;
IdevicePairingFile* pairingFile = [self getPairingFileWithError:&err];
if(err) {
if(logger) {
logger(err.localizedDescription);
}

completionHandler(-17, err.localizedDescription);
return;
}
self->heartbeatSessionId = arc4random();
startHeartbeat(pairingFile, &(self->provider), &(self->heartbeatSessionId), ^(int result, const char *message) {
completionHandler(result,[NSString stringWithCString:message encoding:NSASCIIStringEncoding]);
}, [self createCLogger:logger]);
}
- (void)debugAppWithBundleID:(NSString*)bundleID logger:(LogFunc)logger {
if(!provider) {
if(logger) {
logger(@"Provider not initialized!");
}
NSLog(@"Provider not initialized!");
return;
}

debug_app(provider, [bundleID UTF8String], [self createCLogger:logger]);
}


// apps may have different name, so we must use BnudleId as key. [bundleId:name]
- (NSDictionary<NSString*, NSString*>*)getAppListWithError:(NSError**)error {
if(!provider) {
NSLog(@"Provider not initialized!");
*error = [self errorWithStr:@"Provider not initialized!" code:-1];
return nil;
}

NSString* errorStr = nil;
NSDictionary<NSString*, NSString*>* ans = list_installed_apps(provider, &errorStr);
if(errorStr){
*error = [self errorWithStr:errorStr code:-17];
return nil;
} else {
return ans;
}
}

- (void)dealloc {
self->heartbeatSessionId = arc4random();
if(provider) {
tcp_provider_free(provider);
}
}

@end
Loading