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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions StikJIT/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,43 @@
</array>
</dict>
</array>

<key>ITSAppUsesNonExemptEncryption</key>
<false/>

<key>UIFileSharingEnabled</key>
<true/>

<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>com.apple.package</string>
</array>
<key>UTTypeDescription</key>
<string>StikDebug Mini Tool</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>com.stik.StikJIT.stiktool</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>stiktool</string>
</array>
</dict>
</dict>
</array>

<key>NSLocalNetworkUsageDescription</key>
<string>StikDebug needs access to devices on your local network so it can connect to the targets you add to the Device Library.</string>

<key>NSBonjourServices</key>
<array>
<string>_stikdebug._tcp</string>
<string>_stikdebug._udp</string>
</array>
<key>UIFileSharingEnabled</key>
<true/>
</dict>
</plist>
</plist>
2 changes: 1 addition & 1 deletion StikJIT/JSSupport/ScriptListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ struct ScriptListView: View {
}

// MARK: - Equal-width rounded-rectangle button (centered content)
private struct WideGlassyButton: View {
struct WideGlassyButton: View {
let title: String
let systemImage: String
let action: () -> Void
Expand Down
192 changes: 192 additions & 0 deletions StikJIT/MiniToolSupport/JSSupport/IDeviceJSBridge.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//
// IDeviceJSBridge.m
// StikJIT
//
// Created by s s on 2025/4/24.
//
@import Foundation;
#import "JSSupport.h"

@implementation IDeviceHandle

@end


NSDictionary *dictionaryFromPlistData(NSData *plistData, NSError **error) {
if (!plistData) {
if (error) {
*error = [NSError errorWithDomain:@"PlistConversionErrorDomain"
code:1001
userInfo:@{NSLocalizedDescriptionKey: @"Input plist data is nil."}];
}
return nil;
}

NSPropertyListFormat format;
NSDictionary *result = [NSPropertyListSerialization propertyListWithData:plistData
options:NSPropertyListImmutable
format:&format
error:error];
if (![result isKindOfClass:[NSDictionary class]]) {
if (error && !*error) {
*error = [NSError errorWithDomain:@"PlistConversionErrorDomain"
code:1002
userInfo:@{NSLocalizedDescriptionKey: @"Plist is not a dictionary."}];
}
return nil;
}

return result;
}

NSData *plistDataFromDictionary(NSDictionary *dictionary, NSError **error) {
if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]]) {
if (error) {
*error = [NSError errorWithDomain:@"PlistSerializationErrorDomain"
code:2001
userInfo:@{NSLocalizedDescriptionKey: @"Input is not a valid dictionary."}];
}
return nil;
}

NSData *data = [NSPropertyListSerialization dataWithPropertyList:dictionary
format:NSPropertyListXMLFormat_v1_0
options:0
error:error];
return data;
}

const char** cstrArrFromNSArray(NSArray* arr, int* validCount) {
const char** ans = 0;
*validCount = 0;
if(![arr isKindOfClass:NSArray.class]) {
return 0;
} else {
ans = malloc([arr count] * sizeof(void*));
for (id str in arr) {
if([str isKindOfClass:NSString.class]) {
ans[*validCount] = [str UTF8String];
(*validCount)++;
}
}
}
return ans;
}

@implementation IDeviceJSBridge {
int maxHandleId;
int maxDataId;
}

- (instancetype)initWithContext:(JSContext*)context {
maxHandleId = 0;
maxDataId = 0;
handles = [[NSMutableDictionary alloc] init];
dataPool = [[NSMutableDictionary alloc] init];
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;
}

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
Loading