mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Thread state is hexadecimal, so we now explicitly use NSScanner to parse string as base-16.
316 lines
9.8 KiB
Plaintext
316 lines
9.8 KiB
Plaintext
//
|
|
// ALTDebugConnection.m
|
|
// AltServer
|
|
//
|
|
// Created by Riley Testut on 2/19/21.
|
|
// Copyright © 2021 Riley Testut. All rights reserved.
|
|
//
|
|
|
|
#import "ALTDebugConnection+Private.h"
|
|
|
|
#import "NSError+ALTServerError.h"
|
|
#import "NSError+libimobiledevice.h"
|
|
|
|
char *bin2hex(const unsigned char *bin, size_t length)
|
|
{
|
|
if (bin == NULL || length == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
char *hex = (char *)malloc(length * 2 + 1);
|
|
for (size_t i = 0; i < length; i++)
|
|
{
|
|
hex[i * 2] = "0123456789ABCDEF"[bin[i] >> 4];
|
|
hex[i * 2 + 1] = "0123456789ABCDEF"[bin[i] & 0x0F];
|
|
}
|
|
hex[length * 2] = '\0';
|
|
|
|
return hex;
|
|
}
|
|
|
|
@implementation ALTDebugConnection
|
|
|
|
- (instancetype)initWithDevice:(ALTDevice *)device
|
|
{
|
|
self = [super init];
|
|
if (self)
|
|
{
|
|
_device = device;
|
|
_connectionQueue = dispatch_queue_create_with_target("io.altstore.AltServer.DebugConnection",
|
|
DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL,
|
|
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0));
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[self disconnect];
|
|
}
|
|
|
|
- (void)disconnect
|
|
{
|
|
if (_client == nil)
|
|
{
|
|
return;
|
|
}
|
|
|
|
debugserver_client_free(_client);
|
|
_client = nil;
|
|
}
|
|
|
|
- (void)connectWithCompletionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler
|
|
{
|
|
__block idevice_t device = NULL;
|
|
|
|
void (^finish)(BOOL, NSError *) = ^(BOOL success, NSError *error) {
|
|
if (device)
|
|
{
|
|
idevice_free(device);
|
|
}
|
|
|
|
completionHandler(success, error);
|
|
};
|
|
|
|
dispatch_async(self.connectionQueue, ^{
|
|
/* Find Device */
|
|
if (idevice_new_with_options(&device, self.device.identifier.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS)
|
|
{
|
|
return finish(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]);
|
|
}
|
|
|
|
/* Connect to debugserver */
|
|
debugserver_client_t client = NULL;
|
|
debugserver_error_t error = debugserver_client_start_service(device, &client, "AltServer");
|
|
if (error != DEBUGSERVER_E_SUCCESS)
|
|
{
|
|
return finish(NO, [NSError errorWithDebugServerError:error device:self.device]);
|
|
}
|
|
|
|
self.client = client;
|
|
|
|
finish(YES, nil);
|
|
});
|
|
}
|
|
|
|
- (void)enableUnsignedCodeExecutionForProcessWithName:(NSString *)processName completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler
|
|
{
|
|
[self _enableUnsignedCodeExecutionForProcessWithName:processName pid:0 completionHandler:completionHandler];
|
|
}
|
|
|
|
- (void)enableUnsignedCodeExecutionForProcessWithID:(NSInteger)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler
|
|
{
|
|
[self _enableUnsignedCodeExecutionForProcessWithName:nil pid:(int32_t)pid completionHandler:completionHandler];
|
|
}
|
|
|
|
- (void)_enableUnsignedCodeExecutionForProcessWithName:(nullable NSString *)processName pid:(int32_t)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler
|
|
{
|
|
dispatch_async(self.connectionQueue, ^{
|
|
NSString *name = processName ?: NSLocalizedString(@"this app", @"");
|
|
NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"JIT could not be enabled for %@.", comment: @""), name];
|
|
|
|
NSString *attachCommand = nil;
|
|
|
|
if (processName)
|
|
{
|
|
NSString *encodedName = @(bin2hex((const unsigned char *)processName.UTF8String, (size_t)strlen(processName.UTF8String)));
|
|
attachCommand = [NSString stringWithFormat:@"vAttachOrWait;%@", encodedName];
|
|
}
|
|
else
|
|
{
|
|
// Convert to Big-endian.
|
|
int32_t rawPID = CFSwapInt32HostToBig(pid);
|
|
|
|
NSString *encodedName = @(bin2hex((const unsigned char *)&rawPID, 4));
|
|
attachCommand = [NSString stringWithFormat:@"vAttach;%@", encodedName];
|
|
}
|
|
|
|
NSError *error = nil;
|
|
if (![self sendCommand:attachCommand arguments:nil error:&error])
|
|
{
|
|
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
|
|
userInfo[ALTAppNameErrorKey] = processName;
|
|
userInfo[ALTDeviceNameErrorKey] = self.device.name;
|
|
userInfo[NSLocalizedFailureErrorKey] = localizedFailure;
|
|
|
|
NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
|
|
return completionHandler(NO, returnError);
|
|
}
|
|
|
|
NSString *detachCommand = @"D";
|
|
if (![self sendCommand:detachCommand arguments:nil error:&error])
|
|
{
|
|
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
|
|
userInfo[ALTAppNameErrorKey] = processName;
|
|
userInfo[ALTDeviceNameErrorKey] = self.device.name;
|
|
userInfo[NSLocalizedFailureErrorKey] = localizedFailure;
|
|
|
|
NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
|
|
return completionHandler(NO, returnError);
|
|
}
|
|
|
|
completionHandler(YES, nil);
|
|
});
|
|
}
|
|
|
|
#pragma mark - Private -
|
|
|
|
- (BOOL)sendCommand:(NSString *)command arguments:(nullable NSArray<NSString *> *)arguments error:(NSError **)error
|
|
{
|
|
int argc = (int)arguments.count;
|
|
char **argv = new char*[argc + 1];
|
|
|
|
for (int i = 0; i < argc; i++)
|
|
{
|
|
NSString *argument = arguments[i];
|
|
argv[i] = (char *)argument.UTF8String;
|
|
}
|
|
|
|
argv[argc] = NULL;
|
|
|
|
debugserver_command_t debugCommand = NULL;
|
|
debugserver_command_new(command.UTF8String, argc, argv, &debugCommand);
|
|
|
|
delete[] argv;
|
|
|
|
char *response = NULL;
|
|
size_t responseSize = 0;
|
|
debugserver_error_t debugServerError = debugserver_client_send_command(self.client, debugCommand, &response, &responseSize);
|
|
debugserver_command_free(debugCommand);
|
|
|
|
if (debugServerError != DEBUGSERVER_E_SUCCESS)
|
|
{
|
|
if (error)
|
|
{
|
|
*error = [NSError errorWithDebugServerError:debugServerError device:self.device];
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
NSString *rawResponse = (response != nil) ? @(response) : nil;
|
|
if (![self processResponse:rawResponse error:error])
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)processResponse:(NSString *)rawResponse error:(NSError **)error
|
|
{
|
|
if (rawResponse == nil)
|
|
{
|
|
if (error)
|
|
{
|
|
*error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil];
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
if (rawResponse.length == 0 || [rawResponse isEqualToString:@"OK"])
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
char type = [rawResponse characterAtIndex:0];
|
|
NSString *response = [rawResponse substringFromIndex:1];
|
|
|
|
switch (type)
|
|
{
|
|
case 'O':
|
|
{
|
|
// stdout/stderr
|
|
|
|
char *decodedResponse = NULL;
|
|
debugserver_decode_string(response.UTF8String, response.length, &decodedResponse);
|
|
|
|
NSLog(@"Response: %@", @(decodedResponse));
|
|
|
|
if (decodedResponse)
|
|
{
|
|
free(decodedResponse);
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
case 'T':
|
|
{
|
|
// Thread Information
|
|
|
|
NSLog(@"Thread stopped. Details:\n%s", response.UTF8String + 1);
|
|
|
|
// Parse thread state to determine if app is running.
|
|
NSArray<NSString *> *components = [response componentsSeparatedByString:@";"];
|
|
if (components.count <= 1)
|
|
{
|
|
if (error)
|
|
{
|
|
*error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}];
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
NSString *mainThread = components[1];
|
|
NSString *threadState = [[mainThread componentsSeparatedByString:@":"] lastObject];
|
|
|
|
NSScanner *scanner = [NSScanner scannerWithString:threadState];
|
|
|
|
unsigned long long mainThreadState = 0;
|
|
[scanner scanHexLongLong:&mainThreadState];
|
|
|
|
// If main thread state == 0, app is not running.
|
|
if (mainThreadState == 0)
|
|
{
|
|
if (error)
|
|
{
|
|
*error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil];
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
case 'E':
|
|
{
|
|
// Error
|
|
|
|
if (error)
|
|
{
|
|
NSInteger errorCode = [[[response componentsSeparatedByString:@";"] firstObject] integerValue];
|
|
|
|
switch (errorCode)
|
|
{
|
|
case 96:
|
|
*error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil];
|
|
break;
|
|
|
|
default:
|
|
*error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
case 'W':
|
|
{
|
|
// Warning
|
|
|
|
NSLog(@"WARNING: %@", response);
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
@end
|