StikDebug/StikJIT/idevice/process.m
Stephen f3d2dec2e5
Semi-Rewrite – Major Refactor & Codebase Cleanup (#328)
* Change default UPLOAD_IPA value to 'false'

* Part 1 of the great debloat

* Remove Mini Tools, adopt native alerts in StikJITApp, and update HomeView UI

* Refactor views to use native SwiftUI alerts instead of CustomErrorView

* Update ProfileView to use minimal List UI style with native alerts

* Fix up homeview

* Squash: Redesign HomeView, remove Feature Flags, enable all tabs, refactor alerts, and updates

- Redesign HomeView to a simpler style with 'Enable JIT' action.
- Remove 'Help & Info' and 'Quick Launch' from Home.
- Fix DDI false positive by starting heartbeat on HomeView appear.
- Restore robust DDI checks and update app name to 'StikDebug'.
- Remove FeatureFlags and Beta tags; enable 'profiles', 'processes', 'location' tabs by default.
- Remove 'Devices' tab; add 'Target Device IP' field in Settings.
- Hardcode 'Settings' tab to 4th position.
- Refactor 'SettingsView' tab customization to be reorder-only.
- Remove 'Appearance' theme section from Settings.
- Refactor custom error cards to use native SwiftUI/UIKit alerts across the app (HomeView, ConsoleLogsView, ProfileView, etc).
- Restore JS callback support and script execution logic in HomeView.
- Add necessary Color extensions.
- Update project version to 3.0.0.

* Clean up: Remove In-App Purchases, update checker, and unused IPA installer code

* delete more bloat

* fix build issue

* Remove widget to save on App IDs

* Attempt heartbeat restart when Enable JIT button is pressed

* Get rid of PIP

* Update Tab Naming

* remove more bloat

* final changes for today

* Remove unused packages

* UX Improvements

* revert bundle id

* Update README.md

* remove old idevice

* add updated idevice

* Fix ddi false positive errors and add templates for GitHub stuff

* Remove more unused stuff.

* Fix links and update README content

* Update compatibility table in README.md

* Improve performance of console logs

* Remove old code

* Change default UPLOAD_IPA value to 'true'

* remove the welcome sheet

* import support for .mobiledevicepair

* Add back LC JIT stuff

* Fix heartbeat issues with LC JIT

* update the build yml for nightly stuff

* Update README.md

* Update README.md

* Update GitHub Actions workflow for IPA build

* Update GitHub Actions workflow for nightly builds

* Add URL scheme to kill tasks stikjit://kill-process?pid=1234

* fix build issues

* Added App Intents

* Unify the error handling style across the app
2026-02-25 13:44:00 -05:00

184 lines
6.4 KiB
Objective-C

//
// process.m
// StikDebug
//
// Created by s s on 2025/12/12.
//
#import "JITEnableContext.h"
#import "JITEnableContextInternal.h"
@import Foundation;
// MARK: - Shared AppService session
typedef struct {
AdapterHandle *adapter;
RsdHandshakeHandle *handshake;
AppServiceHandle *appService;
} AppServiceSession;
static void app_service_session_free(AppServiceSession *s) {
if (s->appService) { app_service_free(s->appService); s->appService = NULL; }
if (s->handshake) { rsd_handshake_free(s->handshake); s->handshake = NULL; }
if (s->adapter) { adapter_free(s->adapter); s->adapter = NULL; }
}
// Connects to the device via CoreDeviceProxy → Adapter → RSD → AppService.
// Returns 0 on success; cleans up any partial state and returns 1 on failure.
static int connect_app_service(IdeviceProviderHandle *provider,
AppServiceSession *out,
JITEnableContext *ctx,
NSError **outError)
{
memset(out, 0, sizeof(*out));
IdeviceFfiError *ffiError = NULL;
CoreDeviceProxyHandle *coreProxy = NULL;
ffiError = core_device_proxy_connect(provider, &coreProxy);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Failed to connect CoreDeviceProxy"]
code:ffiError->code];
idevice_error_free(ffiError);
return 1;
}
uint16_t rsdPort = 0;
ffiError = core_device_proxy_get_server_rsd_port(coreProxy, &rsdPort);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Unable to resolve RSD port"]
code:ffiError->code];
idevice_error_free(ffiError);
core_device_proxy_free(coreProxy);
return 1;
}
ffiError = core_device_proxy_create_tcp_adapter(coreProxy, &out->adapter);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Failed to create adapter"]
code:ffiError->code];
idevice_error_free(ffiError);
core_device_proxy_free(coreProxy);
return 1;
}
coreProxy = NULL; // ownership transferred to adapter
AdapterStreamHandle *stream = NULL;
ffiError = adapter_connect(out->adapter, rsdPort, (ReadWriteOpaque **)&stream);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Adapter connect failed"]
code:ffiError->code];
idevice_error_free(ffiError);
app_service_session_free(out);
return 1;
}
ffiError = rsd_handshake_new((ReadWriteOpaque *)stream, &out->handshake);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "RSD handshake failed"]
code:ffiError->code];
idevice_error_free(ffiError);
adapter_stream_close(stream);
app_service_session_free(out);
return 1;
}
stream = NULL; // consumed by handshake
ffiError = app_service_connect_rsd(out->adapter, out->handshake, &out->appService);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Unable to open AppService"]
code:ffiError->code];
idevice_error_free(ffiError);
app_service_session_free(out);
return 1;
}
return 0;
}
// MARK: - JITEnableContext(Process)
@implementation JITEnableContext(Process)
- (NSArray<NSDictionary*>*)fetchProcessesViaAppServiceWithError:(NSError **)error {
[self ensureHeartbeatWithError:error];
if (*error) { return nil; }
AppServiceSession session;
if (connect_app_service(provider, &session, self, error) != 0) { return nil; }
ProcessTokenC *processes = NULL;
uintptr_t count = 0;
IdeviceFfiError *ffiError = app_service_list_processes(session.appService, &processes, &count);
NSMutableArray *result = nil;
if (ffiError) {
if (error) {
*error = [self errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Failed to list processes"]
code:ffiError->code];
}
idevice_error_free(ffiError);
} else {
result = [NSMutableArray arrayWithCapacity:count];
for (uintptr_t idx = 0; idx < count; idx++) {
ProcessTokenC proc = processes[idx];
NSMutableDictionary *entry = [NSMutableDictionary dictionary];
entry[@"pid"] = @(proc.pid);
if (proc.executable_url) {
entry[@"path"] = [NSString stringWithUTF8String:proc.executable_url];
}
[result addObject:entry];
}
if (processes && count > 0) {
app_service_free_process_list(processes, count);
}
}
app_service_session_free(&session);
return result;
}
- (NSArray<NSDictionary*>*)_fetchProcessListLocked:(NSError**)error {
[self ensureHeartbeatWithError:error];
if (*error) { return nil; }
return [self fetchProcessesViaAppServiceWithError:error];
}
- (NSArray<NSDictionary*>*)fetchProcessListWithError:(NSError**)error {
__block NSArray *result = nil;
__block NSError *localError = nil;
dispatch_sync(processInspectorQueue, ^{
result = [self _fetchProcessListLocked:&localError];
});
if (error && localError) {
*error = localError;
}
return result;
}
- (BOOL)killProcessWithPID:(int)pid error:(NSError **)error {
[self ensureHeartbeatWithError:error];
if (*error) { return NO; }
AppServiceSession session;
if (connect_app_service(provider, &session, self, error) != 0) { return NO; }
SignalResponseC *signalResponse = NULL;
IdeviceFfiError *ffiError = app_service_send_signal(session.appService, (uint32_t)pid, SIGKILL, &signalResponse);
BOOL success = NO;
if (ffiError) {
if (error) {
*error = [self errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Failed to kill process"]
code:ffiError->code];
}
idevice_error_free(ffiError);
} else {
success = YES;
}
if (signalResponse) { app_service_free_signal_response(signalResponse); }
app_service_session_free(&session);
return success;
}
@end