diff --git a/README.md b/README.md index 979ac35c..d5889683 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

StikJIT

-

An on-device JIT enabler for iOS versions 17.4+ (17.4-18.5b3 (latest)), excluding iOS 18.4 beta 1 (22E5200), powered by idevice

+

An on-device JIT enabler for iOS versions 17.4+ (17.4-18.5rc1 (latest)), excluding iOS 18.4 beta 1 (22E5200), powered by idevice

diff --git a/StikJIT/JSSupport/IDeviceJSBridge.m b/StikJIT/JSSupport/IDeviceJSBridge.m new file mode 100644 index 00000000..30b3c921 --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridge.m @@ -0,0 +1,178 @@ +// +// IDeviceJSBridge.m +// StikJIT +// +// Created by s s on 2025/4/24. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceHandle + +@end + + +NSDictionary *dictionaryFromPlistData(NSData *plistData, NSError **error) { + if (!plistData) { + if (error) { + *error = [NSError errorWithDomain:@"PlistConversionErrorDomain" + code:1001 + userInfo:@{NSLocalizedDescriptionKey: @"Input plist data is nil."}]; + } + return nil; + } + + NSPropertyListFormat format; + NSDictionary *result = [NSPropertyListSerialization propertyListWithData:plistData + options:NSPropertyListImmutable + format:&format + error:error]; + if (![result isKindOfClass:[NSDictionary class]]) { + if (error && !*error) { + *error = [NSError errorWithDomain:@"PlistConversionErrorDomain" + code:1002 + userInfo:@{NSLocalizedDescriptionKey: @"Plist is not a dictionary."}]; + } + return nil; + } + + return result; +} + +NSData *plistDataFromDictionary(NSDictionary *dictionary, NSError **error) { + if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]]) { + if (error) { + *error = [NSError errorWithDomain:@"PlistSerializationErrorDomain" + code:2001 + userInfo:@{NSLocalizedDescriptionKey: @"Input is not a valid dictionary."}]; + } + return nil; + } + + NSData *data = [NSPropertyListSerialization dataWithPropertyList:dictionary + format:NSPropertyListXMLFormat_v1_0 + options:0 + error:error]; + return data; +} + +const char** cstrArrFromNSArray(NSArray* arr, int* validCount) { + const char** ans = 0; + *validCount = 0; + if(![arr isKindOfClass:NSArray.class]) { + return 0; + } else { + ans = malloc([arr count] * sizeof(void*)); + for (id str in arr) { + if([str isKindOfClass:NSString.class]) { + ans[*validCount] = [str UTF8String]; + (*validCount)++; + } + } + } + return ans; +} + +@implementation IDeviceJSBridge { + int maxHandleId; + int maxDataId; +} + +- (instancetype)init { + maxHandleId = 0; + maxDataId = 0; + handles = [[NSMutableDictionary alloc] init]; + dataPool = [[NSMutableDictionary alloc] init]; + + return self; +} + +- (int)registerIdeviceHandle:(void*)handle freeFunc:(void*)freeFunc { + maxHandleId++; + int ans = maxHandleId; + IDeviceHandle* handleObj = [IDeviceHandle alloc]; + handleObj.handle = handle; + handleObj.freeFunc = freeFunc; + handles[@(maxHandleId)] = handleObj; + return ans; +} + +- (BOOL)freeIdeviceHandle:(int)handleId { + if(handles[@(handleId)]) { + IDeviceHandle* handleObj = handles[@(handleId)]; + void (*freeFunc)(void*) = handleObj.freeFunc; + freeFunc(handleObj.handle); + + + [handles removeObjectForKey:@(handleId)]; + return true; + } else { + return false; + } +} + + +- (bool)freeNSData:(int)handleId { + if(dataPool[@(handleId)]) { + [dataPool removeObjectForKey:@(handleId)]; + return true; + } else { + return false; + } +} + +- (int)registerNSData:(NSData*)data { + maxHandleId++; + int ans = maxHandleId; + dataPool[@(maxHandleId)] = data; + return ans; +} + +- (void)cleanUp { + NSArray *sortedKeys = [[handles allKeys] sortedArrayUsingSelector:@selector(compare:)]; + for(NSNumber* a in sortedKeys) { + [self freeIdeviceHandle:[a intValue]]; + } + for(NSNumber* a in dataPool) { + [self freeNSData:[a intValue]]; + } +} + +- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message replyHandler:(nonnull WK_SWIFT_UI_ACTOR void (^)(id _Nullable, NSString * _Nullable))replyHandler { + webView = [message webView]; + NSDictionary* body = [message body]; + if(![body isKindOfClass:NSDictionary.class]) { + replyHandler(nil, @"Input is not a dictionary"); + return; + } + + NSString* handlerSelectorStr = [NSString stringWithFormat:@"%@WithBody:replyHandler:", body[@"command"]]; + if(![IDeviceJSBridge instanceMethodForSelector:NSSelectorFromString(handlerSelectorStr)]) { + replyHandler(nil, @"Invalid idevice function!"); + return; + } + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSInvocation* invocation = [NSInvocation invocationWithTarget:self selector:NSSelectorFromString(handlerSelectorStr) retainArguments:YES, body, replyHandler]; + [invocation invoke]; + }); + + +} + +- (void)idevice_freeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int handleId = [body[@"handle"] intValue]; + if(!handles[@(handleId)] || handles[@(handleId)].freeFunc != adapter_free) { + replyHandler(nil, @"Invalid handle"); + return; + } + + [self freeIdeviceHandle:handleId]; + + replyHandler(@(YES), nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeAFC.m b/StikJIT/JSSupport/IDeviceJSBridgeAFC.m new file mode 100644 index 00000000..ce2eaacf --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeAFC.m @@ -0,0 +1,365 @@ +// +// IDeviceJSBridgeAFC.m +// StikJIT +// +// Created by s s on 2025/4/25. +// + +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (AFC) + +- (void)afc_client_connect_tcpWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + TcpProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; + + AfcClientHandle* client = NULL; + IdeviceErrorCode err = afc_client_connect_tcp(provider, &client); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int clientHandleId = [self registerIdeviceHandle:client freeFunc:(void*)afc_client_free]; + replyHandler([NSNumber numberWithInt:clientHandleId], nil); +} + +- (void)afc_list_directoryWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + NSString* path = body[@"path"]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != afc_client_free) { + replyHandler(nil, @"Invalid afc client handle"); + return; + } + if (![path isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid path"); + return; + } + + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AfcClientHandle* client = clientHandleObj.handle; + + char** entries = NULL; + size_t count = 0; + IdeviceErrorCode err = afc_list_directory(client, [path UTF8String], &entries, &count); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + NSMutableArray* result = [NSMutableArray arrayWithCapacity:count]; + for (size_t i = 0; i < count; i++) { + [result addObject:[NSString stringWithUTF8String:entries[i]]]; + free(entries[i]); + } + free(entries); + + replyHandler(result, nil); +} + +- (void)afc_make_directoryWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + NSString* path = body[@"path"]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != afc_client_free) { + replyHandler(nil, @"Invalid afc client handle"); + return; + } + if (![path isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid path"); + return; + } + + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AfcClientHandle* client = clientHandleObj.handle; + + IdeviceErrorCode err = afc_make_directory(client, [path UTF8String]); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +- (void)afc_remove_pathWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + NSString* path = body[@"path"]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != afc_client_free) { + replyHandler(nil, @"Invalid afc client handle"); + return; + } + if (![path isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid path"); + return; + } + + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AfcClientHandle* client = clientHandleObj.handle; + + IdeviceErrorCode err = afc_remove_path(client, [path UTF8String]); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +- (void)afc_remove_path_and_contentsWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + NSString* path = body[@"path"]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != afc_client_free) { + replyHandler(nil, @"Invalid afc client handle"); + return; + } + if (![path isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid path"); + return; + } + + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AfcClientHandle* client = clientHandleObj.handle; + + IdeviceErrorCode err = afc_remove_path_and_contents(client, [path UTF8String]); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +- (void)afc_rename_pathWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + NSString* source = body[@"source"]; + NSString* target = body[@"target"]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != afc_client_free) { + replyHandler(nil, @"Invalid afc client handle"); + return; + } + if (![source isKindOfClass:NSString.class] || ![target isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid source or target path"); + return; + } + + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AfcClientHandle* client = clientHandleObj.handle; + + IdeviceErrorCode err = afc_rename_path(client, [source UTF8String], [target UTF8String]); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +- (void)afc_get_file_infoWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + NSString* path = body[@"path"]; + + if (!path || ![path isKindOfClass:[NSString class]]) { + replyHandler(nil, @"Missing or invalid path"); + return; + } + + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != afc_client_free) { + replyHandler(nil, @"Invalid afc client handle"); + return; + } + + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AfcClientHandle* client = clientHandleObj.handle; + + struct AfcFileInfo info; + enum IdeviceErrorCode result = afc_get_file_info(client, [path UTF8String], &info); + + if (result != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", result]); + return; + } + + NSDictionary* ans = @{ + @"size": @(info.size), + @"blocks": @(info.blocks), + @"creation": @(info.creation), + @"modified": @(info.modified), + @"st_nlink": @(info.st_nlink), + @"st_ifmt": @(info.st_ifmt), + @"st_link_target": @(info.st_link_target) + }; + afc_file_info_free(&info); + + replyHandler(ans, nil); +} + +- (void)afc_get_device_infoWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != afc_client_free) { + replyHandler(nil, @"Invalid afc client handle"); + return; + } + + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AfcClientHandle* client = clientHandleObj.handle; + + struct AfcDeviceInfo info; + enum IdeviceErrorCode result = afc_get_device_info(client, &info); + + if (result != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", result]); + return; + } + + NSDictionary* ans = @{ + @"model": @(info.model), + @"total_bytes": @(info.total_bytes), + @"free_bytes": @(info.free_bytes), + @"block_size": @(info.block_size), + }; + afc_device_info_free(&info); + + replyHandler(ans, nil); +} + +- (void)afc_file_openWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int clientId = [body[@"handle"] intValue]; + NSString *path = body[@"path"]; + NSNumber *modeNumber = body[@"mode"]; + + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != afc_client_free) { + replyHandler(nil, @"Invalid afc client handle"); + return; + } + + if (![path isKindOfClass:NSString.class] || ![modeNumber isKindOfClass:NSNumber.class]) { + replyHandler(nil, @"Invalid path or mode"); + return; + } + + IDeviceHandle *clientHandleObj = handles[@(clientId)]; + AfcClientHandle *client = clientHandleObj.handle; + + AfcFileHandle *fileHandle = NULL; + IdeviceErrorCode err = afc_file_open(client, [path UTF8String], (AfcFopenMode)[modeNumber intValue], &fileHandle); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:fileHandle freeFunc:(void *)afc_file_close]; + replyHandler(@(handleId), nil); +} + +- (void)afc_file_closeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int handleId = [body[@"handle"] intValue]; + if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != afc_file_close) { + replyHandler(nil, @"Invalid afc file handle"); + return; + } + + IDeviceHandle *fileHandleObj = handles[@(handleId)]; + AfcFileHandle *fileHandle = fileHandleObj.handle; + + IdeviceErrorCode err = afc_file_close(fileHandle); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + [handles removeObjectForKey:@(handleId)]; + replyHandler(@YES, nil); +} + +- (void)afc_file_readWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int handleId = [body[@"handle"] intValue]; + if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != afc_file_close) { + replyHandler(nil, @"Invalid afc file handle"); + return; + } + + IDeviceHandle *fileHandleObj = handles[@(handleId)]; + AfcFileHandle *fileHandle = fileHandleObj.handle; + + unsigned char* file_data = 0; + size_t len = 0; + IdeviceErrorCode err = afc_file_read(fileHandle, &file_data, &len); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + NSData* data = [NSData dataWithBytes:file_data length:len]; + free(file_data); + int nsdataHandleId = [self registerNSData:data]; + replyHandler(@(nsdataHandleId), nil); +} + +- (void)afc_file_writeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int handleId = [body[@"handle"] intValue]; + if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != afc_file_close) { + replyHandler(nil, @"Invalid afc file handle"); + return; + } + + IDeviceHandle *fileHandleObj = handles[@(handleId)]; + AfcFileHandle *fileHandle = fileHandleObj.handle; + + int nsdataHandleId = [body[@"data_handle"] intValue]; + if (!dataPool[@(nsdataHandleId)]) { + replyHandler(nil, @"Invalid NSData handle"); + return; + } + NSData* data = dataPool[@(nsdataHandleId)]; + + IdeviceErrorCode err = afc_file_write(fileHandle, [data bytes], [data length]); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@(YES), nil); +} + +- (void)afc_make_linkWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int clientId = [body[@"handle"] intValue]; + NSString *target = body[@"target"]; + NSString *source = body[@"source"]; + NSNumber *linkTypeNum = body[@"link_type"]; + + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != afc_client_free) { + replyHandler(nil, @"Invalid afc client handle"); + return; + } + + if (![target isKindOfClass:NSString.class] || ![source isKindOfClass:NSString.class] || ![linkTypeNum isKindOfClass:NSNumber.class]) { + replyHandler(nil, @"Invalid target/source/link_type"); + return; + } + + IDeviceHandle *clientHandleObj = handles[@(clientId)]; + AfcClientHandle *client = clientHandleObj.handle; + + IdeviceErrorCode err = afc_make_link(client, + [target UTF8String], + [source UTF8String], + (AfcLinkType)[linkTypeNum intValue]); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeAMFI.m b/StikJIT/JSSupport/IDeviceJSBridgeAMFI.m new file mode 100644 index 00000000..947e311e --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeAMFI.m @@ -0,0 +1,79 @@ +// +// IDeviceJSBridgeAMFI.m +// StikJIT +// +// Created by s s on 2025/4/26. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (AMFI) + +- (void)amfi_connect_tcpWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + TcpProviderHandle *provider = [JITEnableContext.shared getTcpProviderHandle]; + + AmfiClientHandle *client = NULL; + IdeviceErrorCode err = amfi_connect_tcp(provider, &client); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:client freeFunc:(void *)amfi_client_free]; + replyHandler(@(handleId), nil); +} + +- (void)amfi_reveal_developer_mode_option_in_uiWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int clientId = [body[@"handle"] intValue]; + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != amfi_client_free) { + replyHandler(nil, @"Invalid AmfiClient handle"); + return; + } + + AmfiClientHandle *client = handles[@(clientId)].handle; + IdeviceErrorCode err = amfi_reveal_developer_mode_option_in_ui(client); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +- (void)amfi_enable_developer_modeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int clientId = [body[@"handle"] intValue]; + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != amfi_client_free) { + replyHandler(nil, @"Invalid AmfiClient handle"); + return; + } + + AmfiClientHandle *client = handles[@(clientId)].handle; + IdeviceErrorCode err = amfi_enable_developer_mode(client); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +- (void)amfi_accept_developer_modeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int clientId = [body[@"handle"] intValue]; + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != amfi_client_free) { + replyHandler(nil, @"Invalid AmfiClient handle"); + return; + } + + AmfiClientHandle *client = handles[@(clientId)].handle; + IdeviceErrorCode err = amfi_accept_developer_mode(client); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeAdapter.m b/StikJIT/JSSupport/IDeviceJSBridgeAdapter.m new file mode 100644 index 00000000..974c1902 --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeAdapter.m @@ -0,0 +1,39 @@ +// +// IDeviceJSBridgeAdapter.m +// StikJIT +// +// Created by s s on 2025/4/25. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (Adapter) + +- (void)adapter_connectWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"adapter"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != adapter_free) { + replyHandler(nil, @"Invalid adapter handle"); + return; + } + + int port = [body[@"port"] intValue]; + if(port < 0 || port > 65536 ) { + replyHandler(nil, @"Invalid port"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AdapterHandle* handle = clientHandleObj.handle; + + IdeviceErrorCode err = adapter_connect(handle, port); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@(YES), nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeCoreDevice.m b/StikJIT/JSSupport/IDeviceJSBridgeCoreDevice.m new file mode 100644 index 00000000..37ab8e5b --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeCoreDevice.m @@ -0,0 +1,69 @@ +// +// IDeviceJSBridgeCoreDevice.m +// StikJIT +// +// Created by s s on 2025/4/25. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (CoreDevice) + +- (void)core_device_proxy_connect_tcpWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + TcpProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; + + CoreDeviceProxyHandle *core_device = NULL; + IdeviceErrorCode err = core_device_proxy_connect_tcp(provider, &core_device); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + int handleId = [self registerIdeviceHandle:core_device freeFunc:(void*)core_device_proxy_free]; + replyHandler(@(handleId), nil); +} + +- (void)core_device_proxy_get_server_rsd_portWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != core_device_proxy_free) { + replyHandler(nil, @"Invalid core device proxy handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + CoreDeviceProxyHandle* core_device = clientHandleObj.handle; + + // Get server RSD port + uint16_t rsd_port; + IdeviceErrorCode err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + replyHandler(@(rsd_port), nil); +} + +- (void)core_device_proxy_create_tcp_adapterWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != core_device_proxy_free) { + replyHandler(nil, @"Invalid core device proxy handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + CoreDeviceProxyHandle* core_device = clientHandleObj.handle; + + AdapterHandle *adapter = NULL; + IdeviceErrorCode err = core_device_proxy_create_tcp_adapter(core_device, &adapter); + [handles removeObjectForKey:@(clientId)]; + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:adapter freeFunc:(void*)adapter_free]; + replyHandler(@(handleId), nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeDebugProxy.m b/StikJIT/JSSupport/IDeviceJSBridgeDebugProxy.m new file mode 100644 index 00000000..7e2d2dd2 --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeDebugProxy.m @@ -0,0 +1,84 @@ +// +// IDeviceJSBridgeDebugProxy.m +// StikJIT +// +// Created by s s on 2025/4/25. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (DebugProxy) + +- (void)debug_proxy_adapter_newWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"adapter"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != adapter_free) { + replyHandler(nil, @"Invalid adapter handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AdapterHandle* adapter = clientHandleObj.handle; + + DebugProxyAdapterHandle *debug_proxy = NULL; + IdeviceErrorCode err = debug_proxy_adapter_new(adapter, &debug_proxy); + [handles removeObjectForKey:@(clientId)]; + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:debug_proxy freeFunc:(void*)debug_proxy_free]; + replyHandler(@(handleId), nil); +} + +- (void)debug_proxy_send_commandWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != debug_proxy_free) { + replyHandler(nil, @"Invalid debug proxy handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + DebugProxyAdapterHandle* debug_proxy = clientHandleObj.handle; + + NSObject* debugCommandObj = body[@"debug_command"]; + DebugserverCommandHandle* command = 0; + if([debugCommandObj isKindOfClass:NSString.class]) { + command = debugserver_command_new([(NSString*)debugCommandObj UTF8String], NULL, 0); + } else if ([debugCommandObj isKindOfClass:NSDictionary.class]) { + NSDictionary* commandBody = (NSDictionary*)debugCommandObj; + + NSString* name = commandBody[@"name"]; + if(![name isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid command name"); + return; + } + + NSArray* args = commandBody[@"args"]; + int argsCount = 0; + const char** argsCharArr = cstrArrFromNSArray(args, &argsCount); + + command = debugserver_command_new([name UTF8String], argsCharArr, argsCount); + } else { + replyHandler(nil, @"Invalid command"); + return; + } + + char* attach_response = 0; + IdeviceErrorCode err = debug_proxy_send_command(debug_proxy, command, &attach_response); + debugserver_command_free(command); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + NSString* commandResponse = nil; + if(attach_response) { + commandResponse = @(attach_response); + } + idevice_string_free(attach_response); + replyHandler(commandResponse, nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeInstallationProxy.m b/StikJIT/JSSupport/IDeviceJSBridgeInstallationProxy.m new file mode 100644 index 00000000..0c87715b --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeInstallationProxy.m @@ -0,0 +1,302 @@ +// +// IDeviceJSBridgeInstallationProxy.m +// StikJIT +// +// Created by s s on 2025/4/25. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +struct InstallationProxyCallbackContext { + WKWebView* webView; + int jsCallbackId; +}; + +void installationProxyCallback(uint64_t progress, struct InstallationProxyCallbackContext* context) { + WKWebView* webview = context->webView; + NSString* callbackJS = [NSString stringWithFormat:@"handle_installation_proxy_js_callback(%d, %llu)", context->jsCallbackId, progress]; + dispatch_async(dispatch_get_main_queue(), ^{ + [webview evaluateJavaScript:callbackJS completionHandler:nil]; + }); +} + +@implementation IDeviceJSBridge (InstallationProxy) + +- (void)installation_proxy_connect_tcpWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + TcpProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; + + InstallationProxyClientHandle *client = NULL; + IdeviceErrorCode err = installation_proxy_connect_tcp(provider, &client); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + int handleId = [self registerIdeviceHandle:client freeFunc:(void*)installation_proxy_client_free]; + replyHandler(@(handleId), nil); +} + +- (void)installation_proxy_get_appsWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"client"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != installation_proxy_client_free) { + replyHandler(nil, @"Invalid client handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + InstallationProxyClientHandle* client = clientHandleObj.handle; + NSString* applicationType = body[@"application_type"]; + if(![applicationType isKindOfClass:NSString.class]) { + applicationType = nil; + } + + NSArray* bundleIdentifiers = body[@"bundle_identifiers"]; + + int bundleIdentifiersCount; + const char** bundleIdentifiersCharArr = cstrArrFromNSArray(bundleIdentifiers, &bundleIdentifiersCount); + void *apps = NULL; + size_t apps_len = 0; + IdeviceErrorCode err = installation_proxy_get_apps(client, [applicationType UTF8String], bundleIdentifiersCharArr, bundleIdentifiersCount, &apps, &apps_len); + if(bundleIdentifiersCharArr){ + free(bundleIdentifiersCharArr); + } + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + plist_t *app_list = (plist_t *)apps; + NSMutableArray* ans = [[NSMutableArray alloc] init]; + for(int i = 0; i < apps_len; ++i) { + char* buf = 0; + uint32_t plistlen = 0; + plist_to_bin(app_list[i], &buf, &plistlen); + NSError* err2 = 0; + NSDictionary* appDict = dictionaryFromPlistData([NSData dataWithBytes:buf length:plistlen], &err2); + plist_mem_free(buf); + if(err2) { + replyHandler(nil, @"failed to parse plist data"); + return; + } + [ans addObject:appDict]; + } + replyHandler(ans, nil); + + +} + +- (void)installation_proxy_installWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + + int clientId = [body[@"client"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != installation_proxy_client_free) { + replyHandler(nil, @"Invalid client handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + InstallationProxyClientHandle* client = clientHandleObj.handle; + + NSString* packagePath = body[@"package_path"]; + if(![packagePath isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid package path"); + return; + } + + NSDictionary* optionsDict = body[@"options"]; + plist_t optionsPlist = 0; + if([optionsDict isKindOfClass:NSDictionary.class]) { + NSError* error = 0; + NSData* optionsNSData = plistDataFromDictionary(optionsDict, &error); + if(error) { + replyHandler(nil, [NSString stringWithFormat:@"failed to parse options %@", error.localizedDescription]); + return; + } + plist_from_memory((void*)[optionsNSData bytes], (uint32_t)[optionsNSData length], &optionsPlist); + } + + int callbackId = [body[@"callback_id"] intValue]; + IdeviceErrorCode err = 0; + if(callbackId == -1) { + err = installation_proxy_install(client, [packagePath UTF8String], optionsPlist); + } else { + struct InstallationProxyCallbackContext context; + context.webView = webView; + context.jsCallbackId = callbackId; + + err = installation_proxy_install_with_callback(client, [packagePath UTF8String], optionsPlist, (void (*)(uint64_t, void *))installationProxyCallback, &context); + + } + + if(optionsPlist) { + plist_free(optionsPlist); + } + + if(err) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + replyHandler(@YES, nil); +} + +- (void)installation_proxy_upgradeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + + int clientId = [body[@"client"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != installation_proxy_client_free) { + replyHandler(nil, @"Invalid client handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + InstallationProxyClientHandle* client = clientHandleObj.handle; + + NSString* packagePath = body[@"package_path"]; + if(![packagePath isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid package path"); + return; + } + + NSDictionary* optionsDict = body[@"options"]; + plist_t optionsPlist = 0; + if([optionsDict isKindOfClass:NSDictionary.class]) { + NSError* error = 0; + NSData* optionsNSData = plistDataFromDictionary(optionsDict, &error); + if(error) { + replyHandler(nil, [NSString stringWithFormat:@"failed to parse options %@", error.localizedDescription]); + return; + } + plist_from_memory((void*)[optionsNSData bytes], (uint32_t)[optionsNSData length], &optionsPlist); + } + + int callbackId = [body[@"callback_id"] intValue]; + IdeviceErrorCode err = 0; + if(callbackId == -1) { + err = installation_proxy_upgrade(client, [packagePath UTF8String], optionsPlist); + } else { + struct InstallationProxyCallbackContext context; + context.webView = webView; + context.jsCallbackId = callbackId; + + err = installation_proxy_upgrade_with_callback(client, [packagePath UTF8String], optionsPlist, (void (*)(uint64_t, void *))installationProxyCallback, &context); + + } + + if(optionsPlist) { + plist_free(optionsPlist); + } + + if(err) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + replyHandler(@YES, nil); +} + +- (void)installation_proxy_uninstallWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + + int clientId = [body[@"client"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != installation_proxy_client_free) { + replyHandler(nil, @"Invalid client handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + InstallationProxyClientHandle* client = clientHandleObj.handle; + + NSString* bundleId = body[@"bundle_id"]; + if(![bundleId isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid bundle id"); + return; + } + + NSDictionary* optionsDict = body[@"options"]; + plist_t optionsPlist = 0; + if([optionsDict isKindOfClass:NSDictionary.class]) { + NSError* error = 0; + NSData* optionsNSData = plistDataFromDictionary(optionsDict, &error); + if(error) { + replyHandler(nil, [NSString stringWithFormat:@"failed to parse options %@", error.localizedDescription]); + return; + } + plist_from_memory((void*)[optionsNSData bytes], (uint32_t)[optionsNSData length], &optionsPlist); + } + + int callbackId = [body[@"callback_id"] intValue]; + IdeviceErrorCode err = 0; + if(callbackId == -1) { + err = installation_proxy_uninstall(client, [bundleId UTF8String], optionsPlist); + } else { + struct InstallationProxyCallbackContext context; + context.webView = webView; + context.jsCallbackId = callbackId; + + err = installation_proxy_uninstall_with_callback(client, [bundleId UTF8String], optionsPlist, (void (*)(uint64_t, void *))installationProxyCallback, &context); + + } + + if(optionsPlist) { + plist_free(optionsPlist); + } + + if(err) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + replyHandler(@YES, nil); +} + +- (void)installation_proxy_browseWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"client"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != installation_proxy_client_free) { + replyHandler(nil, @"Invalid client handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + InstallationProxyClientHandle* client = clientHandleObj.handle; + + NSDictionary* optionsDict = body[@"options"]; + plist_t optionsPlist = 0; + if([optionsDict isKindOfClass:NSDictionary.class]) { + NSError* error = 0; + NSData* optionsNSData = plistDataFromDictionary(optionsDict, &error); + if(error) { + replyHandler(nil, [NSString stringWithFormat:@"failed to parse options %@", error.localizedDescription]); + return; + } + plist_from_memory((void*)[optionsNSData bytes], (uint32_t)[optionsNSData length], &optionsPlist); + } + + void *apps = NULL; + size_t apps_len = 0; + IdeviceErrorCode err = installation_proxy_browse(client, optionsPlist, &apps, &apps_len); + + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + plist_t *app_list = (plist_t *)apps; + NSMutableArray* ans = [[NSMutableArray alloc] init]; + for(int i = 0; i < apps_len; ++i) { + char* buf = 0; + uint32_t plistlen = 0; + plist_to_bin(app_list[i], &buf, &plistlen); + NSError* err2 = 0; + NSDictionary* appDict = dictionaryFromPlistData([NSData dataWithBytes:buf length:plistlen], &err2); + plist_mem_free(buf); + if(err2) { + replyHandler(nil, @"failed to parse plist data"); + return; + } + [ans addObject:appDict]; + if([ans count] >= 100) { + break; + } + } + replyHandler(ans, nil); + + +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeLocationSimulation.m b/StikJIT/JSSupport/IDeviceJSBridgeLocationSimulation.m new file mode 100644 index 00000000..af2ef49d --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeLocationSimulation.m @@ -0,0 +1,81 @@ +// +// IDeviceJSBridgeLocationSimulation.m +// StikJIT +// +// Created by s s on 2025/4/26. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + + +@implementation IDeviceJSBridge (LocationSimulation) + +- (void)location_simulation_newWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int serverId = [body[@"server"] intValue]; + if (!handles[@(serverId)] || handles[@(serverId)].freeFunc != remote_server_free) { + replyHandler(nil, @"Invalid remote server handle"); + return; + } + + IDeviceHandle *serverHandleObj = handles[@(serverId)]; + RemoteServerAdapterHandle *server = serverHandleObj.handle; + + LocationSimulationAdapterHandle *simulation = NULL; + IdeviceErrorCode err = location_simulation_new(server, &simulation); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:simulation freeFunc:(void *)location_simulation_free]; + replyHandler(@(handleId), nil); +} + +- (void)location_simulation_clearWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int handleId = [body[@"handle"] intValue]; + if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != location_simulation_free) { + replyHandler(nil, @"Invalid LocationSimulationAdapterHandle"); + return; + } + + IDeviceHandle *handleObj = handles[@(handleId)]; + LocationSimulationAdapterHandle *simulation = handleObj.handle; + + IdeviceErrorCode err = location_simulation_clear(simulation); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +- (void)location_simulation_setWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int handleId = [body[@"handle"] intValue]; + if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != location_simulation_free) { + replyHandler(nil, @"Invalid LocationSimulationAdapterHandle"); + return; + } + + NSNumber *lat = body[@"latitude"]; + NSNumber *lon = body[@"longitude"]; + if (![lat isKindOfClass:NSNumber.class] || ![lon isKindOfClass:NSNumber.class]) { + replyHandler(nil, @"latitude or longitude is invalid"); + return; + } + + IDeviceHandle *handleObj = handles[@(handleId)]; + LocationSimulationAdapterHandle *simulation = handleObj.handle; + + IdeviceErrorCode err = location_simulation_set(simulation, lat.doubleValue, lon.doubleValue); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeMisagent.m b/StikJIT/JSSupport/IDeviceJSBridgeMisagent.m new file mode 100644 index 00000000..a1045ceb --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeMisagent.m @@ -0,0 +1,115 @@ +// +// IDeviceJSBridgeMisagent.m +// StikJIT +// +// Created by s s on 2025/4/26. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (Misagent) + +- (void)misagent_connect_tcpWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + TcpProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; + + MisagentClientHandle* client = NULL; + IdeviceErrorCode err = misagent_connect_tcp(provider, &client); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int clientHandleId = [self registerIdeviceHandle:client freeFunc:(void*)misagent_client_free]; + replyHandler(@(clientHandleId), nil); +} + +- (void)misagent_installWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int clientId = [body[@"handle"] intValue]; + int dataHandleId = [body[@"data_handle"] intValue]; + + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != misagent_client_free) { + replyHandler(nil, @"Invalid misagent client handle"); + return; + } + + if (!dataPool[@(dataHandleId)]) { + replyHandler(nil, @"Invalid NSData handle"); + return; + } + + IDeviceHandle *clientHandleObj = handles[@(clientId)]; + MisagentClientHandle *client = clientHandleObj.handle; + NSData *data = dataPool[@(dataHandleId)]; + + IdeviceErrorCode err = misagent_install(client, data.bytes, data.length); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +- (void)misagent_removeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int clientId = [body[@"handle"] intValue]; + NSString *profileId = body[@"profile_id"]; + + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != misagent_client_free) { + replyHandler(nil, @"Invalid misagent client handle"); + return; + } + + if (![profileId isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid profile_id"); + return; + } + + IDeviceHandle *clientHandleObj = handles[@(clientId)]; + MisagentClientHandle *client = clientHandleObj.handle; + + IdeviceErrorCode err = misagent_remove(client, [profileId UTF8String]); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@YES, nil); +} + +- (void)misagent_copy_allWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int clientId = [body[@"handle"] intValue]; + + if (!handles[@(clientId)] || handles[@(clientId)].freeFunc != misagent_client_free) { + replyHandler(nil, @"Invalid misagent client handle"); + return; + } + + IDeviceHandle *clientHandleObj = handles[@(clientId)]; + MisagentClientHandle *client = clientHandleObj.handle; + + uint8_t **profiles = NULL; + size_t *profile_lens = NULL; + size_t count = 0; + + IdeviceErrorCode err = misagent_copy_all(client, &profiles, &profile_lens, &count); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + NSMutableArray *profileHandleIds = [NSMutableArray array]; + for (size_t i = 0; i < count; i++) { + NSData *profileData = [NSData dataWithBytes:profiles[i] length:profile_lens[i]]; + free(profiles[i]); + int nsdataHandleId = [self registerNSData:profileData]; + [profileHandleIds addObject:@(nsdataHandleId)]; + } + + misagent_free_profiles(profiles, profile_lens, count); + + replyHandler(profileHandleIds, nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeNSData+LocalFile.m b/StikJIT/JSSupport/IDeviceJSBridgeNSData+LocalFile.m new file mode 100644 index 00000000..30717530 --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeNSData+LocalFile.m @@ -0,0 +1,180 @@ +// +// IDeviceJSBridgeNSData.m +// StikJIT +// +// Created by s s on 2025/4/25. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (NSData) + +- (void)nsdata_freeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int handleId = [body[@"handle"] intValue]; + if (!dataPool[@(handleId)]) { + replyHandler(nil, @"Invalid NSData handle"); + return; + } + bool ans = [self freeNSData:handleId]; + replyHandler(@(ans), nil); +} + +- (void)nsdata_readWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int handleId = [body[@"handle"] intValue]; + if (!dataPool[@(handleId)]) { + replyHandler(nil, @"Invalid NSData handle"); + return; + } + NSData* data = dataPool[@(handleId)]; + NSString* ans = [data base64EncodedStringWithOptions:0]; + replyHandler(ans, nil); +} + +- (void)nsdata_read_rangeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int handleId = [body[@"handle"] intValue]; + if (!dataPool[@(handleId)]) { + replyHandler(nil, @"Invalid NSData handle"); + return; + } + NSData* data = dataPool[@(handleId)]; + NSUInteger begin = [body[@"begin"] intValue]; + NSUInteger end = [body[@"end"] intValue]; + if(begin < 0 || end >= [data length] || begin > end) { + replyHandler(nil, @"Invalid range"); + return; + } + + [data subdataWithRange:NSMakeRange(begin, end)]; + NSString* ans = [data base64EncodedStringWithOptions:0]; + replyHandler(ans, nil); +} + +- (void)nsdata_get_sizeWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int handleId = [body[@"handle"] intValue]; + if (!dataPool[@(handleId)]) { + replyHandler(nil, @"Invalid NSData handle"); + return; + } + NSData* data = dataPool[@(handleId)]; + NSUInteger ans = [data length]; + replyHandler(@(ans), nil); +} + +- (void)nsdata_createWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + NSString* dataStr = body[@"base64Data"]; + if (![dataStr isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid base64Data"); + return; + } + + NSData* data = [[NSData alloc] initWithBase64EncodedString:dataStr options:0]; + if (!data) { + replyHandler(nil, @"Failed to decode base64Data"); + return; + } + int ans = [self registerNSData:data]; + replyHandler(@(ans), nil); +} + +@end + + +@implementation IDeviceJSBridge (LocalFile) + +- (void)local_file_openWithBody:(NSDictionary *)body replyHandler:(void (^)(id _Nullable, NSString * _Nullable))replyHandler { + NSString *relativePath = body[@"path"]; + NSString *docPath = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] + .lastObject.path; + NSString* path = [docPath stringByAppendingPathComponent:relativePath]; + NSString *mode = body[@"mode"]; + + if (![path isKindOfClass:NSString.class] || ![mode isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid file path or mode"); + return; + } + + FILE *file = fopen([path UTF8String], [mode UTF8String]); + if (!file) { + replyHandler(nil, @"Failed to open file"); + return; + } + + int handleId = [self registerIdeviceHandle:(void*)file freeFunc:(void *)fclose]; + replyHandler(@(handleId), nil); +} + +- (void)local_file_closeWithBody:(NSDictionary *)body replyHandler:(void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int fileId = [body[@"file"] intValue]; + + if (!handles[@(fileId)] || handles[@(fileId)].freeFunc != fclose) { + replyHandler(nil, @"Invalid file handle"); + return; + } + + FILE *file = handles[@(fileId)].handle; + fclose(file); + [handles removeObjectForKey:@(fileId)]; + + replyHandler(@(YES), nil); +} + +- (void)local_file_get_sizeWithBody:(NSDictionary *)body replyHandler:(void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int fileId = [body[@"file"] intValue]; + + if (!handles[@(fileId)] || handles[@(fileId)].freeFunc != fclose) { + replyHandler(nil, @"Invalid file handle"); + return; + } + + FILE *file = handles[@(fileId)].handle; + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); // Reset position + replyHandler(@(size), nil); +} + +- (void)local_file_read_chunkWithBody:(NSDictionary *)body replyHandler:(void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int fileId = [body[@"file"] intValue]; + long offset = [body[@"offset"] longValue]; + long length = [body[@"length"] longValue]; + + if (!handles[@(fileId)] || handles[@(fileId)].freeFunc != fclose) { + replyHandler(nil, @"Invalid file handle"); + return; + } + + FILE *file = handles[@(fileId)].handle; + fseek(file, offset, SEEK_SET); + void *buffer = malloc(length); + size_t read = fread(buffer, 1, length, file); + + NSData *data = [NSData dataWithBytesNoCopy:buffer length:read freeWhenDone:YES]; + int dataHandleId = [self registerNSData:data]; + replyHandler(@(dataHandleId), nil); +} + +- (void)local_file_write_chunkWithBody:(NSDictionary *)body replyHandler:(void (^)(id _Nullable, NSString * _Nullable))replyHandler { + int fileId = [body[@"file"] intValue]; + int dataId = [body[@"data"] intValue]; + long offset = [body[@"offset"] longValue]; + + if (!handles[@(fileId)] || handles[@(fileId)].freeFunc != fclose) { + replyHandler(nil, @"Invalid file handle"); + return; + } + if (!dataPool[@(dataId)]) { + replyHandler(nil, @"Invalid data handle"); + return; + } + + FILE *file = handles[@(fileId)].handle; + NSData *data = dataPool[@(dataId)]; + fseek(file, offset, SEEK_SET); + size_t written = fwrite(data.bytes, 1, data.length, file); + + replyHandler(@(written), nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeProcessControl.m b/StikJIT/JSSupport/IDeviceJSBridgeProcessControl.m new file mode 100644 index 00000000..4b405aaf --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeProcessControl.m @@ -0,0 +1,128 @@ +// +// IDeviceJSBridgeProcessControl.m +// StikJIT +// +// Created by s s on 2025/4/25. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (ProcessControl) + +- (void)process_control_newWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"server"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != remote_server_free) { + replyHandler(nil, @"Invalid remote server handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + RemoteServerAdapterHandle* remote_server = clientHandleObj.handle; + + ProcessControlAdapterHandle *process_control = NULL; + IdeviceErrorCode err = process_control_new(remote_server, &process_control); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:process_control freeFunc:(void*)process_control_free]; + replyHandler(@(handleId), nil); +} + +- (void)process_control_launch_appWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != process_control_free) { + replyHandler(nil, @"Invalid process control handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + ProcessControlAdapterHandle* process_control = clientHandleObj.handle; + + NSString* bundleId = body[@"bundle_id"]; + if(![bundleId isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid bundle id"); + return; + } + + NSArray* envVars = body[@"env_vars"]; + int envVarsCount = 0; + const char** envVarsCharArr = cstrArrFromNSArray(envVars, &envVarsCount); + + NSArray* arguments = body[@"arguments"]; + int argumentsCount = 0; + const char** argumentsCharArr = cstrArrFromNSArray(arguments, &argumentsCount); + + bool startSuspended = [body[@"start_suspended"] boolValue]; + bool killExisting = [body[@"kill_existing"] boolValue]; + + uint64_t pid = 0; + IdeviceErrorCode err = process_control_launch_app(process_control, [bundleId UTF8String], envVarsCharArr, envVarsCount, argumentsCharArr, argumentsCount, + startSuspended, killExisting, &pid); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + if(envVarsCharArr) { + free(envVarsCharArr); + } + + if(argumentsCharArr) { + free(argumentsCharArr); + } + + replyHandler(@(pid), nil); +} + +- (void)process_control_disable_memory_limitWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != process_control_free) { + replyHandler(nil, @"Invalid process control handle"); + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + ProcessControlAdapterHandle* process_control = clientHandleObj.handle; + + uint64_t pid = [body[@"pid"] unsignedLongLongValue]; + if(pid == 0) { + replyHandler(nil, @"Invalid pid"); + return; + } + + IdeviceErrorCode err = process_control_disable_memory_limit(process_control, pid); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@(YES), nil); +} + +- (void)process_control_kill_appWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != process_control_free) { + replyHandler(nil, @"Invalid process control handle"); + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + ProcessControlAdapterHandle* process_control = clientHandleObj.handle; + + uint64_t pid = [body[@"pid"] unsignedLongLongValue]; + if(pid == 0) { + replyHandler(nil, @"Invalid pid"); + return; + } + + IdeviceErrorCode err = process_control_kill_app(process_control, pid); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + replyHandler(@(YES), nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeRemoteServer.m b/StikJIT/JSSupport/IDeviceJSBridgeRemoteServer.m new file mode 100644 index 00000000..bbe77bd5 --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeRemoteServer.m @@ -0,0 +1,59 @@ +// +// IDeviceJSBridgeRemoteServer.m +// StikJIT +// +// Created by s s on 2025/4/25. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (RemoteServer) + +- (void)remote_server_adapter_newWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"adapter"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != adapter_free) { + replyHandler(nil, @"Invalid adapter handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AdapterHandle* adapter = clientHandleObj.handle; + + RemoteServerAdapterHandle *remote_server = NULL; + IdeviceErrorCode err = remote_server_adapter_new(adapter, &remote_server); + [handles removeObjectForKey:@(clientId)]; + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:remote_server freeFunc:(void*)remote_server_free]; + replyHandler(@(handleId), nil); +} + +- (void)remote_server_adapter_into_innerWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != remote_server_free) { + replyHandler(nil, @"Invalid remote server handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + RemoteServerAdapterHandle* xpc_device = clientHandleObj.handle; + + AdapterHandle *adapter = NULL; + IdeviceErrorCode err = remote_server_adapter_into_inner(xpc_device, &adapter); + [handles removeObjectForKey:@(clientId)]; + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:adapter freeFunc:(void*)adapter_free]; + replyHandler(@(handleId), nil); +} + + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeSBServices.m b/StikJIT/JSSupport/IDeviceJSBridgeSBServices.m new file mode 100644 index 00000000..35afd207 --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeSBServices.m @@ -0,0 +1,62 @@ +// +// IDeviceJSBridgeSBServices.m +// StikJIT +// +// Created by s s on 2025/4/25. +// + +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (SBServices) + +- (void)springboard_services_connect_tcpWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + TcpProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; + + SpringBoardServicesClientHandle *sb_services = NULL; + IdeviceErrorCode err = springboard_services_connect_tcp(provider, &sb_services); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + int handleId = [self registerIdeviceHandle:sb_services freeFunc:(void*)springboard_services_free]; + replyHandler(@(handleId), nil); +} + + +- (void)springboard_services_get_iconWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"client"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != springboard_services_free) { + replyHandler(nil, @"Invalid springboard services client handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + SpringBoardServicesClientHandle* client = clientHandleObj.handle; + NSString* bundleID = body[@"bundle_id"]; + if(![bundleID isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid bundle id"); + return; + } + + void *pngData = NULL; + size_t data_len = 0; + IdeviceErrorCode err = springboard_services_get_icon(client, [bundleID UTF8String], &pngData, &data_len); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + if(data_len == 0) { + replyHandler(@(-1), nil); + } + + NSData* data = [NSData dataWithBytes:pngData length:data_len]; + free(pngData); + int handleId = [self registerNSData:data]; + replyHandler(@(handleId), nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceJSBridgeXPCDevice.m b/StikJIT/JSSupport/IDeviceJSBridgeXPCDevice.m new file mode 100644 index 00000000..345db6a4 --- /dev/null +++ b/StikJIT/JSSupport/IDeviceJSBridgeXPCDevice.m @@ -0,0 +1,109 @@ +// +// IDeviceJSBridgeXPCDevice.m +// StikJIT +// +// Created by s s on 2025/4/25. +// +@import Foundation; +#import "JSSupport.h" +#import "../idevice/JITEnableContext.h" +#import "../idevice/idevice.h" + +@implementation IDeviceJSBridge (XPCDevice) + +- (void)xpc_device_newWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"adapter"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != adapter_free) { + replyHandler(nil, @"Invalid adapter handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + AdapterHandle* handle = clientHandleObj.handle; + + XPCDeviceAdapterHandle *xpc_device = NULL; + IdeviceErrorCode err = xpc_device_new(handle, &xpc_device); + [handles removeObjectForKey:@(clientId)]; + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:xpc_device freeFunc:(void*)xpc_device_free]; + replyHandler(@(handleId), nil); +} + +- (void)xpc_device_get_serviceWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != xpc_device_free) { + replyHandler(nil, @"Invalid xpc device handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + XPCDeviceAdapterHandle* xpc_device = clientHandleObj.handle; + + NSString* serviceName = body[@"service_name"]; + if(![serviceName isKindOfClass:NSString.class]) { + replyHandler(nil, @"Invalid xpc service name"); + return; + } + + XPCServiceHandle *service = NULL; + IdeviceErrorCode err = xpc_device_get_service(xpc_device, [serviceName UTF8String], &service); + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:service freeFunc:(void*)xpc_service_free]; + replyHandler(@(handleId), nil); +} + +- (void)xpc_device_get_infoWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != xpc_service_free) { + replyHandler(nil, @"Invalid xpc service handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + XPCServiceHandle* service = clientHandleObj.handle; + + NSMutableArray* features = [[NSMutableArray alloc] init]; + for(int i = 0; i < service->features_count; ++i) { + [features addObject:@(service->features[i])]; + } + + NSDictionary* ans = @{ + @"port": @(service->port), + @"entitlement": @(service->entitlement), + @"service_version": @(service->service_version), + @"features": features + }; + replyHandler(ans, nil); +} + +- (void)xpc_device_adapter_into_innerWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { + + int clientId = [body[@"handle"] intValue]; + if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != xpc_device_free) { + replyHandler(nil, @"Invalid xpc device handle"); + return; + } + IDeviceHandle* clientHandleObj = handles[@(clientId)]; + XPCDeviceAdapterHandle* xpc_device = clientHandleObj.handle; + + AdapterHandle *adapter = NULL; + IdeviceErrorCode err = xpc_device_adapter_into_inner(xpc_device, &adapter); + [handles removeObjectForKey:@(clientId)]; + if (err != IdeviceSuccess) { + replyHandler(nil, [NSString stringWithFormat:@"error code %d", err]); + return; + } + + int handleId = [self registerIdeviceHandle:adapter freeFunc:(void*)adapter_free]; + replyHandler(@(handleId), nil); +} + +@end diff --git a/StikJIT/JSSupport/IDeviceWebViewController.m b/StikJIT/JSSupport/IDeviceWebViewController.m new file mode 100644 index 00000000..0f901464 --- /dev/null +++ b/StikJIT/JSSupport/IDeviceWebViewController.m @@ -0,0 +1,53 @@ +// +// IDeviceWebViewController.m +// StikJIT +// +// Created by s s on 2025/4/24. +// +@import WebKit; +#import "../idevice/idevice.h" +#import "../idevice/JITEnableContext.h" +#import "JSSupport.h" + + +@implementation IDeviceWebViewController { + WKWebView* webview; + IDeviceJSBridge* bridge; + NSString* currentScript; +} + +- (void)loadView { + webview = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; + currentScript = @""; + WKUserContentController* contentController = webview.configuration.userContentController; + bridge = [[IDeviceJSBridge alloc] init]; + [contentController addScriptMessageHandlerWithReply:bridge contentWorld:[WKContentWorld pageWorld] name:@"ideviceCallback"]; + WKUserScript* ideviceScript = [[WKUserScript alloc] initWithSource:[NSString stringWithContentsOfURL:[NSBundle.mainBundle URLForResource:@"idevice" withExtension:@"js"]] injectionTime:(WKUserScriptInjectionTimeAtDocumentStart) forMainFrameOnly:NO]; + [contentController addUserScript:ideviceScript]; + + [webview setInspectable:YES]; + webview.UIDelegate = self; + self.view = webview; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + +} + +- (void)loadScript:(NSString*)path { + if([path isEqualToString:currentScript]) { + return; + } + currentScript = path; + [bridge cleanUp]; + if([path length] == 0) { + return; + } + + NSData* htmlData = [NSData dataWithContentsOfURL:[NSBundle.mainBundle URLForResource:path withExtension:nil]]; + [webview loadData:htmlData MIMEType:@"text/html" characterEncodingName:@"utf-8" baseURL:[[NSURL URLWithString:@"http://stikjitlocaldocument.com/"] URLByAppendingPathComponent:path]]; +} + +@end diff --git a/StikJIT/JSSupport/JSSupport.h b/StikJIT/JSSupport/JSSupport.h new file mode 100644 index 00000000..cb937f68 --- /dev/null +++ b/StikJIT/JSSupport/JSSupport.h @@ -0,0 +1,39 @@ +// +// JSSupport.h +// StikJIT +// +// Created by s s on 2025/4/24. +// +@import WebKit; + +@interface NSInvocation(MCUtilities) +-(void)invokeOnMainThreadWaitUntilDone:(BOOL)wait; ++(NSInvocation*)invocationWithTarget:(id)target + selector:(SEL)aSelector + retainArguments:(BOOL)retainArguments, ...; +@end + +@interface IDeviceHandle : NSObject +@property void* handle; +@property void* freeFunc; +@end + +@interface IDeviceJSBridge : NSObject { + NSMutableDictionary* handles; + NSMutableDictionary* dataPool; + WKWebView* webView; +} +- (void)cleanUp; +- (int)registerIdeviceHandle:(void*)handle freeFunc:(void*)freeFunc; +- (BOOL)freeIdeviceHandle:(int)handleId; +- (int)registerNSData:(NSData*)data; +- (bool)freeNSData:(int)handleId; +@end + +@interface IDeviceWebViewController : UIViewController +- (void)loadScript:(NSString*)path; +@end + +NSDictionary *dictionaryFromPlistData(NSData *plistData, NSError **error); +NSData *plistDataFromDictionary(NSDictionary *dictionary, NSError **error); +const char** cstrArrFromNSArray(NSArray* arr, int* validCount); diff --git a/StikJIT/JSSupport/NSInvocation.m b/StikJIT/JSSupport/NSInvocation.m new file mode 100644 index 00000000..981b9fa2 --- /dev/null +++ b/StikJIT/JSSupport/NSInvocation.m @@ -0,0 +1,44 @@ +// +// NSInvocation.m +// StikJIT +// Source: http://blog.jayway.com/2010/03/30/performing-any-selector-on-the-main-thread/ +// Created by s s on 2025/4/24. +// +@import Foundation; + +@implementation NSInvocation(MCUtilities) +-(void)invokeOnMainThreadWaitUntilDone:(BOOL)wait +{ + [self performSelectorOnMainThread:@selector(invoke) + withObject:nil + waitUntilDone:wait]; +} ++(NSInvocation*)invocationWithTarget:(id)target + selector:(SEL)aSelector + retainArguments:(BOOL)retainArguments, ... +{ + va_list ap; + va_start(ap, retainArguments); + char* args = (char*)ap; + NSMethodSignature* signature = [target methodSignatureForSelector:aSelector]; + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature]; + if (retainArguments) { + [invocation retainArguments]; + } + [invocation setTarget:target]; + [invocation setSelector:aSelector]; + for (int index = 2; index < [signature numberOfArguments]; index++) { + const char *type = [signature getArgumentTypeAtIndex:index]; + NSUInteger size, align; + NSGetSizeAndAlignment(type, &size, &align); + NSUInteger mod = (NSUInteger)args % align; + if (mod != 0) { + args += (align - mod); + } + [invocation setArgument:args atIndex:index]; + args += size; + } + va_end(ap); + return invocation; +} +@end diff --git a/StikJIT/JSSupport/idevice.js b/StikJIT/JSSupport/idevice.js new file mode 100644 index 00000000..ade45494 --- /dev/null +++ b/StikJIT/JSSupport/idevice.js @@ -0,0 +1,505 @@ +// +// idevice.js +// StikJIT +// +// Created by s s on 2025/4/24. +// + +async function core_device_proxy_connect_tcp() { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "core_device_proxy_connect_tcp" + }); +} + +async function core_device_proxy_get_server_rsd_port(core_device_handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "core_device_proxy_get_server_rsd_port", + "handle": core_device_handle + }); +} + +async function core_device_proxy_create_tcp_adapter(core_device_handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "core_device_proxy_create_tcp_adapter", + "handle": core_device_handle + }); +} + +async function adapter_connect(adapter, port) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "adapter_connect", + "adapter": adapter, + "port": port + }); +} + +async function xpc_device_new(adapter) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "xpc_device_new", + "adapter": adapter, + }); +} + +async function xpc_device_get_service(handle, service_name) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "xpc_device_get_service", + "handle": handle, + "service_name": service_name + }); +} + +async function xpc_device_get_info(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "xpc_device_get_info", + "handle": handle, + }); +} + +async function xpc_device_adapter_into_inner(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "xpc_device_adapter_into_inner", + "handle": handle, + }); +} + +async function remote_server_adapter_new(adapter) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "remote_server_adapter_new", + "adapter": adapter, + }); +} + +async function remote_server_adapter_into_inner(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "remote_server_adapter_into_inner", + "handle": handle, + }); +} + +async function process_control_new(server) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "process_control_new", + "server": server + }); +} + +async function process_control_launch_app(handle, bundle_id, env_vars, arguments, start_suspended, kill_existing) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "process_control_launch_app", + "handle": handle, + "bundle_id": bundle_id, + "env_vars": env_vars, + "arguments": arguments, + "start_suspended": start_suspended, + "kill_existing": kill_existing + }); +} + +async function process_control_disable_memory_limit(handle, pid) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "process_control_disable_memory_limit", + "handle": handle, + "pid": pid + }); +} + +async function process_control_kill_app(handle, pid) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "process_control_kill_app", + "handle": handle, + "pid": pid + }); +} + +async function debug_proxy_adapter_new(adapter) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "debug_proxy_adapter_new", + "adapter": adapter, + }); +} + +async function debug_proxy_send_command(handle, command) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "debug_proxy_send_command", + "handle": handle, + "debug_command": command + }); +} + +async function springboard_services_connect_tcp() { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "springboard_services_connect_tcp" + }); +} + +async function springboard_services_get_icon(client, bundle_id) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "springboard_services_get_icon", + "client": client, + "bundle_id": bundle_id + }); +} + +async function nsdata_read(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "nsdata_read", + "handle": handle, + }); +} + +async function nsdata_read_range(handle, begin, end) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "nsdata_read_range", + "handle": handle, + "begin": begin, + "end": end + }); +} + +async function afc_client_connect_tcp() { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_client_connect_tcp" + }); +} + +async function afc_list_directory(handle, path) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_list_directory", + "handle": handle, + "path": path + }); +} + +async function afc_make_directory(handle, path) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_make_directory", + "handle": handle, + "path": path + }); +} + +async function afc_remove_path(handle, path) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_remove_path", + "handle": handle, + "path": path + }); +} + +async function afc_remove_path_and_contents(handle, path) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_remove_path_and_contents", + "handle": handle, + "path": path + }); +} + +async function afc_rename_path(handle, source, target) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_rename_path", + "handle": handle, + "source": source, + "target": target + }); +} + +async function afc_get_file_info(handle, path) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_get_file_info", + "handle": handle, + "path": path + }); +} + +async function afc_get_device_info(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_get_device_info", + "handle": handle, + }); +} + +async function afc_file_open(handle, path, mode) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_file_open", + "handle": handle, + "path": path, + "mode": mode + }); +} + +async function afc_file_close(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_file_close", + "handle": handle + }); +} + +async function afc_file_read(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_file_read", + "handle": handle + }); +} + +async function afc_file_write(handle, data_handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "afc_file_write", + "handle": handle, + "data_handle": data_handle + }); +} + +// for installation_proxy_callback + +var handle_installation_proxy_callback_dict = {"max": 0} +function handle_installation_proxy_js_callback(id, progress) { + let func = handle_installation_proxy_callback_dict[id]; + if(func) { + func(progress) + } +} + +function installation_proxy_js_callback_register(callback) { + let cur = handle_installation_proxy_callback_dict['max'] + handle_installation_proxy_callback_dict['max']++ + handle_installation_proxy_callback_dict[cur] = callback + return cur; +} + +function installation_proxy_js_callback_unregister(id) { + delete handle_installation_proxy_callback_dict[id] +} + +async function installation_proxy_connect_tcp() { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "installation_proxy_connect_tcp" + }); +} + +async function installation_proxy_get_apps(client, applicationType, bundleIdentifiers) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "installation_proxy_get_apps", + "client": client, + "application_type": applicationType, + "bundle_identifiers": bundleIdentifiers + }); +} + +async function installation_proxy_browse(client, options) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "installation_proxy_browse", + "client": client, + "options": options + }); +} + +async function installation_proxy_install(client, package_path, options, callback) { + if(callback) { + let id = installation_proxy_js_callback_register(callback) + let ans = await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "installation_proxy_install", + "client": client, + "package_path": package_path, + "options": options, + "callback_id": id + }); + installation_proxy_js_callback_unregister(id) + return ans; + } else { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "installation_proxy_install", + "client": client, + "package_path": package_path, + "options": options, + "callback_id": -1 + }); + } +} + +async function installation_proxy_upgrade(client, package_path, options, callback) { + if(callback) { + let id = installation_proxy_js_callback_register(callback) + let ans = await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "installation_proxy_upgrade", + "client": client, + "package_path": package_path, + "options": options, + "callback_id": id + }); + installation_proxy_js_callback_unregister(id) + return ans; + } else { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "installation_proxy_install", + "client": client, + "package_path": package_path, + "options": options, + "callback_id": -1 + }); + } +} + +async function installation_proxy_uninstall(client, bundle_id, options, callback) { + if(callback) { + let id = installation_proxy_js_callback_register(callback) + let ans = await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "installation_proxy_uninstall", + "client": client, + "bundle_id": bundle_id, + "options": options, + "callback_id": id + }); + installation_proxy_js_callback_unregister(id) + return ans; + } else { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "installation_proxy_install", + "client": client, + "bundle_id": bundle_id, + "options": options, + "callback_id": -1 + }); + } +} + +async function amfi_connect_tcp() { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "amfi_connect_tcp" + }); +} + +async function amfi_reveal_developer_mode_option_in_ui(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "amfi_reveal_developer_mode_option_in_ui", + "handle": handle + }); +} + +async function amfi_enable_developer_mode(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "amfi_enable_developer_mode", + "handle": handle + }); +} + +async function amfi_accept_developer_mode(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "amfi_accept_developer_mode", + "handle": handle + }); +} + +async function misagent_connect_tcp() { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "misagent_connect_tcp" + }); +} + +async function misagent_install(handle, data_handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "misagent_install", + "handle": handle, + "data_handle": data_handle + }); +} + +async function misagent_remove(handle, profile_id) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "misagent_remove", + "handle": handle, + "profile_id": profile_id + }); +} + +async function misagent_copy_all(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "misagent_copy_all", + "handle": handle, + }); +} + +async function location_simulation_new(server) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "location_simulation_new", + "server": server + }); +} + +async function location_simulation_clear(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "location_simulation_clear", + "handle": handle + }); +} + +async function location_simulation_set(handle, latitude, longitude) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "location_simulation_set", + "handle": handle, + "latitude": latitude, + "longitude": longitude, + }); +} + +// data related, not idevice + +async function nsdata_get_size(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "nsdata_get_size", + "handle": handle, + }); +} + +async function nsdata_free(handle) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "nsdata_free", + "handle": handle, + }); +} + +async function nsdata_create(base64Data) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "nsdata_create", + "base64Data": base64Data, + }); +} + +async function local_file_open(path, mode) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "local_file_open", + "path": path, + "mode": mode + }); +} + +async function local_file_close(file) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "local_file_close", + "file": file, + }); +} + +async function local_file_get_size(file) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "local_file_get_size", + "file": file, + }); +} + +async function local_file_read_chunk(file, offset, length) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "local_file_read_chunk", + "file": file, + "offset": offset, + "length": length + }); +} + +async function local_file_write_chunk(file, data, offset) { + return await webkit.messageHandlers.ideviceCallback.postMessage({ + "command": "local_file_write_chunk", + "file": file, + "offset": offset, + "data": data + }); +} \ No newline at end of file diff --git a/StikJIT/JSSupport/test.html b/StikJIT/JSSupport/test.html new file mode 100644 index 00000000..0d450b35 --- /dev/null +++ b/StikJIT/JSSupport/test.html @@ -0,0 +1,100 @@ + + + +

Apps: +

+ + + + diff --git a/StikJIT/JSSupport/test2.html b/StikJIT/JSSupport/test2.html new file mode 100644 index 00000000..9fc5bd2d --- /dev/null +++ b/StikJIT/JSSupport/test2.html @@ -0,0 +1,61 @@ + + +

Please input ipa's path relative to Documents folder

+ + +
+ + + + diff --git a/StikJIT/JSSupport/test3.html b/StikJIT/JSSupport/test3.html new file mode 100644 index 00000000..d3f81dbc --- /dev/null +++ b/StikJIT/JSSupport/test3.html @@ -0,0 +1,79 @@ + +

Location Simulation

+

+ latitude + +

+

+ longitude + +

+

+ + +

+ +
+ + + + diff --git a/StikJIT/StikJIT-Bridging-Header.h b/StikJIT/StikJIT-Bridging-Header.h index 7ce5a25c..1cc9a940 100644 --- a/StikJIT/StikJIT-Bridging-Header.h +++ b/StikJIT/StikJIT-Bridging-Header.h @@ -6,3 +6,4 @@ #include "idevice/idevice.h" #include "idevice/em_proxy.h" #include "idevice/heartbeat.h" +#include "JSSupport/JSSupport.h" diff --git a/StikJIT/Utilities/mountDDI.swift b/StikJIT/Utilities/mountDDI.swift index 3cd06f9e..612712a9 100644 --- a/StikJIT/Utilities/mountDDI.swift +++ b/StikJIT/Utilities/mountDDI.swift @@ -173,7 +173,7 @@ func mountPersonalDDI(deviceIP: String = "10.7.0.1", imagePath: String, trustcac } var uniqueChipIDPlist: plist_t? - guard lockdownd_get_value(lockdownClient, "UniqueChipID".cString(using: .utf8), &uniqueChipIDPlist) == IdeviceSuccess else { + guard lockdownd_get_value(lockdownClient, "UniqueChipID".cString(using: .utf8), nil, &uniqueChipIDPlist) == IdeviceSuccess else { print("Failed to get UniqueChipID") return 8 // EC: 8 } diff --git a/StikJIT/Views/MainTabView.swift b/StikJIT/Views/MainTabView.swift index 57b98f45..02c5ddf6 100644 --- a/StikJIT/Views/MainTabView.swift +++ b/StikJIT/Views/MainTabView.swift @@ -28,6 +28,10 @@ struct MainTabView: View { .tabItem { Label("Settings", systemImage: "gearshape.fill") } + RunJSView() + .tabItem { + Label("Scripts", systemImage: "gamecontroller.fill") + } } .accentColor(accentColor) .environment(\.accentColor, accentColor) diff --git a/StikJIT/Views/RunJSView.swift b/StikJIT/Views/RunJSView.swift new file mode 100644 index 00000000..a53c1ffc --- /dev/null +++ b/StikJIT/Views/RunJSView.swift @@ -0,0 +1,65 @@ +// +// RunJSView.swift +// StikJIT +// +// Created by s s on 2025/4/24. +// + +import SwiftUI + + +struct RunJSWebView: UIViewControllerRepresentable { + @Binding var scriptPath: String + + func makeUIViewController(context: Context) -> IDeviceWebViewController { + return IDeviceWebViewController() + } + + func updateUIViewController(_ vc: IDeviceWebViewController, context: Context) { + vc.loadScript(scriptPath) + } +} + +struct RunJSView : View { + @State var selectedScript = "" + @State var webViewShow = false + + + + var body: some View { + List { + Section { + Button("Enable JIT") { + selectScript(path: "test.html") + } + Button("Install App") { + selectScript(path: "test2.html") + } + Button("Location Simulation") { + selectScript(path: "test3.html") + } + } + } + .sheet(isPresented: $webViewShow) { + NavigationView { + RunJSWebView(scriptPath:$selectedScript) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Done") { + selectedScript = "" + webViewShow = false + } + } + } + .navigationTitle(selectedScript) + .navigationBarTitleDisplayMode(.inline) + } + + } + } + + func selectScript(path: String) { + selectedScript = path + webViewShow = true + } +} diff --git a/StikJIT/idevice/JITEnableContext.h b/StikJIT/idevice/JITEnableContext.h index a3d1870d..c0f84999 100644 --- a/StikJIT/idevice/JITEnableContext.h +++ b/StikJIT/idevice/JITEnableContext.h @@ -19,4 +19,5 @@ typedef void (^LogFunc)(NSString *message); - (BOOL)debugAppWithBundleID:(NSString*)bundleID logger:(LogFunc)logger; - (NSDictionary*)getAppListWithError:(NSError**)error; - (UIImage*)getAppIconWithBundleId:(NSString*)bundleId error:(NSError**)error; +- (TcpProviderHandle*)getTcpProviderHandle; @end diff --git a/StikJIT/idevice/JITEnableContext.m b/StikJIT/idevice/JITEnableContext.m index 243a47a8..b42b8c8c 100644 --- a/StikJIT/idevice/JITEnableContext.m +++ b/StikJIT/idevice/JITEnableContext.m @@ -88,6 +88,10 @@ - (IdevicePairingFile*)getPairingFileWithError:(NSError**)error { return pairingFile; } +- (TcpProviderHandle*)getTcpProviderHandle { + return provider; +} + - (void)startHeartbeatWithCompletionHandler:(HeartbeatCompletionHandler)completionHandler logger:(LogFunc)logger { diff --git a/StikJIT/idevice/heartbeat.c b/StikJIT/idevice/heartbeat.c index d2ee11a2..9ff22230 100644 --- a/StikJIT/idevice/heartbeat.c +++ b/StikJIT/idevice/heartbeat.c @@ -55,7 +55,7 @@ void startHeartbeat(IdevicePairingFile* pairing_file, TcpProviderHandle** provid completion(0, "Heartbeat Completed"); - u_int64_t current_interval = 15; + u_int64_t current_interval = 10; while (1) { if(*heartbeatSessionId != currentSessionId) { break; diff --git a/StikJIT/idevice/idevice.h b/StikJIT/idevice/idevice.h index 6dab1c6f..6915f029 100644 --- a/StikJIT/idevice/idevice.h +++ b/StikJIT/idevice/idevice.h @@ -14,12 +14,28 @@ #define LOCKDOWN_PORT 62078 +typedef enum AfcFopenMode { + AfcRdOnly = 1, + AfcRw = 2, + AfcWrOnly = 3, + AfcWr = 4, + AfcAppend = 5, + AfcRdAppend = 6, +} AfcFopenMode; + +/** + * Link type for creating hard or symbolic links + */ +typedef enum AfcLinkType { + Hard = 1, + Symbolic = 2, +} AfcLinkType; typedef enum IdeviceErrorCode { IdeviceSuccess = 0, Socket = -1, - Ssl = -2, - SslSetup = -3, + Tls = -2, + TlsBuilderFailed = -3, Plist = -4, Utf8 = -5, UnexpectedResponse = -6, @@ -54,6 +70,13 @@ typedef enum IdeviceErrorCode { Utf8Error = -35, InvalidArgument = -36, UnknownErrorType = -37, + PemParseFailed = -38, + MisagentFailure = -39, + InstallationProxyOperationFailed = -40, + Afc = -41, + UnknownAfcOpcode = -42, + InvalidAfcMagic = -43, + AfcMissingAttribute = -44, AdapterIOFailed = -996, ServiceNotFound = -997, BufferTooSmall = -998, @@ -79,6 +102,15 @@ typedef enum IdeviceLoggerError { typedef struct AdapterHandle AdapterHandle; +typedef struct AfcClientHandle AfcClientHandle; + +/** + * Handle for an open file on the device + */ +typedef struct AfcFileHandle AfcFileHandle; + +typedef struct AmfiClientHandle AmfiClientHandle; + typedef struct CoreDeviceProxyHandle CoreDeviceProxyHandle; /** @@ -104,8 +136,15 @@ typedef struct ImageMounterHandle ImageMounterHandle; typedef struct InstallationProxyClientHandle InstallationProxyClientHandle; +/** + * Opaque handle to a ProcessControlClient + */ +typedef struct LocationSimulationAdapterHandle LocationSimulationAdapterHandle; + typedef struct LockdowndClientHandle LockdowndClientHandle; +typedef struct MisagentClientHandle MisagentClientHandle; + /** * Opaque handle to a ProcessControlClient */ @@ -133,6 +172,29 @@ typedef struct XPCDeviceAdapterHandle XPCDeviceAdapterHandle; typedef struct sockaddr sockaddr; +/** + * File information structure for C bindings + */ +typedef struct AfcFileInfo { + size_t size; + size_t blocks; + int64_t creation; + int64_t modified; + char *st_nlink; + char *st_ifmt; + char *st_link_target; +} AfcFileInfo; + +/** + * Device information structure for C bindings + */ +typedef struct AfcDeviceInfo { + char *model; + size_t total_bytes; + size_t free_bytes; + size_t block_size; +} AfcDeviceInfo; + /** * Represents a debugserver command */ @@ -353,6 +415,409 @@ enum IdeviceErrorCode adapter_recv(struct AdapterHandle *handle, uintptr_t *length, uintptr_t max_length); +/** + * Connects to the AFC service using a TCP provider + * + * # Arguments + * * [`provider`] - A TcpProvider + * * [`client`] - On success, will be set to point to a newly allocated AfcClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode afc_client_connect_tcp(struct TcpProviderHandle *provider, + struct AfcClientHandle **client); + +/** + * Connects to the AFC service using a Usbmuxd provider + * + * # Arguments + * * [`provider`] - A UsbmuxdProvider + * * [`client`] - On success, will be set to point to a newly allocated AfcClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode afc_client_connect_usbmuxd(struct UsbmuxdProviderHandle *provider, + struct AfcClientHandle **client); + +/** + * Creates a new AfcClient from an existing Idevice connection + * + * # Arguments + * * [`socket`] - An IdeviceSocket handle + * * [`client`] - On success, will be set to point to a newly allocated AfcClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode afc_client_new(struct IdeviceHandle *socket, struct AfcClientHandle **client); + +/** + * Frees an AfcClient handle + * + * # Arguments + * * [`handle`] - The handle to free + * + * # Safety + * `handle` must be a valid pointer to the handle that was allocated by this library, + * or NULL (in which case this function does nothing) + */ +void afc_client_free(struct AfcClientHandle *handle); + +/** + * Lists the contents of a directory on the device + * + * # Arguments + * * [`client`] - A valid AfcClient handle + * * [`path`] - Path to the directory to list (UTF-8 null-terminated) + * * [`entries`] - Will be set to point to an array of directory entries + * * [`count`] - Will be set to the number of entries + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * All pointers must be valid and non-null + * `path` must be a valid null-terminated C string + */ +enum IdeviceErrorCode afc_list_directory(struct AfcClientHandle *client, + const char *path, + char ***entries, + size_t *count); + +/** + * Creates a new directory on the device + * + * # Arguments + * * [`client`] - A valid AfcClient handle + * * [`path`] - Path of the directory to create (UTF-8 null-terminated) + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `path` must be a valid null-terminated C string + */ +enum IdeviceErrorCode afc_make_directory(struct AfcClientHandle *client, const char *path); + +/** + * Retrieves information about a file or directory + * + * # Arguments + * * [`client`] - A valid AfcClient handle + * * [`path`] - Path to the file or directory (UTF-8 null-terminated) + * * [`info`] - Will be populated with file information + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` and `path` must be valid pointers + * `info` must be a valid pointer to an AfcFileInfo struct + */ +enum IdeviceErrorCode afc_get_file_info(struct AfcClientHandle *client, + const char *path, + struct AfcFileInfo *info); + +/** + * Frees memory allocated by afc_get_file_info + * + * # Arguments + * * [`info`] - Pointer to AfcFileInfo struct to free + * + * # Safety + * `info` must be a valid pointer to an AfcFileInfo struct previously returned by afc_get_file_info + */ +void afc_file_info_free(struct AfcFileInfo *info); + +/** + * Retrieves information about the device's filesystem + * + * # Arguments + * * [`client`] - A valid AfcClient handle + * * [`info`] - Will be populated with device information + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` and `info` must be valid pointers + */ +enum IdeviceErrorCode afc_get_device_info(struct AfcClientHandle *client, + struct AfcDeviceInfo *info); + +/** + * Frees memory allocated by afc_get_device_info + * + * # Arguments + * * [`info`] - Pointer to AfcDeviceInfo struct to free + * + * # Safety + * `info` must be a valid pointer to an AfcDeviceInfo struct previously returned by afc_get_device_info + */ +void afc_device_info_free(struct AfcDeviceInfo *info); + +/** + * Removes a file or directory + * + * # Arguments + * * [`client`] - A valid AfcClient handle + * * [`path`] - Path to the file or directory to remove (UTF-8 null-terminated) + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `path` must be a valid null-terminated C string + */ +enum IdeviceErrorCode afc_remove_path(struct AfcClientHandle *client, const char *path); + +/** + * Recursively removes a directory and all its contents + * + * # Arguments + * * [`client`] - A valid AfcClient handle + * * [`path`] - Path to the directory to remove (UTF-8 null-terminated) + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `path` must be a valid null-terminated C string + */ +enum IdeviceErrorCode afc_remove_path_and_contents(struct AfcClientHandle *client, + const char *path); + +/** + * Opens a file on the device + * + * # Arguments + * * [`client`] - A valid AfcClient handle + * * [`path`] - Path to the file to open (UTF-8 null-terminated) + * * [`mode`] - File open mode + * * [`handle`] - Will be set to a new file handle on success + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * All pointers must be valid and non-null + * `path` must be a valid null-terminated C string + */ +enum IdeviceErrorCode afc_file_open(struct AfcClientHandle *client, + const char *path, + enum AfcFopenMode mode, + struct AfcFileHandle **handle); + +/** + * Closes a file handle + * + * # Arguments + * * [`handle`] - File handle to close + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +enum IdeviceErrorCode afc_file_close(struct AfcFileHandle *handle); + +/** + * Reads data from an open file + * + * # Arguments + * * [`handle`] - File handle to read from + * * [`data`] - Will be set to point to the read data + * * [`length`] - Will be set to the length of the read data + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * All pointers must be valid and non-null + */ +enum IdeviceErrorCode afc_file_read(struct AfcFileHandle *handle, uint8_t **data, size_t *length); + +/** + * Writes data to an open file + * + * # Arguments + * * [`handle`] - File handle to write to + * * [`data`] - Data to write + * * [`length`] - Length of data to write + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * All pointers must be valid and non-null + * `data` must point to at least `length` bytes + */ +enum IdeviceErrorCode afc_file_write(struct AfcFileHandle *handle, + const uint8_t *data, + size_t length); + +/** + * Creates a hard or symbolic link + * + * # Arguments + * * [`client`] - A valid AfcClient handle + * * [`target`] - Target path of the link (UTF-8 null-terminated) + * * [`source`] - Path where the link should be created (UTF-8 null-terminated) + * * [`link_type`] - Type of link to create + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * All pointers must be valid and non-null + * `target` and `source` must be valid null-terminated C strings + */ +enum IdeviceErrorCode afc_make_link(struct AfcClientHandle *client, + const char *target, + const char *source, + enum AfcLinkType link_type); + +/** + * Renames a file or directory + * + * # Arguments + * * [`client`] - A valid AfcClient handle + * * [`source`] - Current path of the file/directory (UTF-8 null-terminated) + * * [`target`] - New path for the file/directory (UTF-8 null-terminated) + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * All pointers must be valid and non-null + * `source` and `target` must be valid null-terminated C strings + */ +enum IdeviceErrorCode afc_rename_path(struct AfcClientHandle *client, + const char *source, + const char *target); + +/** + * Automatically creates and connects to AMFI service, returning a client handle + * + * # Arguments + * * [`provider`] - A TcpProvider + * * [`client`] - On success, will be set to point to a newly allocated AmfiClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode amfi_connect_tcp(struct TcpProviderHandle *provider, + struct AmfiClientHandle **client); + +/** + * Automatically creates and connects to AMFI service, returning a client handle + * + * # Arguments + * * [`provider`] - A UsbmuxdProvider + * * [`client`] - On success, will be set to point to a newly allocated AmfiClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode amfi_connect_usbmuxd(struct UsbmuxdProviderHandle *provider, + struct AmfiClientHandle **client); + +/** + * Automatically creates and connects to AMFI service, returning a client handle + * + * # Arguments + * * [`socket`] - An IdeviceSocket handle + * * [`client`] - On success, will be set to point to a newly allocated AmfiClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode amfi_new(struct IdeviceHandle *socket, struct AmfiClientHandle **client); + +/** + * Shows the option in the settings UI + * + * # Arguments + * * `client` - A valid AmfiClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + */ +enum IdeviceErrorCode amfi_reveal_developer_mode_option_in_ui(struct AmfiClientHandle *client); + +/** + * Enables developer mode on the device + * + * # Arguments + * * `client` - A valid AmfiClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + */ +enum IdeviceErrorCode amfi_enable_developer_mode(struct AmfiClientHandle *client); + +/** + * Accepts developer mode on the device + * + * # Arguments + * * `client` - A valid AmfiClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + */ +enum IdeviceErrorCode amfi_accept_developer_mode(struct AmfiClientHandle *client); + +/** + * Frees a handle + * + * # Arguments + * * [`handle`] - The handle to free + * + * # Safety + * `handle` must be a valid pointer to the handle that was allocated by this library, + * or NULL (in which case this function does nothing) + */ +void amfi_client_free(struct AmfiClientHandle *handle); + /** * Automatically creates and connects to Core Device Proxy, returning a client handle * @@ -775,151 +1240,394 @@ enum IdeviceErrorCode heartbeat_connect_usbmuxd(struct UsbmuxdProviderHandle *pr struct HeartbeatClientHandle **client); /** - * Automatically creates and connects to Installation Proxy, returning a client handle + * Automatically creates and connects to Installation Proxy, returning a client handle + * + * # Arguments + * * [`socket`] - An IdeviceSocket handle + * * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode heartbeat_new(struct IdeviceHandle *socket, + struct HeartbeatClientHandle **client); + +/** + * Sends a polo to the device + * + * # Arguments + * * `client` - A valid HeartbeatClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + */ +enum IdeviceErrorCode heartbeat_send_polo(struct HeartbeatClientHandle *client); + +/** + * Sends a polo to the device + * + * # Arguments + * * `client` - A valid HeartbeatClient handle + * * `interval` - The time to wait for a marco + * * `new_interval` - A pointer to set the requested marco + * + * # Returns + * An error code indicating success or failure. + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + */ +enum IdeviceErrorCode heartbeat_get_marco(struct HeartbeatClientHandle *client, + uint64_t interval, + uint64_t *new_interval); + +/** + * Frees a handle + * + * # Arguments + * * [`handle`] - The handle to free + * + * # Safety + * `handle` must be a valid pointer to the handle that was allocated by this library, + * or NULL (in which case this function does nothing) + */ +void heartbeat_client_free(struct HeartbeatClientHandle *handle); + +/** + * Automatically creates and connects to Installation Proxy, returning a client handle + * + * # Arguments + * * [`provider`] - A TcpProvider + * * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode installation_proxy_connect_tcp(struct TcpProviderHandle *provider, + struct InstallationProxyClientHandle **client); + +/** + * Automatically creates and connects to Installation Proxy, returning a client handle + * + * # Arguments + * * [`provider`] - A UsbmuxdProvider + * * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode installation_proxy_connect_usbmuxd(struct UsbmuxdProviderHandle *provider, + struct InstallationProxyClientHandle **client); + +/** + * Automatically creates and connects to Installation Proxy, returning a client handle + * + * # Arguments + * * [`socket`] - An IdeviceSocket handle + * * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode installation_proxy_new(struct IdeviceHandle *socket, + struct InstallationProxyClientHandle **client); + +/** + * Gets installed apps on the device + * + * # Arguments + * * [`client`] - A valid InstallationProxyClient handle + * * [`application_type`] - The application type to filter by (optional, NULL for "Any") + * * [`bundle_identifiers`] - The identifiers to filter by (optional, NULL for all apps) + * * [`out_result`] - On success, will be set to point to a newly allocated array of PlistRef + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `out_result` must be a valid, non-null pointer to a location where the result will be stored + */ +enum IdeviceErrorCode installation_proxy_get_apps(struct InstallationProxyClientHandle *client, + const char *application_type, + const char *const *bundle_identifiers, + size_t bundle_identifiers_len, + void **out_result, + size_t *out_result_len); + +/** + * Frees a handle + * + * # Arguments + * * [`handle`] - The handle to free + * + * # Safety + * `handle` must be a valid pointer to the handle that was allocated by this library, + * or NULL (in which case this function does nothing) + */ +void installation_proxy_client_free(struct InstallationProxyClientHandle *handle); + +/** + * Installs an application package on the device + * + * # Arguments + * * [`client`] - A valid InstallationProxyClient handle + * * [`package_path`] - Path to the .ipa package in the AFC jail + * * [`options`] - Optional installation options as a plist dictionary (can be NULL) + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `package_path` must be a valid C string + * `options` must be a valid plist dictionary or NULL + */ +enum IdeviceErrorCode installation_proxy_install(struct InstallationProxyClientHandle *client, + const char *package_path, + void *options); + +/** + * Installs an application package on the device + * + * # Arguments + * * [`client`] - A valid InstallationProxyClient handle + * * [`package_path`] - Path to the .ipa package in the AFC jail + * * [`options`] - Optional installation options as a plist dictionary (can be NULL) + * * [`callback`] - Progress callback function + * * [`context`] - User context to pass to callback + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `package_path` must be a valid C string + * `options` must be a valid plist dictionary or NULL + */ +enum IdeviceErrorCode installation_proxy_install_with_callback(struct InstallationProxyClientHandle *client, + const char *package_path, + void *options, + void (*callback)(uint64_t progress, + void *context), + void *context); + +/** + * Upgrades an existing application on the device + * + * # Arguments + * * [`client`] - A valid InstallationProxyClient handle + * * [`package_path`] - Path to the .ipa package in the AFC jail + * * [`options`] - Optional upgrade options as a plist dictionary (can be NULL) + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `package_path` must be a valid C string + * `options` must be a valid plist dictionary or NULL + */ +enum IdeviceErrorCode installation_proxy_upgrade(struct InstallationProxyClientHandle *client, + const char *package_path, + void *options); + +/** + * Upgrades an existing application on the device * * # Arguments - * * [`socket`] - An IdeviceSocket handle - * * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle + * * [`client`] - A valid InstallationProxyClient handle + * * [`package_path`] - Path to the .ipa package in the AFC jail + * * [`options`] - Optional upgrade options as a plist dictionary (can be NULL) + * * [`callback`] - Progress callback function + * * [`context`] - User context to pass to callback * * # Returns * An error code indicating success or failure * * # Safety - * `socket` must be a valid pointer to a handle allocated by this library - * `client` must be a valid, non-null pointer to a location where the handle will be stored + * `client` must be a valid pointer to a handle allocated by this library + * `package_path` must be a valid C string + * `options` must be a valid plist dictionary or NULL */ -enum IdeviceErrorCode heartbeat_new(struct IdeviceHandle *socket, - struct HeartbeatClientHandle **client); +enum IdeviceErrorCode installation_proxy_upgrade_with_callback(struct InstallationProxyClientHandle *client, + const char *package_path, + void *options, + void (*callback)(uint64_t progress, + void *context), + void *context); /** - * Sends a polo to the device + * Uninstalls an application from the device * * # Arguments - * * `client` - A valid HeartbeatClient handle + * * [`client`] - A valid InstallationProxyClient handle + * * [`bundle_id`] - Bundle identifier of the application to uninstall + * * [`options`] - Optional uninstall options as a plist dictionary (can be NULL) * * # Returns * An error code indicating success or failure * * # Safety * `client` must be a valid pointer to a handle allocated by this library + * `bundle_id` must be a valid C string + * `options` must be a valid plist dictionary or NULL */ -enum IdeviceErrorCode heartbeat_send_polo(struct HeartbeatClientHandle *client); +enum IdeviceErrorCode installation_proxy_uninstall(struct InstallationProxyClientHandle *client, + const char *bundle_id, + void *options); /** - * Sends a polo to the device + * Uninstalls an application from the device * * # Arguments - * * `client` - A valid HeartbeatClient handle - * * `interval` - The time to wait for a marco - * * `new_interval` - A pointer to set the requested marco + * * [`client`] - A valid InstallationProxyClient handle + * * [`bundle_id`] - Bundle identifier of the application to uninstall + * * [`options`] - Optional uninstall options as a plist dictionary (can be NULL) + * * [`callback`] - Progress callback function + * * [`context`] - User context to pass to callback * * # Returns - * An error code indicating success or failure. + * An error code indicating success or failure * * # Safety * `client` must be a valid pointer to a handle allocated by this library + * `bundle_id` must be a valid C string + * `options` must be a valid plist dictionary or NULL */ -enum IdeviceErrorCode heartbeat_get_marco(struct HeartbeatClientHandle *client, - uint64_t interval, - uint64_t *new_interval); +enum IdeviceErrorCode installation_proxy_uninstall_with_callback(struct InstallationProxyClientHandle *client, + const char *bundle_id, + void *options, + void (*callback)(uint64_t progress, + void *context), + void *context); /** - * Frees a handle + * Checks if the device capabilities match the required capabilities * * # Arguments - * * [`handle`] - The handle to free + * * [`client`] - A valid InstallationProxyClient handle + * * [`capabilities`] - Array of plist values representing required capabilities + * * [`capabilities_len`] - Length of the capabilities array + * * [`options`] - Optional check options as a plist dictionary (can be NULL) + * * [`out_result`] - Will be set to true if all capabilities are supported, false otherwise + * + * # Returns + * An error code indicating success or failure * * # Safety - * `handle` must be a valid pointer to the handle that was allocated by this library, - * or NULL (in which case this function does nothing) + * `client` must be a valid pointer to a handle allocated by this library + * `capabilities` must be a valid array of plist values or NULL + * `options` must be a valid plist dictionary or NULL + * `out_result` must be a valid pointer to a bool */ -void heartbeat_client_free(struct HeartbeatClientHandle *handle); +enum IdeviceErrorCode installation_proxy_check_capabilities_match(struct InstallationProxyClientHandle *client, + void *const *capabilities, + size_t capabilities_len, + void *options, + bool *out_result); /** - * Automatically creates and connects to Installation Proxy, returning a client handle + * Browses installed applications on the device * * # Arguments - * * [`provider`] - A TcpProvider - * * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle + * * [`client`] - A valid InstallationProxyClient handle + * * [`options`] - Optional browse options as a plist dictionary (can be NULL) + * * [`out_result`] - On success, will be set to point to a newly allocated array of PlistRef + * * [`out_result_len`] - Will be set to the length of the result array * * # Returns * An error code indicating success or failure * * # Safety - * `provider` must be a valid pointer to a handle allocated by this library - * `client` must be a valid, non-null pointer to a location where the handle will be stored + * `client` must be a valid pointer to a handle allocated by this library + * `options` must be a valid plist dictionary or NULL + * `out_result` must be a valid, non-null pointer to a location where the result will be stored + * `out_result_len` must be a valid, non-null pointer to a location where the length will be stored */ -enum IdeviceErrorCode installation_proxy_connect_tcp(struct TcpProviderHandle *provider, - struct InstallationProxyClientHandle **client); +enum IdeviceErrorCode installation_proxy_browse(struct InstallationProxyClientHandle *client, + void *options, + void **out_result, + size_t *out_result_len); /** - * Automatically creates and connects to Installation Proxy, returning a client handle + * Creates a new ProcessControlClient from a RemoteServerClient * * # Arguments - * * [`provider`] - A UsbmuxdProvider - * * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle + * * [`server`] - The RemoteServerClient to use + * * [`handle`] - Pointer to store the newly created ProcessControlClient handle * * # Returns * An error code indicating success or failure * * # Safety - * `provider` must be a valid pointer to a handle allocated by this library - * `client` must be a valid, non-null pointer to a location where the handle will be stored + * `server` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored */ -enum IdeviceErrorCode installation_proxy_connect_usbmuxd(struct UsbmuxdProviderHandle *provider, - struct InstallationProxyClientHandle **client); +enum IdeviceErrorCode location_simulation_new(struct RemoteServerAdapterHandle *server, + struct LocationSimulationAdapterHandle **handle); /** - * Automatically creates and connects to Installation Proxy, returning a client handle + * Frees a ProcessControlClient handle * * # Arguments - * * [`socket`] - An IdeviceSocket handle - * * [`client`] - On success, will be set to point to a newly allocated InstallationProxyClient handle - * - * # Returns - * An error code indicating success or failure + * * [`handle`] - The handle to free * * # Safety - * `socket` must be a valid pointer to a handle allocated by this library - * `client` must be a valid, non-null pointer to a location where the handle will be stored + * `handle` must be a valid pointer to a handle allocated by this library or NULL */ -enum IdeviceErrorCode installation_proxy_new(struct IdeviceHandle *socket, - struct InstallationProxyClientHandle **client); +void location_simulation_free(struct LocationSimulationAdapterHandle *handle); /** - * Gets installed apps on the device + * Clears the location set * * # Arguments - * * `client` - A valid InstallationProxyClient handle - * * `application_type` - The application type to filter by (optional, NULL for "Any") - * * `bundle_identifiers` - The identifiers to filter by (optional, NULL for all apps) - * * `out_result` - On success, will be set to point to a newly allocated array of PlistRef + * * [`handle`] - The LocationSimulation handle * * # Returns * An error code indicating success or failure * * # Safety - * `client` must be a valid pointer to a handle allocated by this library - * `out_result` must be a valid, non-null pointer to a location where the result will be stored + * All pointers must be valid or NULL where appropriate */ -enum IdeviceErrorCode installation_proxy_get_apps(struct InstallationProxyClientHandle *client, - const char *application_type, - const char *const *bundle_identifiers, - size_t bundle_identifiers_len, - void **out_result, - size_t *out_result_len); +enum IdeviceErrorCode location_simulation_clear(struct LocationSimulationAdapterHandle *handle); /** - * Frees a handle + * Sets the location * * # Arguments - * * [`handle`] - The handle to free + * * [`handle`] - The LocationSimulation handle + * * [`latitude`] - The latitude to set + * * [`longitude`] - The longitude to set + * + * # Returns + * An error code indicating success or failure * * # Safety - * `handle` must be a valid pointer to the handle that was allocated by this library, - * or NULL (in which case this function does nothing) + * All pointers must be valid or NULL where appropriate */ -void installation_proxy_client_free(struct InstallationProxyClientHandle *handle); +enum IdeviceErrorCode location_simulation_set(struct LocationSimulationAdapterHandle *handle, + double latitude, + double longitude); /** * Connects to lockdownd service using TCP provider @@ -1016,7 +1724,8 @@ enum IdeviceErrorCode lockdownd_start_service(struct LockdowndClientHandle *clie * * # Arguments * * `client` - A valid LockdowndClient handle - * * `value` - The value to get (null-terminated string) + * * `key` - The value to get (null-terminated string) + * * `domain` - The value to get (null-terminated string) * * `out_plist` - Pointer to store the returned plist value * * # Returns @@ -1028,7 +1737,8 @@ enum IdeviceErrorCode lockdownd_start_service(struct LockdowndClientHandle *clie * `out_plist` must be a valid pointer to store the plist */ enum IdeviceErrorCode lockdownd_get_value(struct LockdowndClientHandle *client, - const char *value, + const char *key, + const char *domain, void **out_plist); /** @@ -1086,6 +1796,121 @@ enum IdeviceLoggerError idevice_init_logger(enum IdeviceLogLevel console_level, enum IdeviceLogLevel file_level, char *file_path); +/** + * Automatically creates and connects to Misagent, returning a client handle + * + * # Arguments + * * [`provider`] - A TcpProvider + * * [`client`] - On success, will be set to point to a newly allocated MisagentClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode misagent_connect_tcp(struct TcpProviderHandle *provider, + struct MisagentClientHandle **client); + +/** + * Automatically creates and connects to Misagent, returning a client handle + * + * # Arguments + * * [`provider`] - A UsbmuxdProvider + * * [`client`] - On success, will be set to point to a newly allocated MisagentClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode misagent_connect_usbmuxd(struct UsbmuxdProviderHandle *provider, + struct MisagentClientHandle **client); + +/** + * Installs a provisioning profile on the device + * + * # Arguments + * * [`client`] - A valid MisagentClient handle + * * [`profile_data`] - The provisioning profile data to install + * * [`profile_len`] - Length of the profile data + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `profile_data` must be a valid pointer to profile data of length `profile_len` + */ +enum IdeviceErrorCode misagent_install(struct MisagentClientHandle *client, + const uint8_t *profile_data, + size_t profile_len); + +/** + * Removes a provisioning profile from the device + * + * # Arguments + * * [`client`] - A valid MisagentClient handle + * * [`profile_id`] - The UUID of the profile to remove (C string) + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `profile_id` must be a valid C string + */ +enum IdeviceErrorCode misagent_remove(struct MisagentClientHandle *client, const char *profile_id); + +/** + * Retrieves all provisioning profiles from the device + * + * # Arguments + * * [`client`] - A valid MisagentClient handle + * * [`out_profiles`] - On success, will be set to point to an array of profile data + * * [`out_profiles_len`] - On success, will be set to the number of profiles + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `out_profiles` must be a valid pointer to store the resulting array + * `out_profiles_len` must be a valid pointer to store the array length + */ +enum IdeviceErrorCode misagent_copy_all(struct MisagentClientHandle *client, + uint8_t ***out_profiles, + size_t **out_profiles_len, + size_t *out_count); + +/** + * Frees profiles array returned by misagent_copy_all + * + * # Arguments + * * [`profiles`] - Array of profile data pointers + * * [`lens`] - Array of profile lengths + * * [`count`] - Number of profiles in the array + * + * # Safety + * Must only be called with values returned from misagent_copy_all + */ +void misagent_free_profiles(uint8_t **profiles, size_t *lens, size_t count); + +/** + * Frees a misagent client handle + * + * # Arguments + * * [`handle`] - The handle to free + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library, + * or NULL (in which case this function does nothing) + */ +void misagent_client_free(struct MisagentClientHandle *handle); + /** * Connects to the Image Mounter service using a TCP provider * @@ -1911,6 +2736,89 @@ enum IdeviceErrorCode xpc_device_get_service_names(struct XPCDeviceAdapterHandle */ void xpc_device_free_service_names(char **names, uintptr_t count); +/** + * Connects to the Springboard service using a TCP provider + * + * # Arguments + * * [`provider`] - A TcpProvider + * * [`client`] - On success, will be set to point to a newly allocated SpringBoardServicesClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode springboard_services_connect_tcp(struct TcpProviderHandle *provider, + struct SpringBoardServicesClientHandle **client); + +/** + * Connects to the Springboard service using a usbmuxd provider + * + * # Arguments + * * [`provider`] - A UsbmuxdProvider + * * [`client`] - On success, will be set to point to a newly allocated SpringBoardServicesClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode springboard_services_connect_usbmuxd(struct UsbmuxdProviderHandle *provider, + struct SpringBoardServicesClientHandle **client); + +/** + * Creates a new SpringBoardServices client from an existing Idevice connection + * + * # Arguments + * * [`socket`] - An IdeviceSocket handle + * * [`client`] - On success, will be set to point to a newly allocated SpringBoardServicesClient handle + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +enum IdeviceErrorCode springboard_services_new(struct IdeviceHandle *socket, + struct SpringBoardServicesClientHandle **client); + +/** + * Gets the icon of the specified app by bundle identifier + * + * # Arguments + * * `client` - A valid SpringBoardServicesClient handle + * * `bundle_identifier` - The identifiers of the app to get icon + * * `out_result` - On success, will be set to point to a newly allocated png data + * + * # Returns + * An error code indicating success or failure + * + * # Safety + * `client` must be a valid pointer to a handle allocated by this library + * `out_result` must be a valid, non-null pointer to a location where the result will be stored + */ +enum IdeviceErrorCode springboard_services_get_icon(struct SpringBoardServicesClientHandle *client, + const char *bundle_identifier, + void **out_result, + size_t *out_result_len); + +/** + * Frees an SpringBoardServicesClient handle + * + * # Arguments + * * [`handle`] - The handle to free + * + * # Safety + * `handle` must be a valid pointer to the handle that was allocated by this library, + * or NULL (in which case this function does nothing) + */ +void springboard_services_free(struct SpringBoardServicesClientHandle *handle); + /** * Connects to a usbmuxd instance over TCP * @@ -2011,35 +2919,4 @@ enum IdeviceErrorCode idevice_usbmuxd_unix_addr_new(const char *addr, */ void idevice_usbmuxd_addr_free(struct UsbmuxdAddrHandle *usbmuxd_addr); -enum IdeviceErrorCode springboard_services_connect_tcp(struct TcpProviderHandle *provider, - struct SpringBoardServicesClientHandle **client); - -enum IdeviceErrorCode springboard_services_connect_usbmuxd(struct UsbmuxdProviderHandle *provider, - struct SpringBoardServicesClientHandle **client); - -enum IdeviceErrorCode springboard_services_new(struct IdeviceHandle *socket, - struct SpringBoardServicesClientHandle **client); - -/** - * Gets the icon of the specified app by bundle identifier - * - * # Arguments - * * `client` - A valid SpringBoardServicesClient handle - * * `bundle_identifier` - The identifiers of the app to get icon - * * `out_result` - On success, will be set to point to a newly allocated png data - * - * # Returns - * An error code indicating success or failure - * - * # Safety - * `client` must be a valid pointer to a handle allocated by this library - * `out_result` must be a valid, non-null pointer to a location where the result will be stored - */ -enum IdeviceErrorCode springboard_services_get_icon(struct SpringBoardServicesClientHandle *client, - const char *bundle_identifier, - void **out_result, - size_t *out_result_len); - -void springboard_services_free(struct SpringBoardServicesClientHandle *handle); - #endif diff --git a/StikJIT/idevice/libidevice_ffi.a b/StikJIT/idevice/libidevice_ffi.a index 594e0ef9..c84186c8 100644 Binary files a/StikJIT/idevice/libidevice_ffi.a and b/StikJIT/idevice/libidevice_ffi.a differ