StikDebug/StikJIT/idevice/JITEnableContext.m
2025-09-23 14:14:19 -04:00

374 lines
11 KiB
Objective-C

//
// 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"
#import "StikDebug-Swift.h"
JITEnableContext* sharedJITContext = nil;
@implementation JITEnableContext {
bool heartbeatRunning;
IdeviceProviderHandle* provider;
dispatch_queue_t syslogQueue;
BOOL syslogStreaming;
SyslogRelayClientHandle *syslogClient;
SyslogLineHandler syslogLineHandler;
SyslogErrorHandler syslogErrorHandler;
}
+ (instancetype)shared {
if (!sharedJITContext) {
sharedJITContext = [[JITEnableContext alloc] init];
}
return sharedJITContext;
}
- (instancetype)init {
NSFileManager* fm = [NSFileManager defaultManager];
NSURL* docPathUrl = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
NSURL* logURL = [docPathUrl URLByAppendingPathComponent:@"idevice_log.txt"];
idevice_init_logger(Info, Debug, (char*)logURL.path.UTF8String);
syslogQueue = dispatch_queue_create("com.stik.syslogrelay.queue", DISPATCH_QUEUE_SERIAL);
syslogStreaming = NO;
syslogClient = NULL;
return self;
}
- (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* fmt = [NSString stringWithCString:format encoding:NSASCIIStringEncoding];
NSString* message = [[NSString alloc] initWithFormat:fmt arguments:args];
NSLog(@"%@", message);
if ([message containsString:@"ERROR"] || [message containsString:@"Error"]) {
[[LogManagerBridge shared] addErrorLog:message];
} else if ([message containsString:@"WARNING"] || [message containsString:@"Warning"]) {
[[LogManagerBridge shared] addWarningLog:message];
} else if ([message containsString:@"DEBUG"]) {
[[LogManagerBridge shared] addDebugLog:message];
} else {
[[LogManagerBridge shared] addInfoLog: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 nil;
}
IdevicePairingFile* pairingFile = NULL;
IdeviceFfiError* err = idevice_pairing_file_read(pairingFileURL.fileSystemRepresentation, &pairingFile);
if (err) {
*error = [self errorWithStr:@"Failed to read pairing file!" code:err->code];
return nil;
}
return pairingFile;
}
- (void)startHeartbeatWithCompletionHandler:(HeartbeatCompletionHandler)completionHandler
logger:(LogFunc)logger
{
NSError* err = nil;
IdevicePairingFile* pairingFile = [self getPairingFileWithError:&err];
if (err) {
// silently swallow “pairing file not found” (-17)
if (err.code == -17) {
return;
}
// for all other errors, log and forward
if (logger) {
logger(err.localizedDescription);
}
completionHandler(err.code, err.localizedDescription);
return;
}
if(heartbeatRunning) {
return;
}
startHeartbeat(
pairingFile,
&provider,
&heartbeatRunning,
^(int result, const char *message) {
completionHandler(result,
[NSString stringWithCString:message
encoding:NSASCIIStringEncoding]);
},
[self createCLogger:logger]
);
}
- (void)ensureHeartbeat {
// wait a bit until heartbeat finish. wait at most 10s
int deadline = 50;
while((!lastHeartbeatDate || [[NSDate now] timeIntervalSinceDate:lastHeartbeatDate] > 15) && deadline) {
--deadline;
usleep(200);
}
}
- (BOOL)debugAppWithBundleID:(NSString*)bundleID logger:(LogFunc)logger jsCallback:(DebugAppCallback)jsCallback {
if (!provider) {
if (logger) {
logger(@"Provider not initialized!");
}
NSLog(@"Provider not initialized!");
return NO;
}
[self ensureHeartbeat];
return debug_app(provider,
[bundleID UTF8String],
[self createCLogger:logger], jsCallback) == 0;
}
- (BOOL)debugAppWithPID:(int)pid logger:(LogFunc)logger jsCallback:(DebugAppCallback)jsCallback {
if (!provider) {
if (logger) {
logger(@"Provider not initialized!");
}
NSLog(@"Provider not initialized!");
return NO;
}
[self ensureHeartbeat];
return debug_app_pid(provider,
pid,
[self createCLogger:logger], jsCallback) == 0;
}
- (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*>* apps = list_installed_apps(provider, &errorStr);
if (errorStr) {
*error = [self errorWithStr:errorStr code:-17];
return nil;
}
return apps;
}
- (NSDictionary<NSString*, NSString*>*)getAllAppsWithError:(NSError**)error {
if (!provider) {
NSLog(@"Provider not initialized!");
*error = [self errorWithStr:@"Provider not initialized!" code:-1];
return nil;
}
NSString* errorStr = nil;
NSDictionary<NSString*, NSString*>* apps = list_all_apps(provider, &errorStr);
if (errorStr) {
*error = [self errorWithStr:errorStr code:-17];
return nil;
}
return apps;
}
- (NSDictionary<NSString*, NSString*>*)getHiddenSystemAppsWithError:(NSError**)error {
if (!provider) {
NSLog(@"Provider not initialized!");
*error = [self errorWithStr:@"Provider not initialized!" code:-1];
return nil;
}
NSString* errorStr = nil;
NSDictionary<NSString*, NSString*>* apps = list_hidden_system_apps(provider, &errorStr);
if (errorStr) {
*error = [self errorWithStr:errorStr code:-17];
return nil;
}
return apps;
}
- (UIImage*)getAppIconWithBundleId:(NSString*)bundleId error:(NSError**)error {
if (!provider) {
NSLog(@"Provider not initialized!");
*error = [self errorWithStr:@"Provider not initialized!" code:-1];
return nil;
}
NSString* errorStr = nil;
UIImage* icon = getAppIcon(provider, bundleId, &errorStr);
if (errorStr) {
*error = [self errorWithStr:errorStr code:-17];
return nil;
}
return icon;
}
- (BOOL)launchAppWithoutDebug:(NSString*)bundleID logger:(LogFunc)logger {
if (!provider) {
if (logger) {
logger(@"Provider not initialized!");
}
NSLog(@"Provider not initialized!");
return NO;
}
[self ensureHeartbeat];
int result = launch_app_via_proxy(provider,
[bundleID UTF8String],
[self createCLogger:logger]);
return result == 0;
}
- (void)startSyslogRelayWithHandler:(SyslogLineHandler)lineHandler
onError:(SyslogErrorHandler)errorHandler
{
if (!provider) {
if (errorHandler) {
errorHandler([self errorWithStr:@"Provider not initialized!" code:-1]);
}
return;
}
if (!lineHandler || syslogStreaming) {
return;
}
syslogStreaming = YES;
syslogLineHandler = [lineHandler copy];
syslogErrorHandler = [errorHandler copy];
__weak typeof(self) weakSelf = self;
dispatch_async(syslogQueue, ^{
__strong typeof(self) strongSelf = weakSelf;
if (!strongSelf) { return; }
[strongSelf ensureHeartbeat];
SyslogRelayClientHandle *client = NULL;
IdeviceFfiError *err = syslog_relay_connect_tcp(strongSelf->provider, &client);
if (err != NULL) {
NSString *message = err->message ? [NSString stringWithCString:err->message encoding:NSASCIIStringEncoding] : @"Failed to connect to syslog relay";
NSError *nsError = [strongSelf errorWithStr:message code:err->code];
idevice_error_free(err);
[strongSelf handleSyslogFailure:nsError];
return;
}
strongSelf->syslogClient = client;
while (strongSelf && strongSelf->syslogStreaming) {
char *message = NULL;
IdeviceFfiError *nextErr = syslog_relay_next(client, &message);
if (nextErr != NULL) {
NSString *errMsg = nextErr->message ? [NSString stringWithCString:nextErr->message encoding:NSASCIIStringEncoding] : @"Syslog relay read failed";
NSError *nsError = [strongSelf errorWithStr:errMsg code:nextErr->code];
idevice_error_free(nextErr);
if (message) { idevice_string_free(message); }
[strongSelf handleSyslogFailure:nsError];
client = NULL;
break;
}
if (!message) {
continue;
}
NSString *line = [NSString stringWithCString:message encoding:NSUTF8StringEncoding];
idevice_string_free(message);
if (!line || !strongSelf->syslogLineHandler) {
continue;
}
SyslogLineHandler handlerCopy = strongSelf->syslogLineHandler;
if (handlerCopy) {
dispatch_async(dispatch_get_main_queue(), ^{
handlerCopy(line);
});
}
}
if (client) {
syslog_relay_client_free(client);
}
strongSelf->syslogClient = NULL;
strongSelf->syslogStreaming = NO;
strongSelf->syslogLineHandler = nil;
strongSelf->syslogErrorHandler = nil;
});
}
- (void)stopSyslogRelay {
if (!syslogStreaming) {
return;
}
syslogStreaming = NO;
syslogLineHandler = nil;
syslogErrorHandler = nil;
dispatch_async(syslogQueue, ^{
if (self->syslogClient) {
syslog_relay_client_free(self->syslogClient);
self->syslogClient = NULL;
}
});
}
- (void)handleSyslogFailure:(NSError *)error {
syslogStreaming = NO;
if (syslogClient) {
syslog_relay_client_free(syslogClient);
syslogClient = NULL;
}
SyslogErrorHandler errorCopy = syslogErrorHandler;
syslogLineHandler = nil;
syslogErrorHandler = nil;
if (errorCopy) {
dispatch_async(dispatch_get_main_queue(), ^{
errorCopy(error);
});
}
}
- (void)dealloc {
[self stopSyslogRelay];
if (provider) {
idevice_provider_free(provider);
}
}
@end