From e9d9f3578eb5558a18823559b0dabca0b501506d Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 21 Feb 2026 12:40:04 +0800 Subject: [PATCH] Remove minitool cuz it's no longer useful --- .../JSSupport/IDeviceJSBridge.m | 211 ------ .../JSSupport/IDeviceJSBridgeAFC.m | 370 ---------- .../JSSupport/IDeviceJSBridgeAMFI.m | 86 --- .../JSSupport/IDeviceJSBridgeAdapter.m | 37 - .../JSSupport/IDeviceJSBridgeCoreDevice.m | 73 -- .../JSSupport/IDeviceJSBridgeDebugProxy.m | 90 --- .../IDeviceJSBridgeInstallationProxy.m | 301 --------- .../IDeviceJSBridgeLocationSimulation.m | 78 --- .../JSSupport/IDeviceJSBridgeMisagent.m | 120 ---- .../IDeviceJSBridgeNSData+LocalFile.m | 191 ------ .../JSSupport/IDeviceJSBridgeProcessControl.m | 125 ---- .../JSSupport/IDeviceJSBridgeRSD.m | 182 ----- .../JSSupport/IDeviceJSBridgeRemoteServer.m | 43 -- .../JSSupport/IDeviceJSBridgeSBServices.m | 67 -- StikJIT/MiniToolSupport/JSSupport/JSSupport.h | 42 -- .../MiniToolSupport/JSSupport/NSInvocation.m | 44 -- StikJIT/MiniToolSupport/JSSupport/idevice.js | 515 -------------- .../MiniToolSupport/MiniToolEditorView.swift | 97 --- .../MiniToolSupport/MiniToolListView.swift | 440 ------------ StikJIT/MiniToolSupport/MiniToolModels.swift | 144 ---- .../MiniToolSupport/MiniToolRunnerView.swift | 111 --- StikJIT/MiniToolSupport/MiniToolRuntime.swift | 630 ------------------ StikJIT/StikJIT-Bridging-Header.h | 1 - StikJIT/Utilities/FeatureFlags.swift | 2 - StikJIT/Utilities/TabConfiguration.swift | 3 - StikJIT/Views/MainTabView.swift | 3 - StikJIT/Views/SettingsView.swift | 3 - 27 files changed, 4009 deletions(-) delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridge.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAFC.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAMFI.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAdapter.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeCoreDevice.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeDebugProxy.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeInstallationProxy.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeLocationSimulation.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeMisagent.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeNSData+LocalFile.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeProcessControl.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeRSD.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeRemoteServer.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeSBServices.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/JSSupport.h delete mode 100644 StikJIT/MiniToolSupport/JSSupport/NSInvocation.m delete mode 100644 StikJIT/MiniToolSupport/JSSupport/idevice.js delete mode 100644 StikJIT/MiniToolSupport/MiniToolEditorView.swift delete mode 100644 StikJIT/MiniToolSupport/MiniToolListView.swift delete mode 100644 StikJIT/MiniToolSupport/MiniToolModels.swift delete mode 100644 StikJIT/MiniToolSupport/MiniToolRunnerView.swift delete mode 100644 StikJIT/MiniToolSupport/MiniToolRuntime.swift diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridge.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridge.m deleted file mode 100644 index 63bdc2e..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridge.m +++ /dev/null @@ -1,211 +0,0 @@ -// -// IDeviceJSBridge.m -// StikJIT -// -// Created by s s on 2025/4/24. -// -@import Foundation; -#import "JSSupport.h" - -@implementation IDeviceHandle - -@end - - -NSDictionary *dictionaryFromPlist(plist_t plist, NSError **error) { - if (!plist) { - if (error) { - *error = [NSError errorWithDomain:@"PlistConversionErrorDomain" - code:1001 - userInfo:@{NSLocalizedDescriptionKey: @"Input plist is nil."}]; - } - return nil; - } - char* buf = 0; - uint32_t plistlen = 0; - // try bin plist first, if failed, we try xml plist - plist_to_bin(plist, &buf, &plistlen); - - NSPropertyListFormat format; - NSDictionary *result = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buf length:plistlen] - options:NSPropertyListImmutable format:&format error:error]; - plist_mem_free(buf); - if (![result isKindOfClass:[NSDictionary class]]) { - plist_to_xml(plist, &buf, &plistlen); - if (error) { - *error = nil; - } - result = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buf length:plistlen] - options:NSPropertyListImmutable format:&format error:error]; - plist_free(buf); - } - - 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)initWithContext:(JSContext*)context allowedFunctions:(NSArray*)allowedFunctions { - maxHandleId = 0; - maxDataId = 0; - handles = [[NSMutableDictionary alloc] init]; - dataPool = [[NSMutableDictionary alloc] init]; - self->allowedFunctions = allowedFunctions; - self->context = context; - return self; -} - -- (NSString*)errFreeFromIdeviceFfiError:(IdeviceFfiError*)err { - NSString* ans = [NSString stringWithFormat:@"%s (%d)", err->message, err->code]; - idevice_error_free(err); - return ans; -} - -- (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)didReceiveScriptMessage:(nonnull NSDictionary *)message resolve:(JSValue*)resolveFunc reject:(JSValue*)rejectFunc { - if(![message isKindOfClass:NSDictionary.class]) { - [rejectFunc callWithArguments:@[@"Input is not a dictionary"]]; - return; - } - - if(![self->allowedFunctions containsObject:message[@"command"]]) { - [rejectFunc callWithArguments:@[[NSString stringWithFormat:@"calling %@ is not allowed! Declare it in toolInfo.json", message[@"command"]]]]; - return; - } - - NSString* handlerSelectorStr = [NSString stringWithFormat:@"%@WithBody:replyHandler:", message[@"command"]]; - if(![self respondsToSelector:NSSelectorFromString(handlerSelectorStr)]) { - [rejectFunc callWithArguments:@[@"Invalid idevice function!"]]; - return; - } - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - void (^replyHandler)(id _Nullable, NSString * _Nullable) = ^(id result, NSString* errMsg) { - if(errMsg) { - [rejectFunc callWithArguments:@[errMsg]]; - } else { - [resolveFunc callWithArguments:@[result]]; - } - }; - - NSInvocation* invocation = [NSInvocation invocationWithTarget:self selector:NSSelectorFromString(handlerSelectorStr) retainArguments:YES, message, 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); -} - -- (void)dealloc { - [self cleanUp]; -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAFC.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAFC.m deleted file mode 100644 index 3b33118..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAFC.m +++ /dev/null @@ -1,370 +0,0 @@ -// -// IDeviceJSBridgeAFC.m -// StikJIT -// -// Created by s s on 2025/4/25. -// - -@import Foundation; -#import "JSSupport.h" - -@implementation IDeviceJSBridge (AFC) - -- (void)afc_client_connectWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - NSError* heartbeatErr = nil; - [JITEnableContext.shared ensureHeartbeatWithError:&heartbeatErr]; - if(heartbeatErr) { - replyHandler(nil, heartbeatErr.localizedDescription); - return; - } - - IdeviceProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; - - AfcClientHandle* client = NULL; - IdeviceFfiError* err = afc_client_connect(provider, &client); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - IdeviceFfiError* err = afc_list_directory(client, [path UTF8String], &entries, &count); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - - IdeviceFfiError* err = afc_make_directory(client, [path UTF8String]); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - - IdeviceFfiError* err = afc_remove_path(client, [path UTF8String]); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - - IdeviceFfiError* err = afc_remove_path_and_contents(client, [path UTF8String]); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - - IdeviceFfiError* err = afc_rename_path(client, [source UTF8String], [target UTF8String]); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - struct IdeviceFfiError* err = afc_get_file_info(client, [path UTF8String], &info); - - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - 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; - struct IdeviceFfiError* err = afc_get_device_info(client, &info); - - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - 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; - IdeviceFfiError* err = afc_file_open(client, [path UTF8String], (AfcFopenMode)[modeNumber intValue], &fileHandle); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - - IdeviceFfiError* err = afc_file_close(fileHandle); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - IdeviceFfiError* err = afc_file_read(fileHandle, &file_data, &len); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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)]; - - IdeviceFfiError* err = afc_file_write(fileHandle, [data bytes], [data length]); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - - IdeviceFfiError* err = afc_make_link(client, - [target UTF8String], - [source UTF8String], - (AfcLinkType)[linkTypeNum intValue]); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - replyHandler(@YES, nil); -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAMFI.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAMFI.m deleted file mode 100644 index 3d3c107..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAMFI.m +++ /dev/null @@ -1,86 +0,0 @@ -// -// 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_connectWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - NSError* heartbeatErr = nil; - [JITEnableContext.shared ensureHeartbeatWithError:&heartbeatErr]; - if(heartbeatErr) { - replyHandler(nil, heartbeatErr.localizedDescription); - return; - } - - IdeviceProviderHandle *provider = [JITEnableContext.shared getTcpProviderHandle]; - - AmfiClientHandle *client = NULL; - IdeviceFfiError* err = amfi_connect(provider, &client); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - IdeviceFfiError* err = amfi_reveal_developer_mode_option_in_ui(client); - if (err ) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - IdeviceFfiError* err = amfi_enable_developer_mode(client); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - IdeviceFfiError* err = amfi_accept_developer_mode(client); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - replyHandler(@YES, nil); -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAdapter.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAdapter.m deleted file mode 100644 index 48ec3a4..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeAdapter.m +++ /dev/null @@ -1,37 +0,0 @@ -// -// IDeviceJSBridgeAdapter.m -// StikJIT -// -// Created by s s on 2025/4/25. -// -@import Foundation; -#import "JSSupport.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; - AdapterStreamHandle *stream = NULL; - IdeviceFfiError* err = adapter_connect(handle, port, (ReadWriteOpaque **)&stream); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - int handleId = [self registerIdeviceHandle:stream freeFunc:(void *)adapter_stream_close]; - replyHandler(@(handleId), nil); -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeCoreDevice.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeCoreDevice.m deleted file mode 100644 index 97c2709..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeCoreDevice.m +++ /dev/null @@ -1,73 +0,0 @@ -// -// IDeviceJSBridgeCoreDevice.m -// StikJIT -// -// Created by s s on 2025/4/25. -// -@import Foundation; -#import "JSSupport.h" -@implementation IDeviceJSBridge (CoreDevice) - -- (void)core_device_proxy_connectWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - NSError* heartbeatErr = nil; - [JITEnableContext.shared ensureHeartbeatWithError:&heartbeatErr]; - if(heartbeatErr) { - replyHandler(nil, heartbeatErr.localizedDescription); - return; - } - - IdeviceProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; - - CoreDeviceProxyHandle *core_device = NULL; - IdeviceFfiError* err = core_device_proxy_connect(provider, &core_device); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - IdeviceFfiError* err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - IdeviceFfiError* err = core_device_proxy_create_tcp_adapter(core_device, &adapter); - [handles removeObjectForKey:@(clientId)]; - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - int handleId = [self registerIdeviceHandle:adapter freeFunc:(void*)adapter_free]; - replyHandler(@(handleId), nil); -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeDebugProxy.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeDebugProxy.m deleted file mode 100644 index 40f23cf..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeDebugProxy.m +++ /dev/null @@ -1,90 +0,0 @@ -// -// IDeviceJSBridgeDebugProxy.m -// StikJIT -// -// Created by s s on 2025/4/25. -// -@import Foundation; -#import "JSSupport.h" - -@implementation IDeviceJSBridge (DebugProxy) - -- (void)debug_proxy_connect_rsdWithBody:(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; - - int handshakeId = [body[@"handshake"] intValue]; - if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != rsd_handshake_free) { - replyHandler(nil, @"Invalid handshake handle"); - return; - } - IDeviceHandle* handshakeHandleObj = handles[@(handshakeId)]; - RsdHandshakeHandle* handshake = handshakeHandleObj.handle; - - DebugProxyHandle *debug_proxy = NULL; - IdeviceFfiError* err = debug_proxy_connect_rsd(adapter, handshake, &debug_proxy); - [handles removeObjectForKey:@(clientId)]; - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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)]; - DebugProxyHandle* 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; - IdeviceFfiError* err = debug_proxy_send_command(debug_proxy, command, &attach_response); - debugserver_command_free(command); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - NSString* commandResponse = nil; - if(attach_response) { - commandResponse = @(attach_response); - } - idevice_string_free(attach_response); - replyHandler(commandResponse, nil); -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeInstallationProxy.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeInstallationProxy.m deleted file mode 100644 index 389047a..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeInstallationProxy.m +++ /dev/null @@ -1,301 +0,0 @@ -// -// IDeviceJSBridgeInstallationProxy.m -// StikJIT -// -// Created by s s on 2025/4/25. -// -@import Foundation; -@import JavaScriptCore; -#import "JSSupport.h" - -struct InstallationProxyCallbackContext { - int callbackId; - JSContext* context; -}; - -void installationProxyCallback(uint64_t progress, struct InstallationProxyCallbackContext* context) { - [context->context evaluateScript:[NSString stringWithFormat:@"handle_installation_proxy_js_callback(%d, %llu)", context->callbackId, progress]]; -} - -@implementation IDeviceJSBridge (InstallationProxy) - -- (void)installation_proxy_connectWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - NSError* heartbeatErr = nil; - [JITEnableContext.shared ensureHeartbeatWithError:&heartbeatErr]; - if(heartbeatErr) { - replyHandler(nil, heartbeatErr.localizedDescription); - return; - } - - IdeviceProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; - - InstallationProxyClientHandle *client = NULL; - IdeviceFfiError* err = installation_proxy_connect(provider, &client); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - IdeviceFfiError* err = installation_proxy_get_apps(client, [applicationType UTF8String], bundleIdentifiersCharArr, bundleIdentifiersCount, &apps, &apps_len); - if(bundleIdentifiersCharArr){ - free(bundleIdentifiersCharArr); - } - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - plist_t *app_list = (plist_t *)apps; - do { - NSMutableArray* ans = [[NSMutableArray alloc] init]; - for(int i = 0; i < apps_len; ++i) { - NSError* err2 = 0; - NSDictionary* appDict = dictionaryFromPlist(app_list[i], &err2); - - if(err2) { - replyHandler(nil, @"failed to parse plist data"); - break; - } - [ans addObject:appDict]; - } - replyHandler(ans, nil); - } while(0); - - for(int i = 0; i < apps_len; ++i) { - plist_free(app_list[i]); - } -} - -- (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, 0); - } - - int callbackId = [body[@"callback_id"] intValue]; - IdeviceFfiError* err = 0; - if(callbackId == -1) { - err = installation_proxy_install(client, [packagePath UTF8String], optionsPlist); - } else { - struct InstallationProxyCallbackContext context; - context.callbackId = callbackId; - context.context = self->context; - 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, [self errFreeFromIdeviceFfiError: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, 0); - } - - int callbackId = [body[@"callback_id"] intValue]; - IdeviceFfiError* err = 0; - if(callbackId == -1) { - err = installation_proxy_upgrade(client, [packagePath UTF8String], optionsPlist); - } else { - struct InstallationProxyCallbackContext context; - context.callbackId = callbackId; - context.context = self->context; - 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, [self errFreeFromIdeviceFfiError: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, 0); - } - - int callbackId = [body[@"callback_id"] intValue]; - IdeviceFfiError* err = 0; - if(callbackId == -1) { - err = installation_proxy_uninstall(client, [bundleId UTF8String], optionsPlist); - } else { - struct InstallationProxyCallbackContext context; - context.callbackId = callbackId; - context.context = self->context; - 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, [self errFreeFromIdeviceFfiError: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, 0); - } - - plist_t *app_list = NULL; - size_t apps_len = 0; - IdeviceFfiError* err = installation_proxy_browse(client, optionsPlist, &app_list, &apps_len); - - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - do { - NSMutableArray* ans = [[NSMutableArray alloc] init]; - for(int i = 0; i < apps_len; ++i) { - NSError* err2 = 0; - NSDictionary* appDict = dictionaryFromPlist(app_list[i], &err2); - - if(err2) { - replyHandler(nil, @"failed to parse plist data"); - break; - } - [ans addObject:appDict]; - } - replyHandler(ans, nil); - } while(0); - - for(int i = 0; i < apps_len; ++i) { - plist_free(app_list[i]); - } - - -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeLocationSimulation.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeLocationSimulation.m deleted file mode 100644 index 382fc01..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeLocationSimulation.m +++ /dev/null @@ -1,78 +0,0 @@ -// -// IDeviceJSBridgeLocationSimulation.m -// StikJIT -// -// Created by s s on 2025/4/26. -// -@import Foundation; -#import "JSSupport.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)]; - RemoteServerHandle *server = serverHandleObj.handle; - - LocationSimulationHandle *simulation = NULL; - IdeviceFfiError* err = location_simulation_new(server, &simulation); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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)]; - LocationSimulationHandle *simulation = handleObj.handle; - - IdeviceFfiError* err = location_simulation_clear(simulation); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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)]; - LocationSimulationHandle *simulation = handleObj.handle; - - IdeviceFfiError* err = location_simulation_set(simulation, lat.doubleValue, lon.doubleValue); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - replyHandler(@YES, nil); -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeMisagent.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeMisagent.m deleted file mode 100644 index bc94b95..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeMisagent.m +++ /dev/null @@ -1,120 +0,0 @@ -// -// IDeviceJSBridgeMisagent.m -// StikJIT -// -// Created by s s on 2025/4/26. -// -@import Foundation; -#import "JSSupport.h" - -@implementation IDeviceJSBridge (Misagent) - -- (void)misagent_connectWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - NSError* heartbeatErr = nil; - [JITEnableContext.shared ensureHeartbeatWithError:&heartbeatErr]; - if(heartbeatErr) { - replyHandler(nil, heartbeatErr.localizedDescription); - return; - } - - IdeviceProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; - - MisagentClientHandle* client = NULL; - IdeviceFfiError* err = misagent_connect(provider, &client); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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)]; - - IdeviceFfiError* err = misagent_install(client, data.bytes, data.length); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - - IdeviceFfiError* err = misagent_remove(client, [profileId UTF8String]); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - - IdeviceFfiError* err = misagent_copy_all(client, &profiles, &profile_lens, &count); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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/MiniToolSupport/JSSupport/IDeviceJSBridgeNSData+LocalFile.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeNSData+LocalFile.m deleted file mode 100644 index e5742be..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeNSData+LocalFile.m +++ /dev/null @@ -1,191 +0,0 @@ -// -// IDeviceJSBridgeNSData.m -// StikJIT -// -// Created by s s on 2025/4/25. -// -@import Foundation; -#import "JSSupport.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"]; - NSURL *miniToolDataURL = [[NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] - .lastObject URLByAppendingPathComponent:@"MiniToolData"] ; - BOOL isDir = false; - if(![NSFileManager.defaultManager fileExistsAtPath:miniToolDataURL.path isDirectory:&isDir] || !isDir) { - [NSFileManager.defaultManager removeItemAtURL:miniToolDataURL error:nil]; - [NSFileManager.defaultManager createDirectoryAtURL:miniToolDataURL withIntermediateDirectories:YES attributes:@{} error:nil]; - } - NSString* path = [miniToolDataURL.path stringByAppendingPathComponent:relativePath]; - NSString *mode = body[@"mode"]; - - // Prevent path traversal attacks by ensuring the resolved path stays within MiniToolData directory - NSString *resolvedPath = [path stringByStandardizingPath]; - NSString *baseDir = [miniToolDataURL.path stringByStandardizingPath]; - if (![resolvedPath hasPrefix:baseDir]) { - replyHandler(nil, @"Path traversal is not allowed."); - return; - } - - 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/MiniToolSupport/JSSupport/IDeviceJSBridgeProcessControl.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeProcessControl.m deleted file mode 100644 index 2b1a35c..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeProcessControl.m +++ /dev/null @@ -1,125 +0,0 @@ -// -// IDeviceJSBridgeProcessControl.m -// StikJIT -// -// Created by s s on 2025/4/25. -// -@import Foundation; -#import "JSSupport.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)]; - RemoteServerHandle* remote_server = clientHandleObj.handle; - - ProcessControlHandle *process_control = NULL; - IdeviceFfiError* err = process_control_new(remote_server, &process_control); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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)]; - ProcessControlHandle* 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; - IdeviceFfiError* err = process_control_launch_app(process_control, [bundleId UTF8String], envVarsCharArr, envVarsCount, argumentsCharArr, argumentsCount, - startSuspended, killExisting, &pid); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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)]; - ProcessControlHandle* process_control = clientHandleObj.handle; - - uint64_t pid = [body[@"pid"] unsignedLongLongValue]; - if(pid == 0) { - replyHandler(nil, @"Invalid pid"); - return; - } - - IdeviceFfiError* err = process_control_disable_memory_limit(process_control, pid); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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)]; - ProcessControlHandle* process_control = clientHandleObj.handle; - - uint64_t pid = [body[@"pid"] unsignedLongLongValue]; - if(pid == 0) { - replyHandler(nil, @"Invalid pid"); - return; - } - - IdeviceFfiError* err = process_control_kill_app(process_control, pid); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - replyHandler(@(YES), nil); -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeRSD.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeRSD.m deleted file mode 100644 index 1da7668..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeRSD.m +++ /dev/null @@ -1,182 +0,0 @@ -// -// IDeviceJSBridgeRSD.m -// StikDebug -// -// Created by s s on 2025/12/13. -// - -@import Foundation; -#import "JSSupport.h" - -static NSDictionary *RsdServiceToDictionary(const CRsdService *service) { - if (!service) { - return @{}; - } - - NSMutableArray *features = [[NSMutableArray alloc] initWithCapacity:service->features_count]; - for (size_t i = 0; i < service->features_count; ++i) { - if (service->features[i]) { - [features addObject:@(service->features[i])]; - } - } - - NSString *name = service->name ? @(service->name) : @""; - NSString *entitlement = service->entitlement ? @(service->entitlement) : @""; - - return @{ - @"name": name, - @"entitlement": entitlement, - @"port": @(service->port), - @"uses_remote_xpc": @(service->uses_remote_xpc), - @"features_count": @(service->features_count), - @"features": features, - @"service_version": @(service->service_version) - }; -} - -@implementation IDeviceJSBridge (RSD) - -- (void)rsd_handshake_newWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - int socketId = [body[@"socket"] intValue]; - if (!handles[@(socketId)] || handles[@(socketId)].freeFunc != adapter_stream_close) { - replyHandler(nil, @"Invalid socket handle"); - return; - } - IDeviceHandle *socketHandleObj = handles[@(socketId)]; - ReadWriteOpaque *socket = socketHandleObj.handle; - - RsdHandshakeHandle *handshake = NULL; - IdeviceFfiError *err = rsd_handshake_new(socket, &handshake); - [handles removeObjectForKey:@(socketId)]; - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - int handleId = [self registerIdeviceHandle:handshake freeFunc:(void *)rsd_handshake_free]; - replyHandler(@(handleId), nil); -} - -- (void)rsd_get_protocol_versionWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - int handleId = [body[@"handle"] intValue]; - if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != rsd_handshake_free) { - replyHandler(nil, @"Invalid RSD handshake handle"); - return; - } - RsdHandshakeHandle *handshake = handles[@(handleId)].handle; - - size_t version = 0; - IdeviceFfiError *err = rsd_get_protocol_version(handshake, &version); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - replyHandler(@(version), nil); -} - -- (void)rsd_get_uuidWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - int handleId = [body[@"handle"] intValue]; - if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != rsd_handshake_free) { - replyHandler(nil, @"Invalid RSD handshake handle"); - return; - } - RsdHandshakeHandle *handshake = handles[@(handleId)].handle; - - char *uuid = NULL; - IdeviceFfiError *err = rsd_get_uuid(handshake, &uuid); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - NSString *uuidStr = uuid ? @(uuid) : nil; - if (uuid) { - rsd_free_string(uuid); - } - - replyHandler(uuidStr, nil); -} - -- (void)rsd_get_servicesWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - int handleId = [body[@"handle"] intValue]; - if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != rsd_handshake_free) { - replyHandler(nil, @"Invalid RSD handshake handle"); - return; - } - RsdHandshakeHandle *handshake = handles[@(handleId)].handle; - - CRsdServiceArray *services = NULL; - IdeviceFfiError *err = rsd_get_services(handshake, &services); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - NSMutableArray *ans = [[NSMutableArray alloc] init]; - if (services && services->services) { - for (size_t i = 0; i < services->count; ++i) { - [ans addObject:RsdServiceToDictionary(&services->services[i])]; - } - } - - if (services) { - rsd_free_services(services); - } - replyHandler(ans, nil); -} - -- (void)rsd_service_availableWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - int handleId = [body[@"handle"] intValue]; - if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != rsd_handshake_free) { - replyHandler(nil, @"Invalid RSD handshake handle"); - return; - } - RsdHandshakeHandle *handshake = handles[@(handleId)].handle; - - NSString *serviceName = body[@"service_name"]; - if (![serviceName isKindOfClass:NSString.class]) { - replyHandler(nil, @"Invalid service name"); - return; - } - - bool available = false; - IdeviceFfiError *err = rsd_service_available(handshake, [serviceName UTF8String], &available); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - replyHandler(@(available), nil); -} - -- (void)rsd_get_service_infoWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - int handleId = [body[@"handle"] intValue]; - if (!handles[@(handleId)] || handles[@(handleId)].freeFunc != rsd_handshake_free) { - replyHandler(nil, @"Invalid RSD handshake handle"); - return; - } - RsdHandshakeHandle *handshake = handles[@(handleId)].handle; - - NSString *serviceName = body[@"service_name"]; - if (![serviceName isKindOfClass:NSString.class]) { - replyHandler(nil, @"Invalid service name"); - return; - } - - CRsdService *serviceInfo = NULL; - IdeviceFfiError *err = rsd_get_service_info(handshake, [serviceName UTF8String], &serviceInfo); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - NSDictionary *ans = RsdServiceToDictionary(serviceInfo); - if (serviceInfo) { - rsd_free_service(serviceInfo); - } - - replyHandler(ans, nil); -} - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeRemoteServer.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeRemoteServer.m deleted file mode 100644 index eeba3bc..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeRemoteServer.m +++ /dev/null @@ -1,43 +0,0 @@ -// -// IDeviceJSBridgeRemoteServer.m -// StikJIT -// -// Created by s s on 2025/4/25. -// -@import Foundation; -#import "JSSupport.h" - -@implementation IDeviceJSBridge (RemoteServer) - -- (void)remote_server_connect_rsdWithBody:(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; - - int handshakeId = [body[@"handshake"] intValue]; - if(!handles[@(clientId)] || handles[@(clientId)].freeFunc != rsd_handshake_free) { - replyHandler(nil, @"Invalid handshake handle"); - return; - } - IDeviceHandle* handshakeHandleObj = handles[@(handshakeId)]; - RsdHandshakeHandle* handshake = handshakeHandleObj.handle; - - RemoteServerHandle *remote_server = NULL; - IdeviceFfiError* err = remote_server_connect_rsd(adapter, handshake, &remote_server); - [handles removeObjectForKey:@(clientId)]; - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError:err]); - return; - } - - int handleId = [self registerIdeviceHandle:remote_server freeFunc:(void*)remote_server_free]; - replyHandler(@(handleId), nil); -} - - -@end diff --git a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeSBServices.m b/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeSBServices.m deleted file mode 100644 index 4896b39..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridgeSBServices.m +++ /dev/null @@ -1,67 +0,0 @@ -// -// IDeviceJSBridgeSBServices.m -// StikJIT -// -// Created by s s on 2025/4/25. -// - -@import Foundation; -#import "JSSupport.h" - -@implementation IDeviceJSBridge (SBServices) - -- (void)springboard_services_connectWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler { - NSError* heartbeatErr = nil; - [JITEnableContext.shared ensureHeartbeatWithError:&heartbeatErr]; - if(heartbeatErr) { - replyHandler(nil, heartbeatErr.localizedDescription); - return; - } - - IdeviceProviderHandle* provider = [JITEnableContext.shared getTcpProviderHandle]; - - SpringBoardServicesClientHandle *sb_services = NULL; - IdeviceFfiError* err = springboard_services_connect(provider, &sb_services); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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; - IdeviceFfiError* err = springboard_services_get_icon(client, [bundleID UTF8String], &pngData, &data_len); - if (err) { - replyHandler(nil, [self errFreeFromIdeviceFfiError: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/MiniToolSupport/JSSupport/JSSupport.h b/StikJIT/MiniToolSupport/JSSupport/JSSupport.h deleted file mode 100644 index f538fdd..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/JSSupport.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// JSSupport.h -// StikJIT -// -// Created by s s on 2025/4/24. -// -#import "../../idevice/JITEnableContext.h" -#import "../../idevice/idevice.h" -@import JavaScriptCore; - -@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; - NSArray* allowedFunctions; - JSContext* context; -} -- (instancetype)initWithContext:(JSContext*)context allowedFunctions:(NSArray*)allowedFunctions; -- (void)didReceiveScriptMessage:(NSDictionary *)message resolve:(JSValue*)resolveFunc reject:(JSValue*)rejectFunc; -- (void)cleanUp; -- (NSString*)errFreeFromIdeviceFfiError:(IdeviceFfiError*)err; -- (int)registerIdeviceHandle:(void*)handle freeFunc:(void*)freeFunc; -- (BOOL)freeIdeviceHandle:(int)handleId; -- (int)registerNSData:(NSData*)data; -- (bool)freeNSData:(int)handleId; -@end - - -NSDictionary *dictionaryFromPlist(plist_t plist, NSError **error); -NSData *plistDataFromDictionary(NSDictionary *dictionary, NSError **error); -const char** cstrArrFromNSArray(NSArray* arr, int* validCount); diff --git a/StikJIT/MiniToolSupport/JSSupport/NSInvocation.m b/StikJIT/MiniToolSupport/JSSupport/NSInvocation.m deleted file mode 100644 index 981b9fa..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/NSInvocation.m +++ /dev/null @@ -1,44 +0,0 @@ -// -// 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/MiniToolSupport/JSSupport/idevice.js b/StikJIT/MiniToolSupport/JSSupport/idevice.js deleted file mode 100644 index e627b26..0000000 --- a/StikJIT/MiniToolSupport/JSSupport/idevice.js +++ /dev/null @@ -1,515 +0,0 @@ -// -// idevice.js -// StikJIT -// -// Created by s s on 2025/4/24. -// - -async function core_device_proxy_connect() { - return await __postIdeviceMessage({ - "command": "core_device_proxy_connect" - }); -} - -async function core_device_proxy_get_server_rsd_port(core_device_handle) { - return await __postIdeviceMessage({ - "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 __postIdeviceMessage({ - "command": "core_device_proxy_create_tcp_adapter", - "handle": core_device_handle - }); -} - -async function adapter_connect(adapter, port) { - return await __postIdeviceMessage({ - "command": "adapter_connect", - "adapter": adapter, - "port": port - }); -} - -async function rsd_handshake_new(socket) { - return await __postIdeviceMessage({ - "command": "rsd_handshake_new", - "socket": socket - }); -} - -async function rsd_get_protocol_version(handle) { - return await __postIdeviceMessage({ - "command": "rsd_get_protocol_version", - "handle": handle - }); -} - -async function rsd_get_uuid(handle) { - return await __postIdeviceMessage({ - "command": "rsd_get_uuid", - "handle": handle - }); -} - -async function rsd_get_services(handle) { - return await __postIdeviceMessage({ - "command": "rsd_get_services", - "handle": handle - }); -} - -async function rsd_service_available(handle, service_name) { - return await __postIdeviceMessage({ - "command": "rsd_service_available", - "handle": handle, - "service_name": service_name - }); -} - -async function rsd_get_service_info(handle, service_name) { - return await __postIdeviceMessage({ - "command": "rsd_get_service_info", - "handle": handle, - "service_name": service_name - }); -} - -async function remote_server_connect_rsd(adapter, handshake) { - return await __postIdeviceMessage({ - "command": "remote_server_connect_rsd", - "adapter": adapter, - "handshake": handshake - }); -} - -async function process_control_new(server) { - return await __postIdeviceMessage({ - "command": "process_control_new", - "server": server - }); -} - -async function process_control_launch_app(handle, bundle_id, env_vars, arguments, start_suspended, kill_existing) { - return await __postIdeviceMessage({ - "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 __postIdeviceMessage({ - "command": "process_control_disable_memory_limit", - "handle": handle, - "pid": pid - }); -} - -async function process_control_kill_app(handle, pid) { - return await __postIdeviceMessage({ - "command": "process_control_kill_app", - "handle": handle, - "pid": pid - }); -} - -async function debug_proxy_connect_rsd(adapter, handshake) { - return await __postIdeviceMessage({ - "command": "debug_proxy_connect_rsd", - "adapter": adapter, - "handshake": handshake - }); -} - -async function debug_proxy_send_command(handle, command) { - return await __postIdeviceMessage({ - "command": "debug_proxy_send_command", - "handle": handle, - "debug_command": command - }); -} - -async function springboard_services_connect() { - return await __postIdeviceMessage({ - "command": "springboard_services_connect" - }); -} - -async function springboard_services_get_icon(client, bundle_id) { - return await __postIdeviceMessage({ - "command": "springboard_services_get_icon", - "client": client, - "bundle_id": bundle_id - }); -} - -async function nsdata_read(handle) { - return await __postIdeviceMessage({ - "command": "nsdata_read", - "handle": handle, - }); -} - -async function nsdata_read_range(handle, begin, end) { - return await __postIdeviceMessage({ - "command": "nsdata_read_range", - "handle": handle, - "begin": begin, - "end": end - }); -} - -async function afc_client_connect() { - return await __postIdeviceMessage({ - "command": "afc_client_connect" - }); -} - -async function afc_list_directory(handle, path) { - return await __postIdeviceMessage({ - "command": "afc_list_directory", - "handle": handle, - "path": path - }); -} - -async function afc_make_directory(handle, path) { - return await __postIdeviceMessage({ - "command": "afc_make_directory", - "handle": handle, - "path": path - }); -} - -async function afc_remove_path(handle, path) { - return await __postIdeviceMessage({ - "command": "afc_remove_path", - "handle": handle, - "path": path - }); -} - -async function afc_remove_path_and_contents(handle, path) { - return await __postIdeviceMessage({ - "command": "afc_remove_path_and_contents", - "handle": handle, - "path": path - }); -} - -async function afc_rename_path(handle, source, target) { - return await __postIdeviceMessage({ - "command": "afc_rename_path", - "handle": handle, - "source": source, - "target": target - }); -} - -async function afc_get_file_info(handle, path) { - return await __postIdeviceMessage({ - "command": "afc_get_file_info", - "handle": handle, - "path": path - }); -} - -async function afc_get_device_info(handle) { - return await __postIdeviceMessage({ - "command": "afc_get_device_info", - "handle": handle, - }); -} - -async function afc_file_open(handle, path, mode) { - return await __postIdeviceMessage({ - "command": "afc_file_open", - "handle": handle, - "path": path, - "mode": mode - }); -} - -async function afc_file_close(handle) { - return await __postIdeviceMessage({ - "command": "afc_file_close", - "handle": handle - }); -} - -async function afc_file_read(handle) { - return await __postIdeviceMessage({ - "command": "afc_file_read", - "handle": handle - }); -} - -async function afc_file_write(handle, data_handle) { - return await __postIdeviceMessage({ - "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() { - return await __postIdeviceMessage({ - "command": "installation_proxy_connect" - }); -} - -async function installation_proxy_get_apps(client, applicationType, bundleIdentifiers) { - return await __postIdeviceMessage({ - "command": "installation_proxy_get_apps", - "client": client, - "application_type": applicationType, - "bundle_identifiers": bundleIdentifiers - }); -} - -async function installation_proxy_browse(client, options) { - return await __postIdeviceMessage({ - "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 __postIdeviceMessage({ - "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 __postIdeviceMessage({ - "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 __postIdeviceMessage({ - "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 __postIdeviceMessage({ - "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 __postIdeviceMessage({ - "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 __postIdeviceMessage({ - "command": "installation_proxy_install", - "client": client, - "bundle_id": bundle_id, - "options": options, - "callback_id": -1 - }); - } -} - -async function amfi_connect() { - return await __postIdeviceMessage({ - "command": "amfi_connect" - }); -} - -async function amfi_reveal_developer_mode_option_in_ui(handle) { - return await __postIdeviceMessage({ - "command": "amfi_reveal_developer_mode_option_in_ui", - "handle": handle - }); -} - -async function amfi_enable_developer_mode(handle) { - return await __postIdeviceMessage({ - "command": "amfi_enable_developer_mode", - "handle": handle - }); -} - -async function amfi_accept_developer_mode(handle) { - return await __postIdeviceMessage({ - "command": "amfi_accept_developer_mode", - "handle": handle - }); -} - -async function misagent_connect() { - return await __postIdeviceMessage({ - "command": "misagent_connect" - }); -} - -async function misagent_install(handle, data_handle) { - return await __postIdeviceMessage({ - "command": "misagent_install", - "handle": handle, - "data_handle": data_handle - }); -} - -async function misagent_remove(handle, profile_id) { - return await __postIdeviceMessage({ - "command": "misagent_remove", - "handle": handle, - "profile_id": profile_id - }); -} - -async function misagent_copy_all(handle) { - return await __postIdeviceMessage({ - "command": "misagent_copy_all", - "handle": handle, - }); -} - -async function location_simulation_new(server) { - return await __postIdeviceMessage({ - "command": "location_simulation_new", - "server": server - }); -} - -async function location_simulation_clear(handle) { - return await __postIdeviceMessage({ - "command": "location_simulation_clear", - "handle": handle - }); -} - -async function location_simulation_set(handle, latitude, longitude) { - return await __postIdeviceMessage({ - "command": "location_simulation_set", - "handle": handle, - "latitude": latitude, - "longitude": longitude, - }); -} - -// data related, not idevice - -async function nsdata_get_size(handle) { - return await __postIdeviceMessage({ - "command": "nsdata_get_size", - "handle": handle, - }); -} - -async function nsdata_free(handle) { - return await __postIdeviceMessage({ - "command": "nsdata_free", - "handle": handle, - }); -} - -async function nsdata_create(base64Data) { - return await __postIdeviceMessage({ - "command": "nsdata_create", - "base64Data": base64Data, - }); -} - -async function local_file_open(path, mode) { - return await __postIdeviceMessage({ - "command": "local_file_open", - "path": path, - "mode": mode - }); -} - -async function local_file_close(file) { - return await __postIdeviceMessage({ - "command": "local_file_close", - "file": file, - }); -} - -async function local_file_get_size(file) { - return await __postIdeviceMessage({ - "command": "local_file_get_size", - "file": file, - }); -} - -async function local_file_read_chunk(file, offset, length) { - return await __postIdeviceMessage({ - "command": "local_file_read_chunk", - "file": file, - "offset": offset, - "length": length - }); -} - -async function local_file_write_chunk(file, data, offset) { - return await __postIdeviceMessage({ - "command": "local_file_write_chunk", - "file": file, - "offset": offset, - "data": data - }); -} diff --git a/StikJIT/MiniToolSupport/MiniToolEditorView.swift b/StikJIT/MiniToolSupport/MiniToolEditorView.swift deleted file mode 100644 index 0f0391d..0000000 --- a/StikJIT/MiniToolSupport/MiniToolEditorView.swift +++ /dev/null @@ -1,97 +0,0 @@ -import SwiftUI -import CodeEditorView -import LanguageSupport - -struct MiniToolEditorView: View { - let tool: MiniToolBundle - - private enum Target: String, CaseIterable, Identifiable { - case indexHTML = "index.html" - case backgroundJS = "background.js" - - var id: String { rawValue } - var displayName: String { - switch self { - case .indexHTML: return "index.html" - case .backgroundJS: return "background.js" - } - } - } - - @State private var htmlContent: String = "" - @State private var backgroundContent: String = "" - @State private var selectedTarget: Target = .indexHTML - @State private var position: CodeEditor.Position = .init() - @State private var messages: Set> = [] - - @AppStorage("appTheme") private var appThemeRaw: String = AppTheme.system.rawValue - @Environment(\.colorScheme) private var colorScheme - @Environment(\.dismiss) private var dismiss - @Environment(\.themeExpansionManager) private var themeExpansion - - private var backgroundStyle: BackgroundStyle { themeExpansion?.backgroundStyle(for: appThemeRaw) ?? AppTheme.system.backgroundStyle } - private var preferredScheme: ColorScheme? { themeExpansion?.preferredColorScheme(for: appThemeRaw) } - private var editorTheme: Theme { - colorScheme == .dark ? Theme.defaultDark : Theme.defaultLight - } - - var body: some View { - ZStack { - ThemedBackground(style: backgroundStyle) - .ignoresSafeArea() - - VStack(spacing: 12) { - Picker("File", selection: $selectedTarget) { - ForEach(Target.allCases) { target in - Text(target.displayName).tag(target) - } - } - .pickerStyle(.segmented) - - CodeEditor( - text: binding(for: selectedTarget), - position: $position, - messages: $messages, - language: .swift() - ) - .font(.system(.footnote, design: .monospaced)) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .environment(\.codeEditorTheme, editorTheme) - } - .padding(.horizontal, 16) - .padding(.top, 12) - .padding(.bottom, 16) - } - .navigationTitle(tool.name) - .navigationBarTitleDisplayMode(.inline) - .onAppear(perform: load) - .toolbar { - ToolbarItem(placement: .confirmationAction) { - Button("Save") { - save() - } - } - } - .preferredColorScheme(preferredScheme) - .tint(Color.white) - .toolbar(.hidden, for: .tabBar) - } - - private func binding(for target: Target) -> Binding { - switch target { - case .indexHTML: return $htmlContent - case .backgroundJS: return $backgroundContent - } - } - - private func load() { - htmlContent = (try? String(contentsOf: tool.indexURL)) ?? "" - backgroundContent = (try? String(contentsOf: tool.backgroundURL)) ?? "" - } - - private func save() { - try? htmlContent.write(to: tool.indexURL, atomically: true, encoding: .utf8) - try? backgroundContent.write(to: tool.backgroundURL, atomically: true, encoding: .utf8) - dismiss() - } -} diff --git a/StikJIT/MiniToolSupport/MiniToolListView.swift b/StikJIT/MiniToolSupport/MiniToolListView.swift deleted file mode 100644 index 80d9746..0000000 --- a/StikJIT/MiniToolSupport/MiniToolListView.swift +++ /dev/null @@ -1,440 +0,0 @@ -import SwiftUI -import UniformTypeIdentifiers -import UIKit - -class ToolCombo : Hashable, ObservableObject { - static func == (lhs: ToolCombo, rhs: ToolCombo) -> Bool { - return lhs === rhs - } - func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - - init(tool: MiniToolBundle, info: ToolInfo) { - self.tool = tool - self.info = info - } - - init() { - self.tool = nil - self.info = nil - } - - @Published var tool: MiniToolBundle? - @Published var info: ToolInfo? -} - -struct MiniToolListView: View { - @StateObject private var store = MiniToolStore() - @State private var searchText = "" - @State private var showImporter = false - @State private var showDeleteConfirmation = false - @State private var pendingDelete: MiniToolBundle? - @State private var alertVisible = false - @State private var alertTitle = "" - @State private var alertMessage = "" - @State private var showInfoSheet = false - @State private var navigationToolCombo: ToolCombo? - @StateObject private var pendingToolCombo: ToolCombo = ToolCombo() - @AppStorage("appTheme") private var appThemeRaw: String = AppTheme.system.rawValue - @Environment(\.themeExpansionManager) private var themeExpansion - @Environment(\.colorScheme) private var colorScheme - - private var backgroundStyle: BackgroundStyle { themeExpansion?.backgroundStyle(for: appThemeRaw) ?? AppTheme.system.backgroundStyle } - private var preferredScheme: ColorScheme? { themeExpansion?.preferredColorScheme(for: appThemeRaw) } - - private var filteredTools: [MiniToolBundle] { - guard !searchText.isEmpty else { return store.tools } - return store.tools.filter { $0.name.localizedCaseInsensitiveContains(searchText) } - } - - var body: some View { - NavigationStack { - ZStack { - ThemedBackground(style: backgroundStyle) - .ignoresSafeArea() - - ScrollView { - VStack(spacing: 20) { - headerCard - - if filteredTools.isEmpty { - emptyCard - } else { - ForEach(filteredTools) { tool in - toolRow(tool) - } - } - } - .padding(.horizontal, 20) - .padding(.vertical, 30) - } - - if store.isBusy { - Color.black.opacity(0.35).ignoresSafeArea() - ProgressView("Working…") - .padding(16) - .background( - RoundedRectangle(cornerRadius: 16, style: .continuous) - .fill(.ultraThinMaterial) - .overlay( - RoundedRectangle(cornerRadius: 16, style: .continuous) - .strokeBorder(Color.white.opacity(0.15), lineWidth: 1) - ) - ) - .shadow(color: .black.opacity(0.15), radius: 12, x: 0, y: 4) - } - - if alertVisible { - CustomErrorView( - title: alertTitle, - message: alertMessage, - onDismiss: { alertVisible = false }, - messageType: .error - ) - } - } - .navigationTitle("Mini Tools") - .toolbar { - ToolbarItemGroup(placement: .navigationBarTrailing) { - Button { showImporter = true } label: { - Label("Import", systemImage: "tray.and.arrow.down") - } - } - } - .onAppear { store.refresh() } - .onChange(of: store.lastError) { _, message in - guard let message else { return } - presentError(title: "Mini Tool", message: message) - store.lastError = nil - } - .alert("Delete Mini Tool?", isPresented: $showDeleteConfirmation, presenting: pendingDelete) { tool in - Button("Delete", role: .destructive) { delete(tool) } - Button("Cancel", role: .cancel) { pendingDelete = nil } - } message: { tool in - Text("Delete \(tool.name)? This removes its files permanently.") - } - .fileImporter( - isPresented: $showImporter, - allowedContentTypes: [UTType("com.stik.StikJIT.stiktool") ?? .data], - allowsMultipleSelection: false - ) { result in - switch result { - case .success(let urls): - guard let url = urls.first else { return } - store.importTool(from: url) - case .failure(let error): - presentError(title: "Import Failed", message: error.localizedDescription) - } - } - .navigationDestination(item: $navigationToolCombo) { combo in - MiniToolRunnerView(tool: combo.tool!, toolInfo: combo.info!) - } - } - .preferredColorScheme(preferredScheme) - .sheet(isPresented: $showInfoSheet) { - toolInfoSheet - } - } - - // MARK: - Cards - - private var headerCard: some View { - VStack(spacing: 12) { - TextField("Search tools…", text: $searchText) - .padding(12) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) - .overlay( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .strokeBorder(Color.white.opacity(0.12), lineWidth: 1) - ) - - HStack(spacing: 12) { - WideGlassyButton(title: "Import", systemImage: "tray.and.arrow.down") { - showImporter = true - } - } - } - .padding(20) - .background(glassyBackground) - } - - private func toolRow(_ tool: MiniToolBundle) -> some View { - HStack(spacing: 12) { - Button { - handleRun(tool) - } label: { - HStack(spacing: 12) { - Image(systemName: "shippingbox.fill") - .foregroundColor(.blue) - .imageScale(.large) - VStack(alignment: .leading, spacing: 4) { - Text(tool.name) - .font(.body.weight(.medium)) - .lineLimit(1) - Text(tool.url.lastPathComponent) - .font(.caption) - .foregroundColor(.secondary) - .lineLimit(1) - } - Spacer() - } - } - .buttonStyle(.plain) - - NavigationLink { - MiniToolEditorView(tool: tool) - } label: { - Image(systemName: "pencil") - .foregroundColor(.primary) - } - .buttonStyle(.borderless) - - Button(role: .destructive) { - pendingDelete = tool - showDeleteConfirmation = true - } label: { - Image(systemName: "trash") - .foregroundColor(.red) - } - .buttonStyle(.borderless) - - } - .padding(20) - .background(glassyBackground) - .contextMenu { - Button { copy(tool.url.lastPathComponent) } label: { - Label("Copy Name", systemImage: "doc.on.doc") - } - Button { copy(tool.url.path) } label: { - Label("Copy Path", systemImage: "folder") - } - } - } - - private var emptyCard: some View { - VStack(spacing: 6) { - Label("No mini tools found", systemImage: "shippingbox") - .font(.subheadline.weight(.semibold)) - Text("Tap New or Import to add a tool.") - .font(.footnote) - .foregroundColor(.secondary) - } - .padding(40) - .frame(maxWidth: .infinity) - .background(glassyBackground) - } - - private var glassyBackground: some View { - ZStack { - RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(.ultraThinMaterial) - RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill( - LinearGradient( - gradient: Gradient(colors: overlayColors()), - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .opacity(0.32) - RoundedRectangle(cornerRadius: 20, style: .continuous) - .strokeBorder(Color.white.opacity(0.15), lineWidth: 1) - } - .shadow(color: .black.opacity(0.15), radius: 12, x: 0, y: 4) - } - - private func overlayColors() -> [Color] { - let colors: [Color] - switch backgroundStyle { - case .staticGradient(let palette): - colors = palette - case .animatedGradient(let palette, _): - colors = palette - case .blobs(_, let background): - colors = background - case .particles(let particle, let background): - colors = background.isEmpty ? [particle, particle.opacity(0.4)] : background - case .customGradient(let palette): - colors = palette - case .adaptiveGradient(let light, let dark): - colors = colorScheme == .dark ? dark : light - } - if colors.count >= 2 { return colors } - if let first = colors.first { return [first, first.opacity(0.6)] } - return [Color.blue, Color.purple] - } - - // MARK: - Actions - - private func delete(_ tool: MiniToolBundle) { - store.delete(tool) - if let error = store.lastError { - presentError(title: "Delete Failed", message: error) - } - } - - private func presentError(title: String, message: String) { - alertTitle = title - alertMessage = message - alertVisible = true - } - - private func copy(_ text: String) { - UIPasteboard.general.string = text - } - - private func handleRun(_ tool: MiniToolBundle) { - do { - guard let info = try loadToolInfo(for: tool) else { - presentError(title: "Mini Tool", message: "toolInfo.json is missing or unreadable for this tool.") - return - } - if try isTrusted(tool) { - navigationToolCombo = ToolCombo(tool: tool, info: info) - return - } - - pendingToolCombo.info = info - pendingToolCombo.tool = tool - print("set pendingToolInfo") - showInfoSheet = true - print("pendingToolInfo = true") - } catch { - presentError(title: "Mini Tool", message: error.localizedDescription) - } - } - - private func loadToolInfo(for tool: MiniToolBundle) throws -> ToolInfo? { - let infoURL = tool.url.appendingPathComponent("toolInfo.json") - guard FileManager.default.fileExists(atPath: infoURL.path) else { return nil } - let data = try Data(contentsOf: infoURL) - return try JSONDecoder().decode(ToolInfo.self, from: data) - } - - private func isTrusted(_ tool: MiniToolBundle) throws -> Bool { - guard let uuid = UIDevice.current.identifierForVendor?.uuidString else { return false } - let checkURL = tool.url.appendingPathComponent("check") - guard FileManager.default.fileExists(atPath: checkURL.path) else { return false } - let contents = try String(contentsOf: checkURL, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines) - return contents == uuid - } - - private func authorizeAndRun() { - guard let tool = pendingToolCombo.tool else { - return - } - - do { - try writeCheck(for: tool) - navigationToolCombo = pendingToolCombo - } catch { - presentError(title: "Mini Tool", message: error.localizedDescription) - } - resetPending() - } - - private func writeCheck(for tool: MiniToolBundle) throws { - guard let uuid = UIDevice.current.identifierForVendor?.uuidString else { - throw NSError(domain: "MiniTool", code: -1, userInfo: [NSLocalizedDescriptionKey: "identifierForVendor is unavailable."]) - } - let checkURL = tool.url.appendingPathComponent("check") - try uuid.write(to: checkURL, atomically: true, encoding: .utf8) - } - - private func resetPending() { - showInfoSheet = false -// pendingTool = MiniToolBundle() - } - - @ViewBuilder - private var toolInfoSheet: some View { - if let tool = pendingToolCombo.tool, let info = pendingToolCombo.info { - ToolInfoSheet(tool: tool, info: info) { - authorizeAndRun() - } onCancel: { - resetPending() - } - } - } -} - -private struct ToolInfoSheet: View { - let tool: MiniToolBundle - let info: ToolInfo - let onAllow: () -> Void - let onCancel: () -> Void - - var body: some View { - NavigationStack { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - VStack(alignment: .leading, spacing: 4) { - Text(tool.name) - .font(.title2.weight(.semibold)) - Text(info.desc) - .font(.body) - .foregroundColor(.secondary) - } - - VStack(alignment: .leading, spacing: 8) { - Label("Author", systemImage: "person") - .font(.subheadline.weight(.semibold)) - Text(info.author) - .font(.body) - } - - if let capabilities = info.capabilities { - VStack(alignment: .leading, spacing: 8) { - Label("Capabilities", systemImage: "checkmark.shield") - .font(.subheadline.weight(.semibold)) - CapabilityRow(title: "Internet Access", enabled: capabilities.internetAccess ?? false) - } - } - - if let functions = info.requiredIDeviceFunctions, !functions.isEmpty { - VStack(alignment: .leading, spacing: 8) { - Label("Required iDevice Functions", systemImage: "bolt.horizontal.circle") - .font(.subheadline.weight(.semibold)) - ForEach(functions, id: \.self) { function in - HStack(spacing: 8) { - Image(systemName: "bolt.fill") - .foregroundColor(.blue) - Text(function) - .font(.body) - } - } - } - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(20) - } - .navigationTitle("Allow Tool?") - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("Cancel") { onCancel() } - } - ToolbarItem(placement: .confirmationAction) { - Button("Allow & Run") { onAllow() } - .bold() - } - } - } - } -} - -private struct CapabilityRow: View { - let title: String - let enabled: Bool - - var body: some View { - HStack(spacing: 8) { - Image(systemName: enabled ? "checkmark.circle.fill" : "xmark.circle") - .foregroundColor(enabled ? .green : .red) - Text(title) - .font(.body) - } - } -} - diff --git a/StikJIT/MiniToolSupport/MiniToolModels.swift b/StikJIT/MiniToolSupport/MiniToolModels.swift deleted file mode 100644 index 59b03a5..0000000 --- a/StikJIT/MiniToolSupport/MiniToolModels.swift +++ /dev/null @@ -1,144 +0,0 @@ -import Foundation - -struct ToolInfo: Decodable { - struct Capabilities: Decodable { - let internetAccess: Bool? - - enum CodingKeys: String, CodingKey { - case internetAccess = "internet_access" - } - } - - let author: String - let desc: String - let capabilities: Capabilities? - let requiredIDeviceFunctions: [String]? - - - - enum CodingKeys: String, CodingKey { - case author - case desc - case capabilities - case requiredIDeviceFunctions = "required_idevice_functions" - } -} - - -class MiniToolBundle: ObservableObject, Identifiable { - static func == (lhs: MiniToolBundle, rhs: MiniToolBundle) -> Bool { - return lhs.name == rhs.name - } - - @Published var url: URL - - init(url: URL) { - self.url = url - } - - var id: String { url.lastPathComponent } - var name: String { url.deletingPathExtension().lastPathComponent } - var indexURL: URL { url.appendingPathComponent("index.html") } - var backgroundURL: URL { url.appendingPathComponent("background.js") } - - var isValid: Bool { - let fm = FileManager.default - return fm.fileExists(atPath: indexURL.path) && fm.fileExists(atPath: backgroundURL.path) - } - - func getHostName() -> String { - let d = name.data(using: .utf8) - let b = d!.base64EncodedString().replacingOccurrences(of:"+", with:"").replacingOccurrences(of:"/", with:"").replacingOccurrences(of:"=", with:"") - - return "\(b).stiktool" - } -} - -final class MiniToolStore: ObservableObject { - @Published private(set) var tools: [MiniToolBundle] = [] - @Published var isBusy: Bool = false - @Published var lastError: String? - - func refresh() { - let directory = toolsDirectory() - let contents = (try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil)) ?? [] - let bundles = contents - .filter { url in - let ext = url.pathExtension.lowercased() - return ext == "stiktool" || url.hasDirectoryPath - } - .map { MiniToolBundle(url: $0) } - .filter { $0.isValid } - .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } - DispatchQueue.main.async { - self.tools = bundles - } - } - - func delete(_ tool: MiniToolBundle) { - do { - try FileManager.default.removeItem(at: tool.url) - refresh() - } catch { - lastError = error.localizedDescription - } - } - - func importTool(from externalURL: URL) { - isBusy = true - DispatchQueue.global(qos: .userInitiated).async { - defer { DispatchQueue.main.async { self.isBusy = false } } - do { - let accessing = externalURL.startAccessingSecurityScopedResource() - defer { if accessing { externalURL.stopAccessingSecurityScopedResource() } } - let destination = self.toolsDirectory().appendingPathComponent(externalURL.lastPathComponent) - let fm = FileManager.default - if fm.fileExists(atPath: destination.path) { - try fm.removeItem(at: destination) - } - try fm.copyItem(at: externalURL, to: destination) - DispatchQueue.main.async { self.refresh() } - } catch { - DispatchQueue.main.async { self.lastError = error.localizedDescription } - } - } - } - - func toolsDirectory() -> URL { - let dir = URL.documentsDirectory.appendingPathComponent("tools", isDirectory: true) - var isDir: ObjCBool = false - let fm = FileManager.default - if fm.fileExists(atPath: dir.path, isDirectory: &isDir) { - if !isDir.boolValue { - try? fm.removeItem(at: dir) - } - } - if !fm.fileExists(atPath: dir.path) { - try? fm.createDirectory(at: dir, withIntermediateDirectories: true) - } - return dir - } - - static func toolsDataDirectory() -> URL { - let dir = URL.documentsDirectory.appendingPathComponent("MiniToolData", isDirectory: true) - var isDir: ObjCBool = false - let fm = FileManager.default - if fm.fileExists(atPath: dir.path, isDirectory: &isDir) { - if !isDir.boolValue { - try? fm.removeItem(at: dir) - } - } - if !fm.fileExists(atPath: dir.path) { - try? fm.createDirectory(at: dir, withIntermediateDirectories: true) - } - return dir - } - - private func sanitizedToolName(_ raw: String) -> String { - let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) - let allowed = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-_")) - let filtered = trimmed.unicodeScalars.map { allowed.contains($0) ? Character($0) : "_" } - let name = String(filtered).replacingOccurrences(of: "__+", with: "_", options: .regularExpression) - return name.isEmpty ? "Untitled" : name - } -} diff --git a/StikJIT/MiniToolSupport/MiniToolRunnerView.swift b/StikJIT/MiniToolSupport/MiniToolRunnerView.swift deleted file mode 100644 index 674f851..0000000 --- a/StikJIT/MiniToolSupport/MiniToolRunnerView.swift +++ /dev/null @@ -1,111 +0,0 @@ -import SwiftUI -import WebKit - -struct MiniToolRunnerView: View { - let tool: MiniToolBundle - let toolInfo: ToolInfo - @StateObject private var runtime: MiniToolRuntime - @State private var showLogs = false - @State private var initiated = false - - @AppStorage("appTheme") private var appThemeRaw: String = AppTheme.system.rawValue - @Environment(\.themeExpansionManager) private var themeExpansion - - private var backgroundStyle: BackgroundStyle { themeExpansion?.backgroundStyle(for: appThemeRaw) ?? AppTheme.system.backgroundStyle } - private var preferredScheme: ColorScheme? { themeExpansion?.preferredColorScheme(for: appThemeRaw) } - - init(tool: MiniToolBundle, toolInfo: ToolInfo) { - self.tool = tool - self.toolInfo = toolInfo - _runtime = StateObject(wrappedValue: MiniToolRuntime(tool: tool, toolInfo: toolInfo)) - } - - var body: some View { - ZStack { - ThemedBackground(style: backgroundStyle) - .ignoresSafeArea() - - VStack(spacing: 12) { - MiniToolWebContainer(runtime: runtime) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) - .overlay( - RoundedRectangle(cornerRadius: 16, style: .continuous) - .strokeBorder(Color.white.opacity(0.12), lineWidth: 1) - ) - .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) - - if !runtime.logs.isEmpty && showLogs { - logList - } - } - .padding(16) - } - .navigationTitle(tool.name) - .navigationBarTitleDisplayMode(.inline) - .onAppear { - if !initiated { - runtime.start() - initiated = true - } - } - .toolbar { - ToolbarItemGroup(placement: .navigationBarTrailing) { - Button { - showLogs.toggle() - } label: { - Label("Toggle Log", systemImage: "bubble.left") - } - Button { - runtime.reload() - } label: { - Label("Reload", systemImage: "arrow.clockwise") - } - } - } - .preferredColorScheme(preferredScheme) - } - - private var logList: some View { - VStack(alignment: .leading, spacing: 8) { - HStack { - Label("Messages", systemImage: "bubble.left") - .font(.subheadline.weight(.semibold)) - Spacer() - } - ScrollViewReader { proxy in - ScrollView { - LazyVStack(alignment: .leading, spacing: 6) { - ForEach(Array(runtime.logs.enumerated()), id: \.offset) { index, line in - Text(line) - .font(.caption.monospaced()) - .id(index) - .frame(maxWidth: .infinity, alignment: .leading) - } - } - } - .onChange(of: runtime.logs.count) { _, newCount in - guard newCount > 0 else { return } - withAnimation { proxy.scrollTo(newCount - 1, anchor: .bottom) } - } - } - } - .padding(12) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) - .overlay( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .strokeBorder(Color.white.opacity(0.12), lineWidth: 1) - ) - } -} - -private struct MiniToolWebContainer: UIViewRepresentable { - @ObservedObject var runtime: MiniToolRuntime - - func makeUIView(context: Context) -> WKWebView { - runtime.webView! - } - - func updateUIView(_ uiView: WKWebView, context: Context) { - } -} diff --git a/StikJIT/MiniToolSupport/MiniToolRuntime.swift b/StikJIT/MiniToolSupport/MiniToolRuntime.swift deleted file mode 100644 index 9435c7e..0000000 --- a/StikJIT/MiniToolSupport/MiniToolRuntime.swift +++ /dev/null @@ -1,630 +0,0 @@ -import Foundation -import UniformTypeIdentifiers -import SwiftUI -import WebKit -import JavaScriptCore - -final class MiniToolRuntime: NSObject, ObservableObject { - let tool: MiniToolBundle - let toolInfo: ToolInfo - @Published var logs: [String] = [] - @Published var isReady: Bool = false - - var webView: WKWebView? - private var context: JSContext? - - private var appXHRTasks: [String: URLSessionDataTask] = [:] - - private let messageHandlerName = "miniToolBridge" - - private var ideviceJSBridge : IDeviceJSBridge? = nil - - init(tool: MiniToolBundle, toolInfo: ToolInfo) { - self.tool = tool - self.toolInfo = toolInfo - super.init() - let configuration = WKWebViewConfiguration() - let controller = WKUserContentController() - controller.addUserScript(WKUserScript(source: MiniToolRuntime.frontendBridgeScript, - injectionTime: .atDocumentStart, - forMainFrameOnly: true)) - configuration.userContentController = controller - let leakAvoider = LeakAvoider(delegate: self) - configuration.setURLSchemeHandler(leakAvoider, forURLScheme: "app") - webView = WKWebView(frame: .zero, configuration: configuration) - controller.add(leakAvoider, name: messageHandlerName) - webView!.navigationDelegate = self - webView!.isInspectable = true - } - - deinit { - webView!.configuration.userContentController.removeScriptMessageHandler(forName: messageHandlerName) - } - - func start() { - DispatchQueue.main.async { - self.logs.removeAll() - } - loadBackground() - loadFrontend() - } - - func reload() { - start() - } - - // MARK: - Loading - - private func loadFrontend() { - guard FileManager.default.fileExists(atPath: tool.indexURL.path) else { - appendLog("index.html missing for \(tool.name)") - return - } - isReady = false - webView!.load(URLRequest(url: URL(string: "app://\(tool.getHostName())")!)) - } - - private func loadBackground() { - context = JSContext() - ideviceJSBridge = IDeviceJSBridge(context: context, allowedFunctions: toolInfo.requiredIDeviceFunctions ?? []) - context?.exceptionHandler = { [weak self] _, exception in - if let message = exception?.toString() { - self?.appendLog("Background exception: \(message)") - } - } - - let sendToFrontend: @convention(block) (Any?) -> Void = { [weak self] payload in - self?.deliverToFrontend(payload ?? NSNull()) - } - - let logFunction: @convention(block) (Any?) -> Void = { [weak self] msg in - self?.appendLog(msg as? String ?? "Unable to decode log message.") - - } - - let ideviceFunction: @convention(block) (Any?) -> JSValue = { [weak self] msg in - return JSValue.init(newPromiseIn: self?.context!) { resolve, reject in - if let msg = msg as? [String: Any] { - if let bridge = self?.ideviceJSBridge { - bridge.didReceiveScriptMessage(msg, resolve: resolve, reject: reject); - } else { - resolve?.call(withArguments: ["Current Runtime is Terminated."]) - } - } - } - } - context?.setObject(sendToFrontend, forKeyedSubscript: "__miniToolPostMessage" as NSString) - context?.setObject(logFunction, forKeyedSubscript: "__miniToolLog" as NSString) - context?.setObject(ideviceFunction, forKeyedSubscript: "__postIdeviceMessage" as NSString) - - context?.evaluateScript(MiniToolRuntime.backgroundBridgeScript) - if let ideviceJSURL = Bundle.main.url(forResource: "idevice", withExtension: "js"), - let ideviceJSText = try? String(contentsOf: ideviceJSURL, encoding: .utf8) { - context?.evaluateScript(ideviceJSText) - } - - do { - let script = try String(contentsOf: tool.backgroundURL) - context?.evaluateScript(script) - } catch { - appendLog("Failed to load background.js: \(error.localizedDescription)") - } - } - - private func deliverToBackground(_ payload: Any) { - guard let receiver = context?.objectForKeyedSubscript("__miniToolReceive"), - !receiver.isUndefined else { - appendLog("Background handler is not ready") - return - } - _ = receiver.call(withArguments: [payload]) - } - - private func deliverToFrontend(_ payload: Any) { - guard let json = MiniToolRuntime.encodePayload(payload) else { - appendLog("Unable to encode payload for frontend") - return - } - DispatchQueue.main.async { - let script = "window.miniTool && window.miniTool.__receive(\(json))" - self.webView!.evaluateJavaScript(script) { _, error in - if let error { - self.appendLog("Frontend dispatch error: \(error.localizedDescription)") - } - } - } - } - - private func appendLog(_ text: String) { - DispatchQueue.main.async { - self.logs.append(text) - } - } -} - -extension MiniToolRuntime : WKURLSchemeHandler { - func webView( - _ webView: WKWebView, - start urlSchemeTask: WKURLSchemeTask - ) { - guard let url = urlSchemeTask.request.url else { return } - - guard let host = url.host(), host == tool.getHostName() else { - urlSchemeTask.didFailWithError(NSError(domain: "Invalid Host Name", code: 404)) - return - } - - let path = url.path.isEmpty ? "index.html" : url.path - - let fileURL = tool.url - .appendingPathComponent(path).standardizedFileURL - - if fileURL.path().hasPrefix(tool.url.path()) { - urlSchemeTask.didFailWithError(NSError(domain: "Path traversal is not allowed.", code: -1)) - return - } - - if fileURL.path() == tool.url.appending(path: "check").path() { - urlSchemeTask.didFailWithError(NSError(domain: "Reading this file is not allowed.", code: -1)) - return - } - - do { - let data = try Data(contentsOf: fileURL) - let mimeType : String - if let type = UTType(filenameExtension: fileURL.pathExtension), let mimetype = type.preferredMIMEType { - mimeType = mimetype - } else { - mimeType = UTType.data.preferredMIMEType! - } - - let response = URLResponse( - url: url, - mimeType: mimeType, - expectedContentLength: data.count, - textEncodingName: nil - ) - - urlSchemeTask.didReceive(response) - urlSchemeTask.didReceive(data) - urlSchemeTask.didFinish() - } catch { - urlSchemeTask.didFailWithError(error) - } - } - - func webView(_ webView: WKWebView, stop urlSchemeTask: any WKURLSchemeTask) { - - } -} - -// MARK: - WKScriptMessageHandler - -extension MiniToolRuntime: WKScriptMessageHandler { - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - guard message.name == messageHandlerName else { return } - if let dict = message.body as? [String: Any], dict["__appXHR"] as? Bool == true { - handleAppXHRMessage(dict) - return - } - if let dict = message.body as? [String: Any], let payload = dict["payload"] { - deliverToBackground(payload) - } else { - deliverToBackground(message.body) - } - } -} - -// MARK: - WKNavigationDelegate - -extension MiniToolRuntime: WKNavigationDelegate { - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { - // Don't allow loading other contents - guard let url = navigationAction.request.url else { - return WKNavigationActionPolicy.cancel - } - guard let scheme = url.scheme, scheme == "app" else { - return WKNavigationActionPolicy.cancel - } - guard let host = url.host(), host == tool.getHostName() else { - return WKNavigationActionPolicy.cancel - } - return WKNavigationActionPolicy.allow - } - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - isReady = true - deliverToBackground(["type": "ui-ready", "tool": tool.name]) - deliverToFrontend(["type": "ready", "tool": tool.name]) - } - - func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) { - download.delegate = self - } - -} - -extension MiniToolRuntime: WKDownloadDelegate { - - func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String) async -> URL? { - return MiniToolStore.toolsDataDirectory().appending(path: suggestedFilename, directoryHint: .notDirectory) - } -} - -// MARK: - Scripts & Encoding - -extension MiniToolRuntime { - static let frontendBridgeScript = """ - window.miniTool = window.miniTool || {}; - window.miniTool.__handler = null; - window.miniTool.onMessage = function(handler) { window.miniTool.__handler = handler; }; - window.miniTool.postMessage = function(payload) { - window.webkit.messageHandlers.miniToolBridge.postMessage({ payload: payload }); - }; - window.miniTool.__receive = function(payload) { - try { - if (typeof window.miniTool.__handler === 'function') { - window.miniTool.__handler(payload); - } - } catch (err) { - console.error(err); - } - }; - - (function() { - const handler = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.miniToolBridge; - function safePostMessage(body) { - if (!handler || !handler.postMessage) { - console.error('miniToolBridge unavailable for AppXMLHttpRequest'); - return; - } - handler.postMessage(body); - } - - function base64ToArrayBuffer(base64) { - const binaryString = atob(base64); - const len = binaryString.length; - const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes.buffer; - } - - class AppXMLHttpRequest { - constructor() { - this.readyState = 0; - this.status = 0; - this.statusText = ''; - this.responseText = ''; - this.response = null; - this.responseType = ''; - this.onreadystatechange = null; - this.onload = null; - this.onerror = null; - this.onabort = null; - this._headers = {}; - this._method = null; - this._url = null; - this._async = true; - this._aborted = false; - this._id = AppXMLHttpRequest.__nextId(); - } - - open(method, url, async = true) { - this._method = method; - this._url = url; - this._async = async !== false; - this.readyState = 1; // OPENED - this._emitReadyState(); - } - - setRequestHeader(key, value) { - this._headers[key] = value; - } - - send(body = null) { - if (!this._method || !this._url) { - throw new Error('AppXMLHttpRequest: call open() before send().'); - } - - this.readyState = 2; // HEADERS_RECEIVED (simulated) - this._emitReadyState(); - - AppXMLHttpRequest.__pending[this._id] = this; - - let encodedBody = null; - let bodyIsBase64 = false; - if (typeof body === 'string') { - encodedBody = body; - } else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) { - const view = body instanceof ArrayBuffer ? new Uint8Array(body) : new Uint8Array(body.buffer, body.byteOffset || 0, body.byteLength || body.length || 0); - let binary = ''; - for (let i = 0; i < view.length; i++) { - binary += String.fromCharCode(view[i]); - } - encodedBody = btoa(binary); - bodyIsBase64 = true; - } else if (body != null) { - try { - encodedBody = JSON.stringify(body); - } catch (err) { - console.error('AppXMLHttpRequest: unable to serialize body', err); - } - } - - safePostMessage({ - __appXHR: true, - action: 'request', - id: this._id, - method: this._method, - url: this._url, - async: this._async, - headers: this._headers, - body: encodedBody, - bodyIsBase64: bodyIsBase64, - responseType: this.responseType - }); - } - - abort() { - this._aborted = true; - safePostMessage({ __appXHR: true, action: 'abort', id: this._id }); - } - - _complete(payload) { - if (this._aborted && !payload.aborted) { - return; - } - - this.status = payload.status || 0; - this.statusText = payload.statusText || ''; - this.responseText = payload.responseText || ''; - const base64 = payload.base64 || null; - - if (this.responseType === 'json') { - try { - this.response = this.responseText ? JSON.parse(this.responseText) : null; - } catch (err) { - console.error('AppXMLHttpRequest: failed to parse JSON response', err); - this.response = null; - } - } else if (this.responseType === 'arraybuffer' && base64) { - this.response = base64ToArrayBuffer(base64); - } else { - this.response = this.responseText; - } - - this.readyState = 4; // DONE - this._emitReadyState(); - - if (payload.aborted) { - if (typeof this.onabort === 'function') { - try { this.onabort(); } catch (err) { console.error(err); } - } - delete AppXMLHttpRequest.__pending[this._id]; - return; - } - - if (payload.error) { - if (typeof this.onerror === 'function') { - try { this.onerror(new Error(payload.error)); } catch (err) { console.error(err); } - } - } else if (typeof this.onload === 'function') { - try { this.onload(); } catch (err) { console.error(err); } - } - - delete AppXMLHttpRequest.__pending[this._id]; - } - - _emitReadyState() { - if (typeof this.onreadystatechange === 'function') { - try { - this.onreadystatechange(); - } catch (err) { - console.error(err); - } - } - } - - static __nextId() { - AppXMLHttpRequest.__counter += 1; - return `app-xhr-${AppXMLHttpRequest.__counter}`; - } - - static __receive(payload) { - const instance = AppXMLHttpRequest.__pending[payload.id]; - if (instance) { - instance._complete(payload); - } - } - } - - AppXMLHttpRequest.__counter = 0; - AppXMLHttpRequest.__pending = {}; - - window.__XMLHttpRequest = window.XMLHttpRequest; - window.XMLHttpRequest = AppXMLHttpRequest; // alias with requested casing - })(); - """ - - static let backgroundBridgeScript = """ - var miniTool = this.miniTool || {}; - miniTool.__handler = null; - miniTool.onMessage = function(handler) { miniTool.__handler = handler; }; - miniTool.postMessage = function(payload) { __miniToolPostMessage(payload); }; - miniTool.log = function(log) { __miniToolLog(log); } - function __miniToolReceive(payload) { - try { - if (typeof miniTool.__handler === 'function') { - miniTool.__handler(payload); - } - } catch (err) { - console.log(err); - } - } - this.miniTool = miniTool; - """ - - static func encodePayload(_ payload: Any) -> String? { - if JSONSerialization.isValidJSONObject(payload) { - if let data = try? JSONSerialization.data(withJSONObject: payload, options: []), - let string = String(data: data, encoding: .utf8) { - return string - } - } - if let string = payload as? String { - let escaped = string.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") - return "\"\(escaped)\"" - } - if let number = payload as? NSNumber { - return number.stringValue - } - return nil - } -} - -// MARK: - App-backed XHR - -private extension MiniToolRuntime { - func handleAppXHRMessage(_ payload: [String: Any]) { - guard let capabilities = toolInfo.capabilities, capabilities.internetAccess ?? false else { - appendLog("Internet access is disabled! To enable it, enable internetAccess in toolInfo.json") - return - } - - guard let id = payload["id"] as? String else { - appendLog("AppXHR missing id") - return - } - - let action = payload["action"] as? String ?? "request" - - if action == "abort" { - if let task = appXHRTasks[id] { - task.cancel() - appXHRTasks[id] = nil - } - deliverAppXHRResponse(["id": id, "status": 0, "statusText": "aborted", "aborted": true]) - return - } - - guard let urlString = payload["url"] as? String, let url = URL(string: urlString) else { - appendLog("AppXHR invalid URL for id \(id)") - deliverAppXHRResponse(["id": id, "status": 0, "statusText": "invalid-url", "error": "Invalid URL"]) - return - } - - var request = URLRequest(url: url) - request.httpMethod = (payload["method"] as? String) ?? "GET" - - if let headers = payload["headers"] as? [String: Any] { - headers.forEach { key, value in - if let valueString = value as? String ?? (value as? NSNumber)?.stringValue { - request.setValue(valueString, forHTTPHeaderField: key) - } - } - } - - if let body = payload["body"] { - let isBase64 = payload["bodyIsBase64"] as? Bool ?? false - if let stringBody = body as? String { - if isBase64, let data = Data(base64Encoded: stringBody) { - request.httpBody = data - } else { - request.httpBody = stringBody.data(using: .utf8) - } - } else if JSONSerialization.isValidJSONObject(body), - let data = try? JSONSerialization.data(withJSONObject: body, options: []) { - request.httpBody = data - } - } - - let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in - guard let self else { return } - - var responsePayload: [String: Any] = [ - "id": id, - "status": 0, - "statusText": "" - ] - - if let http = response as? HTTPURLResponse { - responsePayload["status"] = http.statusCode - responsePayload["statusText"] = HTTPURLResponse.localizedString(forStatusCode: http.statusCode) - - let headers = http.allHeaderFields.reduce(into: [String: String]()) { partialResult, entry in - if let key = entry.key as? String { - partialResult[key] = String(describing: entry.value) - } - } - responsePayload["headers"] = headers - } - - if let error = error as NSError? { - if error.code == NSURLErrorCancelled { - responsePayload["aborted"] = true - responsePayload["statusText"] = "aborted" - } else { - responsePayload["error"] = error.localizedDescription - } - } - - if let data = data { - responsePayload["responseText"] = String(data: data, encoding: .utf8) ?? "" - responsePayload["base64"] = data.base64EncodedString() - } - - DispatchQueue.main.async { - self.appXHRTasks[id] = nil - self.deliverAppXHRResponse(responsePayload) - } - } - - appXHRTasks[id] = task - task.resume() - } - - func deliverAppXHRResponse(_ payload: [String: Any]) { - guard let json = MiniToolRuntime.encodePayload(payload) else { - appendLog("AppXHR: Unable to encode response for id \(payload["id"] ?? "")") - return - } - - DispatchQueue.main.async { - let script = "window.XMLHttpRequest.__receive(\(json))" - self.webView!.evaluateJavaScript(script) { _, error in - if let error { - self.appendLog("AppXHR deliver error: \(error.localizedDescription)") - } - } - } - } -} - -class LeakAvoider : NSObject, WKURLSchemeHandler, WKScriptMessageHandler, WKDownloadDelegate { - func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String) async -> URL? { - return await self.delegate?.download(download, decideDestinationUsing: response, suggestedFilename: suggestedFilename) - } - - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - self.delegate?.userContentController(userContentController, didReceive: message) - } - - func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { - self.delegate?.webView(webView, start: urlSchemeTask) - } - - func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { - self.delegate?.webView(webView, stop: urlSchemeTask) - } - - - - weak var delegate : (WKURLSchemeHandler & WKScriptMessageHandler & WKDownloadDelegate)? - init (delegate:WKURLSchemeHandler & WKScriptMessageHandler & WKDownloadDelegate) { - self.delegate = delegate - super.init() - } - -} diff --git a/StikJIT/StikJIT-Bridging-Header.h b/StikJIT/StikJIT-Bridging-Header.h index c720e00..c4b0c21 100644 --- a/StikJIT/StikJIT-Bridging-Header.h +++ b/StikJIT/StikJIT-Bridging-Header.h @@ -11,4 +11,3 @@ #include "idevice/ls.h" #include "idevice/profiles.h" #include "idevice/something.h" -#include "MiniToolSupport/JSSupport/JSSupport.h" diff --git a/StikJIT/Utilities/FeatureFlags.swift b/StikJIT/Utilities/FeatureFlags.swift index b1c63c1..b7a145d 100644 --- a/StikJIT/Utilities/FeatureFlags.swift +++ b/StikJIT/Utilities/FeatureFlags.swift @@ -7,6 +7,4 @@ enum FeatureFlags { static let showBetaTabs = true /// Forces the Theme Expansion to be unlocked and visible everywhere. static let alwaysUnlockThemeExpansion = true - /// Controls visibility of the Mini Tools tab and related UI. - static let isMiniToolsEnabled = false } diff --git a/StikJIT/Utilities/TabConfiguration.swift b/StikJIT/Utilities/TabConfiguration.swift index fa63b2e..e8614b0 100644 --- a/StikJIT/Utilities/TabConfiguration.swift +++ b/StikJIT/Utilities/TabConfiguration.swift @@ -6,9 +6,6 @@ enum TabConfiguration { private static let coreIDs: [String] = ["home", "console", "scripts", "deviceinfo"] static var allowedIDs: [String] { var ids = coreIDs - if FeatureFlags.isMiniToolsEnabled { - ids.append("tools") - } if FeatureFlags.showBetaTabs { ids.append("profiles") ids.append("processes") diff --git a/StikJIT/Views/MainTabView.swift b/StikJIT/Views/MainTabView.swift index 3df5378..8caf282 100644 --- a/StikJIT/Views/MainTabView.swift +++ b/StikJIT/Views/MainTabView.swift @@ -56,9 +56,6 @@ struct MainTabView: View { TabDescriptor(id: "scripts", title: "Scripts", systemImage: "scroll") { AnyView(ScriptListView()) }, TabDescriptor(id: "deviceinfo", title: "Device Info", systemImage: "iphone.and.arrow.forward") { AnyView(DeviceInfoView()) } ] - if FeatureFlags.isMiniToolsEnabled { - tabs.append(TabDescriptor(id: "tools", title: "Mini Tools", systemImage: "shippingbox.fill") { AnyView(MiniToolListView()) }) - } if FeatureFlags.showBetaTabs { tabs.append(TabDescriptor(id: "profiles", title: "Profiles", systemImage: "magazine.fill") { AnyView(ProfileView()) }) tabs.append(TabDescriptor(id: "processes", title: "Processes", systemImage: "rectangle.stack.person.crop") { AnyView(ProcessInspectorView()) }) diff --git a/StikJIT/Views/SettingsView.swift b/StikJIT/Views/SettingsView.swift index e9f386d..3f7119d 100644 --- a/StikJIT/Views/SettingsView.swift +++ b/StikJIT/Views/SettingsView.swift @@ -90,9 +90,6 @@ struct SettingsView: View { TabOption(id: "console", title: "Console", detail: "Live device logs", icon: "terminal", isBeta: false), TabOption(id: "scripts", title: "Scripts", detail: "Manage automation scripts", icon: "scroll", isBeta: false) ] - if FeatureFlags.isMiniToolsEnabled { - options.append(TabOption(id: "tools", title: "Mini Tools", detail: "Import and run stiktool bundles", icon: "shippingbox", isBeta: true)) - } options.append(TabOption(id: "deviceinfo", title: "Device Info", detail: "View detailed device metadata", icon: "iphone.and.arrow.forward", isBeta: false)) if FeatureFlags.showBetaTabs { options.append(TabOption(id: "profiles", title: "Profiles", detail: "Install/remove profiles", icon: "magazine", isBeta: true))