Remove minitool cuz it's no longer useful

This commit is contained in:
Huge_Black 2026-02-21 12:40:04 +08:00
parent 59c1403ea4
commit e9d9f3578e
27 changed files with 0 additions and 4009 deletions

View file

@ -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<NSString*>*)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<NSNumber *> *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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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<NSString *> *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

View file

@ -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

View file

@ -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

View file

@ -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<NSNumber*, IDeviceHandle*>* handles;
NSMutableDictionary<NSNumber*, NSData*>* dataPool;
NSArray<NSString*>* allowedFunctions;
JSContext* context;
}
- (instancetype)initWithContext:(JSContext*)context allowedFunctions:(NSArray<NSString*>*)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);

View file

@ -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

View file

@ -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
});
}

View file

@ -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<TextLocated<Message>> = []
@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<String> {
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()
}
}

View file

@ -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)
}
}
}

View file

@ -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
}
}

View file

@ -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) {
}
}

View file

@ -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"] ?? "<unknown>")")
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()
}
}

View file

@ -11,4 +11,3 @@
#include "idevice/ls.h"
#include "idevice/profiles.h"
#include "idevice/something.h"
#include "MiniToolSupport/JSSupport/JSSupport.h"

View file

@ -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
}

View file

@ -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")

View file

@ -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()) })

View file

@ -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))