├── simject ├── simject.plist ├── Makefile └── simject.m ├── simjectExampleTweak ├── simjectExampleTweak.plist ├── control ├── Makefile └── simjectExampleTweak.x ├── .gitignore ├── CydiaSubstrate.tbd ├── resim ├── Makefile └── resim.mm ├── LICENSE ├── Makefile ├── remount.sh ├── installsubstrate.sh └── README.md /simject/simject.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.apple.UIKit", "com.apple.springboard" ); }; } 2 | -------------------------------------------------------------------------------- /simjectExampleTweak/simjectExampleTweak.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.apple.springboard" ); }; } 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | */out/* 3 | debs/* 4 | *.deb 5 | *.dylib 6 | *.sublime-workspace 7 | _/* 8 | obj/* 9 | bin/* 10 | .DS_Store 11 | .theos 12 | -------------------------------------------------------------------------------- /simjectExampleTweak/control: -------------------------------------------------------------------------------- 1 | Package: net.angelxwind.simjectexampletweak 2 | Name: simject Example Tweak 3 | Depends: mobilesubstrate 4 | Version: 1.0 5 | Architecture: iphoneos-arm 6 | Description: An example of how to compile a tweak targeting the iOS Simulator. 7 | Maintainer: Karen/あけみ (angelXwind) 8 | Author: Karen/あけみ (angelXwind) 9 | Section: Tweaks 10 | -------------------------------------------------------------------------------- /CydiaSubstrate.tbd: -------------------------------------------------------------------------------- 1 | --- !tapi-tbd 2 | tbd-version: 4 3 | targets: [ armv7-ios, armv7s-ios, arm64-ios, arm64e-ios, i386-ios-simulator, x86_64-ios-simulator, arm64-ios-simulator ] 4 | install-name: '/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate' 5 | exports: 6 | - targets: [ armv7-ios, armv7s-ios, arm64-ios, arm64e-ios, i386-ios-simulator, x86_64-ios-simulator, arm64-ios-simulator ] 7 | symbols: [ _MSCloseImage, _MSDebug, _MSFindAddress, _MSFindSymbol, 8 | _MSGetImageByName, _MSHookClassPair, _MSHookFunction, 9 | _MSHookMemory, _MSHookMessageEx, _MSImageAddress, 10 | _MSMapImage ] 11 | ... 12 | -------------------------------------------------------------------------------- /resim/Makefile: -------------------------------------------------------------------------------- 1 | ifndef DYLIB_DIR 2 | DYLIB_DIR = /opt/simject 3 | endif 4 | 5 | ifndef ARCH 6 | ARCHFLAG = 7 | else 8 | ARCHFLAG = -arch $(ARCH) 9 | endif 10 | 11 | ifdef SYSROOT 12 | SYSROOTFLAG = -isysroot $(SYSROOT) 13 | endif 14 | 15 | clean:: 16 | -@rm -r out/ 17 | 18 | build : resim.mm 19 | @mkdir -p out 20 | @echo "$(PREFIX) Compiling resim for $(shell clang -print-target-triple)" 21 | @echo "$(SUB_PREFIX) Target directory is $(DYLIB_DIR)" 22 | @$(CXX) resim.mm $(ARCHFLAG) $(SYSROOTFLAG) -framework Foundation -DDYLIB_DIR=@\"$(DYLIB_DIR)\" -o out/resim 23 | @echo "$(SUB_PREFIX) Signing..." 24 | @codesign -f -s - out/resim 25 | @echo "$(SUB_PREFIX) Copying library..." 26 | @mkdir -p ../bin 27 | @cp out/resim ../bin 28 | -------------------------------------------------------------------------------- /simjectExampleTweak/Makefile: -------------------------------------------------------------------------------- 1 | # ARM Mac 2 | TARGET = simulator:clang::12.0 3 | ARCHS = arm64 4 | 5 | # Intel Mac 6 | # TARGET = simulator:clang::7.0 7 | # ARCHS = x86_64 i386 8 | # i386 slice is required for 32-bit iOS Simulator (iPhone 5, etc.) 9 | 10 | DEBUG = 0 11 | 12 | include $(THEOS)/makefiles/common.mk 13 | 14 | TWEAK_NAME = simjectExampleTweak 15 | simjectExampleTweak_FILES = simjectExampleTweak.x 16 | simjectExampleTweak_CFLAGS = -fobjc-arc -Wno-deprecated-declarations 17 | 18 | include $(THEOS_MAKE_PATH)/tweak.mk 19 | 20 | setup:: clean all 21 | @rm -f /opt/simject/$(TWEAK_NAME).dylib 22 | @cp -v $(THEOS_OBJ_DIR)/$(TWEAK_NAME).dylib /opt/simject/$(TWEAK_NAME).dylib 23 | @codesign -f -s - /opt/simject/$(TWEAK_NAME).dylib 24 | @cp -v $(PWD)/$(TWEAK_NAME).plist /opt/simject 25 | -------------------------------------------------------------------------------- /simjectExampleTweak/simjectExampleTweak.x: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | %hook SpringBoard 4 | -(void) applicationDidFinishLaunching:(id)arg { 5 | %orig(arg); 6 | if (@available(iOS 8.0, *)) { 7 | UIAlertController *lookWhatWorks = [UIAlertController alertControllerWithTitle:@"simject Example Tweak" 8 | message:@"It works! (ノ´ヮ´)ノ*:・゚✧" 9 | preferredStyle:UIAlertControllerStyleAlert]; 10 | UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; 11 | [lookWhatWorks addAction:ok]; 12 | [self.keyWindow.rootViewController presentViewController:lookWhatWorks animated:YES completion:nil]; 13 | } else { 14 | UIAlertView *lookWhatWorks = [[UIAlertView alloc] initWithTitle:@"simject Example Tweak" 15 | message:@"It works! (ノ´ヮ´)ノ*:・゚✧" 16 | delegate:self 17 | cancelButtonTitle:@"OK" 18 | otherButtonTitles:nil]; 19 | [lookWhatWorks show]; 20 | } 21 | } 22 | %end 23 | -------------------------------------------------------------------------------- /simject/Makefile: -------------------------------------------------------------------------------- 1 | ifndef DYLIB_DIR 2 | DYLIB_DIR = /opt/simject 3 | endif 4 | # shouldn't need more than one unless it's being packaged 5 | 6 | ifndef ARCH 7 | ARCHFLAG = 8 | else 9 | ARCHFLAG = -arch $(ARCH) 10 | endif 11 | 12 | ifndef SYSROOT 13 | SYSROOT = $(shell xcode-select -p)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 14 | endif 15 | 16 | ifndef TRIPLE 17 | TRIPLE = $(shell clang -print-target-triple) 18 | endif 19 | 20 | clean:: 21 | -@rm -r out/ 22 | 23 | build : simject.m 24 | @mkdir -p out 25 | @echo "$(PREFIX) Compiling simject for $(TRIPLE)" 26 | @echo "$(SUB_PREFIX) Using SDK at $(SYSROOT)" 27 | @echo "$(SUB_PREFIX) Target directory is $(DYLIB_DIR)" 28 | @$(CC) simject.m $(ARCHFLAG) -framework Foundation -isysroot $(SYSROOT) -target $(TRIPLE) -DDYLIB_DIR=@\"$(DYLIB_DIR)\" -dynamiclib -o out/simject.dylib 29 | @echo "$(SUB_PREFIX) Signing..." 30 | @codesign -f -s - out/simject.dylib 31 | @echo "$(SUB_PREFIX) Copying library..." 32 | @mkdir -p ../bin 33 | @cp out/simject.dylib ../bin 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2025, Karen/あけみ (angelXwind). 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PREFIX = \033[1;36m>>\033[0m 2 | export SUB_PREFIX = \033[1;36m>>>\033[0m 3 | export DONE_PREFIX = \033[1;32m>>\033[0m 4 | 5 | ifndef DYLIB_DIR 6 | DYLIB_DIR = /opt/simject 7 | endif 8 | 9 | PARENT = $(shell dirname $(DYLIB_DIR)) 10 | DD_NOT_EXISTS = $(shell test -d $(DYLIB_DIR); echo $$?) 11 | DD_NOT_WRITABLE = $(shell test -w $(PARENT); echo $$?) 12 | ifeq '$(DD_NOT_WRITABLE)' '1' 13 | NEED_ROOT = sudo 14 | NEED_ROOT_ASK = \033[1;32m>>\033[0m $(PARENT) is not writable. Using sudo. 15 | endif 16 | 17 | ifndef SYSROOT 18 | SYSROOT = $(shell xcode-select -p)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 19 | endif 20 | 21 | ifndef TRIPLE 22 | TRIPLE = $(shell clang -print-target-triple) 23 | endif 24 | 25 | all:: 26 | @make -C resim build 27 | @make -C simject build 28 | 29 | clean:: 30 | @rm -rfv bin 31 | @make -C resim clean 32 | @make -C simject clean 33 | 34 | setup:: all 35 | @test -w $(PARENT) || echo "$(NEED_ROOT_ASK)" 36 | @$(NEED_ROOT) mkdir -p $(DYLIB_DIR) 37 | @$(NEED_ROOT) chown -R $(USER) $(DYLIB_DIR) 38 | @echo "$(PREFIX) Copying Tweak Loader to $(DYLIB_DIR)" 39 | @rm -f $(DYLIB_DIR)/simject.dylib 40 | @cp bin/simject.dylib $(DYLIB_DIR) 41 | @cp simject/simject.plist $(DYLIB_DIR) 42 | @echo "$(PREFIX) Installing resim" 43 | @sudo mkdir -p /usr/local/bin 44 | @sudo cp bin/resim /usr/local/bin/resim 45 | @echo "$(DONE_PREFIX) Done. Place your tweak's dynamic libraries and accompanying property lists inside $(DYLIB_DIR) to load them in the iOS Simulator." 46 | @echo "$(DONE_PREFIX) To load/reload tweaks, run 'resim' in your terminal" 47 | -------------------------------------------------------------------------------- /remount.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # EXPERIMENTAL script 4 | # Based on https://github.com/EthanArbuckle/Tmpfs-Overlay 5 | 6 | # This tool needs to be run with sudo. It may need to be given Full Disk Access or Developer Tools permissions. 7 | 8 | # Function to mount tmpfs to the target directory 9 | mount_tmpfs_to_target_dir() { 10 | local target_dir_path="$1" 11 | if sudo mount_tmpfs "$target_dir_path"; then 12 | return 0 # success 13 | else 14 | echo "An error occurred while mounting tmpfs onto $target_dir_path" 15 | return 1 16 | fi 17 | } 18 | 19 | # Function to check if tmpfs is mounted on the target directory 20 | tmpfs_mounted_on_directory() { 21 | local target_dir_path="$1" 22 | mount_output=$(mount) 23 | if echo "$mount_output" | grep -q "tmpfs on $target_dir_path"; then 24 | return 0 25 | else 26 | return 1 27 | fi 28 | } 29 | 30 | # Function to set up an overlay on the target directory 31 | setup_overlay_on_directory() { 32 | local target_dir_path="$1" 33 | 34 | if [ ! -d "$target_dir_path" ]; then 35 | echo "Cannot set up overlay on non-existent directory: $target_dir_path" 36 | return 1 37 | fi 38 | 39 | if tmpfs_mounted_on_directory "$target_dir_path"; then 40 | echo "tmpfs is already mounted on $target_dir_path" 41 | return 0 # success (no need to mount again) 42 | fi 43 | 44 | # Create a temporary directory to copy contents to 45 | temp_dir=$(mktemp -d -p /tmp) 46 | if [ ! -d "$temp_dir" ]; then 47 | echo "Failed to create temporary directory" 48 | return 1 49 | fi 50 | echo "Created temporary directory: $temp_dir" 51 | 52 | # Copy contents of the target directory to the temporary directory 53 | echo "Copying contents of $target_dir_path to temporary directory" 54 | rsync -a "$target_dir_path"/ "$temp_dir"/ 55 | 56 | # Create a tmpfs mount at the target directory 57 | if ! mount_tmpfs_to_target_dir "$target_dir_path"; then 58 | return 1 59 | fi 60 | 61 | # Move the contents of the temporary directory back to the tmpfs mount 62 | echo "Moving contents back to $target_dir_path" 63 | sudo rsync -a --exclude='*fsevent*' "$temp_dir"/ "$target_dir_path"/ 64 | 65 | # Clean up the temporary directory 66 | rm -rf "$temp_dir" 67 | 68 | return 0 69 | } 70 | 71 | # Main script execution 72 | if [ $# -lt 1 ]; then 73 | echo "Usage: $0 " 74 | exit 1 75 | fi 76 | 77 | input_path="$1" 78 | 79 | if [ ! -d "$input_path" ]; then 80 | echo "Error: $input_path does not exist" 81 | exit 1 82 | fi 83 | 84 | if ! setup_overlay_on_directory "$input_path"; then 85 | echo "Failed to set up overlay on directory $input_path" 86 | exit 1 87 | fi 88 | 89 | echo "Successfully set up overlay on directory $input_path" 90 | -------------------------------------------------------------------------------- /simject/simject.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #define dylibDir DYLIB_DIR 6 | 7 | static NSArray *blackListForFLEX; 8 | 9 | NSArray *simjectGenerateDylibList() { 10 | NSString *processName = [[NSProcessInfo processInfo] processName]; 11 | // launchctl, you are a special case 12 | if ([processName isEqualToString:@"launchctl"]) { 13 | return nil; 14 | } 15 | // Create an array containing all the filenames in dylibDir (/opt/simject) 16 | NSError *e = nil; 17 | NSArray *dylibDirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dylibDir error:&e]; 18 | if (e) { 19 | return nil; 20 | } 21 | // Read current bundle identifier 22 | NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier; 23 | // We're only interested in the plist files 24 | NSArray *plists = [dylibDirContents filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF ENDSWITH %@", @"plist"]]; 25 | // Create an empty mutable array that will contain a list of dylib paths to be injected into the target process 26 | NSMutableArray *dylibsToInject = [NSMutableArray array]; 27 | // Loop through the list of plists 28 | for (NSString *plist in plists) { 29 | // Don't inject simject itself 30 | if ([plist isEqualToString:@"simject.plist"]) { 31 | continue; 32 | } 33 | // Check if the current dylib is FLEXible and if the current process is in the blacklist 34 | if (bundleIdentifier && [[plist uppercaseString] rangeOfString:@"FLEX" options:NSLiteralSearch].location != NSNotFound && [blackListForFLEX containsObject:bundleIdentifier]) { 35 | continue; 36 | } 37 | // We'll want to deal with absolute paths, so append the filename to dylibDir 38 | NSString *plistPath = [dylibDir stringByAppendingPathComponent:plist]; 39 | NSDictionary *filter = [NSDictionary dictionaryWithContentsOfFile:plistPath]; 40 | // This boolean indicates whether or not the dylib has already been injected 41 | BOOL isInjected = NO; 42 | // If supported iOS versions are specified within the plist, we check those first 43 | // Note: Whether or not the iOS versions are in doubles or strings, this shall pass, unlike CydiaSubstrate that expects doubles 44 | NSArray *supportedVersions = filter[@"Filter"][@"CoreFoundationVersion"]; 45 | if (supportedVersions) { 46 | if (supportedVersions.count != 1 && supportedVersions.count != 2) { 47 | continue; // Supported versions are in the wrong format, we should skip 48 | } 49 | if (supportedVersions.count == 1 && [supportedVersions[0] doubleValue] > kCFCoreFoundationVersionNumber) { 50 | continue; // Doesn't meet lower bound 51 | } 52 | if (supportedVersions.count == 2 && ([supportedVersions[0] doubleValue] > kCFCoreFoundationVersionNumber || [supportedVersions[1] doubleValue] <= kCFCoreFoundationVersionNumber)) { 53 | continue; // Outside bounds 54 | } 55 | } 56 | // Get the name of the dylib 57 | NSString *dylibName = [[plistPath stringByDeletingPathExtension] stringByAppendingString:@".dylib"]; 58 | // Decide whether or not to load the dylib based on the Bundles values 59 | for (NSString *entry in filter[@"Filter"][@"Bundles"]) { 60 | // Check to see whether or not this bundle is actually loaded in this application or not 61 | if (!CFBundleGetBundleWithIdentifier((CFStringRef)entry)) { 62 | // If not, skip it 63 | continue; 64 | } 65 | [dylibsToInject addObject:dylibName]; 66 | isInjected = YES; 67 | break; 68 | } 69 | if (!isInjected) { 70 | // Decide whether or not to load the dylib based on the Executables values 71 | for (NSString *process in filter[@"Filter"][@"Executables"]) { 72 | if ([process isEqualToString:processName]) { 73 | [dylibsToInject addObject:dylibName]; 74 | isInjected = YES; 75 | break; 76 | } 77 | } 78 | } 79 | if (!isInjected) { 80 | // Decide whether or not to load the dylib based on the Classes values 81 | for (NSString *clazz in filter[@"Filter"][@"Classes"]) { 82 | // Also check if this class is loaded in this application or not 83 | if (!NSClassFromString(clazz)) { 84 | // This class couldn't be loaded, skip 85 | continue; 86 | } 87 | // It's fine to add this dylib at this point 88 | [dylibsToInject addObject:dylibName]; 89 | isInjected = YES; 90 | break; 91 | } 92 | } 93 | } 94 | return dylibsToInject; 95 | } 96 | 97 | static __attribute__((constructor)) void SimjectInit (int argc, char **argv, char **envp) { 98 | // Since many iOS tweak developers use FLEXible (or some variant of that tweak) to inspect the iOS Simulator... 99 | // There are some processes that can crash when FLEXible is injected into them, significantly decreasing overall performance. 100 | // These processes do indeed load UIKit as a library, but they do not actually present a GUI so there is no point in injecting FLEXible into them. 101 | blackListForFLEX = @[@"com.apple.Search.framework", @"com.apple.accessibility.AccessibilityUIServer", @"com.apple.backboardd"]; 102 | // Inject any dylib meant to be run for this application 103 | for (NSString *dylib in simjectGenerateDylibList()) { 104 | BOOL success = dlopen([dylib UTF8String], RTLD_LAZY | RTLD_GLOBAL) != NULL; 105 | os_log_info(OS_LOG_DEFAULT, "Injecting %s into %s: %d.", [dylib UTF8String], [NSBundle.mainBundle.bundleIdentifier UTF8String], success); 106 | if (!success) os_log_error(OS_LOG_DEFAULT, "Couldn't inject %s into %s:\n%s.", [dylib UTF8String], [NSBundle.mainBundle.bundleIdentifier UTF8String], dlerror()); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /installsubstrate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [[ -z $1 ]] 6 | then 7 | echo -e "Error: Substrate type must be specified\n" 8 | echo -e "If you target iOS 12+ (of Xcode 10+), you must run the following:\n" 9 | echo -e "\t./installsubstrate.sh subst\n" 10 | echo -e "This will install Substitute\n" 11 | echo -e "Otherwise, you can run:\n" 12 | echo -e "\t./installsubstrate.sh cs\n" 13 | echo -e "This will install cycript's CydiaSubstrate\n" 14 | echo -e "If you only want to symlink CydiaSubstrate.framework to new iOS runtimes, you can run:\n" 15 | echo -e "\t./installsubstrate.sh link\n" 16 | echo -e "If you are developing simulator tweaks that utilize MSHookFunction, you can install the simulator-supported version of CydiaSubstrate.tbd (tbd v4) by running:\n" 17 | echo -e "\t./installsubstrate.sh theos\n" 18 | exit 1 19 | fi 20 | 21 | echo "You may be asked for the login password for sudo operations" 22 | 23 | SELF_DIR=$PWD 24 | 25 | SJ_RUNTIME_ROOT_PREFIX=/Library/Developer/CoreSimulator/Profiles/Runtimes 26 | SJ_RUNTIME_ROOT_10=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot 27 | SJ_RUNTIME_ROOT_10_BETA=/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot 28 | SJ_RUNTIME_ROOT_11=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot 29 | SJ_RUNTIME_ROOT_11_BETA=/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot 30 | 31 | SJ_PATH=/opt/simject 32 | SJ_FW_PATH=${SJ_PATH}/Frameworks 33 | mkdir -p ${SJ_FW_PATH} 34 | 35 | if [[ $1 = "theos" ]] 36 | then 37 | CS_FW_PATH="$THEOS/vendor/lib/CydiaSubstrate.framework" 38 | if [[ ! -d $CS_FW_PATH ]] 39 | then 40 | echo "Error: CydiaSubstrate.framework not found in ${CS_FW_PATH}" 41 | exit 1 42 | fi 43 | if [[ -f ${CS_FW_PATH}/CydiaSubstrate.tbd.bak ]] 44 | then 45 | echo "Notice: CydiaSubstrate.tbd has already been backed up, skipping" 46 | exit 0 47 | fi 48 | echo "Backing up CydiaSubstrate.tbd..." 49 | mv $CS_FW_PATH/CydiaSubstrate.tbd $CS_FW_PATH/CydiaSubstrate.tbd.bak 50 | echo "Copying the new CydiaSubstrate.tbd to $CS_FW_PATH..." 51 | cp $SELF_DIR/CydiaSubstrate.tbd $CS_FW_PATH/ 52 | exit 0 53 | elif [[ $1 = "link" ]] 54 | then 55 | cd ${SJ_FW_PATH} 56 | if [[ ! -d CydiaSubstrate.framework ]] 57 | then 58 | echo "Error: CydiaSubstrate.framework not found in ${SJ_FW_PATH}" 59 | exit 1 60 | fi 61 | elif [[ $1 = "subst" ]] 62 | then 63 | cd ${SJ_FW_PATH} 64 | echo "Installing Substitute..." 65 | rm -rf substitute CydiaSubstrate.framework 66 | git clone https://github.com/PoomSmart/substitute.git 67 | cd substitute/ 68 | ./configure --xcode-sdk=iphonesimulator --xcode-archs=$(uname -m) && make 69 | mv out/libsubstitute.dylib out/CydiaSubstrate 70 | codesign -f -s - out/CydiaSubstrate 71 | mkdir -p ../CydiaSubstrate.framework 72 | mv out/CydiaSubstrate ../CydiaSubstrate.framework/CydiaSubstrate 73 | cd .. && rm -rf substitute 74 | elif [[ $1 = "cs" ]] 75 | then 76 | cd ${SJ_FW_PATH} 77 | echo "Installing CydiaSubstrate..." 78 | rm -rf CydiaSubstrate.framework 79 | curl -Lo /tmp/simject_cycript.zip https://cache.saurik.com/cycript/mac/cycript_0.9.594.zip 80 | unzip /tmp/simject_cycript.zip -d /tmp/simject_cycript 81 | mkdir -p CydiaSubstrate.framework 82 | mv /tmp/simject_cycript/Cycript.lib/libsubstrate.dylib CydiaSubstrate.framework/CydiaSubstrate 83 | rm -rf /tmp/simject_cycript /tmp/simject_cycript.zip 84 | else 85 | echo "Error: Unrecognized substrate type (${1}), exiting" 86 | exit 1 87 | fi 88 | 89 | echo "Symlinking CydiaSubstrate.framework for all installed iOS runtimes..." 90 | 91 | if [[ -d "${SJ_RUNTIME_ROOT_10}" ]] 92 | then 93 | echo "Symlinking to ${SJ_RUNTIME_ROOT_10}" 94 | sudo mkdir -p "${SJ_RUNTIME_ROOT_10}/Library/Frameworks" 95 | sudo mkdir -p "${SJ_RUNTIME_ROOT_10}/Library/MobileSubstrate" 96 | sudo rm -rf "${SJ_RUNTIME_ROOT_10}/Library/Frameworks/CydiaSubstrate.framework" 97 | sudo ln -s ${SJ_FW_PATH}/CydiaSubstrate.framework "${SJ_RUNTIME_ROOT_10}/Library/Frameworks/" 98 | sudo rm -rf "${SJ_RUNTIME_ROOT_10}/Library/MobileSubstrate/DynamicLibraries" 99 | sudo ln -s ${SJ_PATH} "${SJ_RUNTIME_ROOT_10}/Library/MobileSubstrate/DynamicLibraries" 100 | fi 101 | 102 | if [[ -d "${SJ_RUNTIME_ROOT_10_BETA}" ]] 103 | then 104 | echo "Symlinking to ${SJ_RUNTIME_ROOT_10_BETA}" 105 | sudo mkdir -p "${SJ_RUNTIME_ROOT_10_BETA}/Library/Frameworks" 106 | sudo mkdir -p "${SJ_RUNTIME_ROOT_10_BETA}/Library/MobileSubstrate" 107 | sudo rm -rf "${SJ_RUNTIME_ROOT_10_BETA}/Library/Frameworks/CydiaSubstrate.framework" 108 | sudo ln -s ${SJ_FW_PATH}/CydiaSubstrate.framework "${SJ_RUNTIME_ROOT_10_BETA}/Library/Frameworks" 109 | sudo rm -rf "${SJ_RUNTIME_ROOT_10_BETA}/Library/MobileSubstrate/DynamicLibraries" 110 | sudo ln -s ${SJ_PATH} "${SJ_RUNTIME_ROOT_10_BETA}/Library/MobileSubstrate/DynamicLibraries" 111 | fi 112 | 113 | if [[ -d "${SJ_RUNTIME_ROOT_11}" ]] 114 | then 115 | echo "Symlinking to ${SJ_RUNTIME_ROOT_11}" 116 | sudo mkdir -p "${SJ_RUNTIME_ROOT_11}/Library/Frameworks" 117 | sudo mkdir -p "${SJ_RUNTIME_ROOT_11}/Library/MobileSubstrate" 118 | sudo rm -rf "${SJ_RUNTIME_ROOT_11}/Library/Frameworks/CydiaSubstrate.framework" 119 | sudo ln -s ${SJ_FW_PATH}/CydiaSubstrate.framework "${SJ_RUNTIME_ROOT_11}/Library/Frameworks" 120 | sudo rm -rf "${SJ_RUNTIME_ROOT_11}/Library/MobileSubstrate/DynamicLibraries" 121 | sudo ln -s ${SJ_PATH} "${SJ_RUNTIME_ROOT_11}/Library/MobileSubstrate/DynamicLibraries" 122 | fi 123 | 124 | if [[ -d "${SJ_RUNTIME_ROOT_11_BETA}" ]] 125 | then 126 | echo "Symlinking to ${SJ_RUNTIME_ROOT_11_BETA}" 127 | sudo mkdir -p "${SJ_RUNTIME_ROOT_11_BETA}/Library/Frameworks" 128 | sudo mkdir -p "${SJ_RUNTIME_ROOT_11_BETA}/Library/MobileSubstrate" 129 | sudo rm -rf "${SJ_RUNTIME_ROOT_11_BETA}/Library/Frameworks/CydiaSubstrate.framework" 130 | sudo ln -s ${SJ_FW_PATH}/CydiaSubstrate.framework "${SJ_RUNTIME_ROOT_11_BETA}/Library/Frameworks" 131 | sudo rm -rf "${SJ_RUNTIME_ROOT_11_BETA}/Library/MobileSubstrate/DynamicLibraries" 132 | sudo ln -s ${SJ_PATH} "${SJ_RUNTIME_ROOT_11_BETA}/Library/MobileSubstrate/DynamicLibraries" 133 | fi 134 | 135 | if [[ -d "${SJ_RUNTIME_ROOT_PREFIX}" ]] 136 | then 137 | OIFS="$IFS" 138 | IFS=$'\n' 139 | for SJ_runtime in $(find ${SJ_RUNTIME_ROOT_PREFIX} -type d -maxdepth 1 -name "*.simruntime") 140 | do 141 | echo "Symlinking to ${SJ_runtime}" 142 | sudo mkdir -p "${SJ_runtime}/Contents/Resources/RuntimeRoot/Library/Frameworks" 143 | sudo mkdir -p "${SJ_runtime}/Contents/Resources/RuntimeRoot/Library/MobileSubstrate" 144 | sudo rm -rf "${SJ_runtime}/Contents/Resources/RuntimeRoot/Library/Frameworks/CydiaSubstrate.framework" 145 | sudo ln -s "${SJ_FW_PATH}/CydiaSubstrate.framework" "${SJ_runtime}/Contents/Resources/RuntimeRoot/Library/Frameworks" 146 | sudo rm -rf "${SJ_runtime}/Contents/Resources/RuntimeRoot/Library/MobileSubstrate/DynamicLibraries" 147 | sudo ln -s ${SJ_PATH} "${SJ_runtime}/Contents/Resources/RuntimeRoot/Library/MobileSubstrate/DynamicLibraries" 148 | done 149 | IFS="$OIFS" 150 | fi 151 | 152 | SJ_VOLUMES=/Library/Developer/CoreSimulator/Volumes 153 | if [ -d "${SJ_VOLUMES}" ] 154 | then 155 | OIFS="$IFS" 156 | IFS=$'\n' 157 | for SJ_volume in $(find ${SJ_VOLUMES} -type d -maxdepth 1 -name "iOS_*") 158 | do 159 | RUNTIME_ROOT=${SJ_volume}${SJ_RUNTIME_ROOT_PREFIX}/*.simruntime/Contents/Resources/RuntimeRoot 160 | echo "Remounting ${RUNTIME_ROOT}/Library as read-write..." 161 | sh $SELF_DIR/remount.sh ${RUNTIME_ROOT}/Library || echo 'Continuing...' 162 | cd ${RUNTIME_ROOT}/Library 163 | LIBRARY_PATH=$(pwd) 164 | FRAMEWORK_PATH=${LIBRARY_PATH}/Frameworks 165 | echo "Symlink to ${SJ_volume}" 166 | rm -rf "$FRAMEWORK_PATH/CydiaSubstrate.framework" 167 | ln -s ${SJ_FW_PATH}/CydiaSubstrate.framework "$FRAMEWORK_PATH/" 168 | mkdir -p "$LIBRARY_PATH/MobileSubstrate" 169 | rm -rf "$LIBRARY_PATH/MobileSubstrate/DynamicLibraries" 170 | ln -s ${SJ_PATH} "$LIBRARY_PATH/MobileSubstrate/DynamicLibraries" 171 | done 172 | IFS="$OIFS" 173 | fi 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simject 2 | 3 | simject is a command-line tool and iOS dynamic library that allows iOS tweak developers to easily test their tweaks on the iOS Simulator. 4 | 5 | simject is BSD-licensed. See `LICENSE` for more information. 6 | 7 | ## Alternative: simulator-trainer for Better Developer Experience 8 | 9 | **For a more streamlined development experience**, consider using [simulator-trainer](https://github.com/EthanArbuckle/simulator-trainer) instead. simulator-trainer offers significant improvements over simject: 10 | 11 | - **No need to build tweaks with simulator target** - You can use your regular device tweaks 12 | - **Drag & drop installation** - Simply drag .deb files onto the simulator window to install tweaks 13 | - **No CLI setup required** - No need for command-line tools like `resim` 14 | - **Automatic binary conversion** - Non-simulator binaries are automatically converted during installation 15 | - **Built-in debugging tools** - Includes objc_msgSend tracing and cycript integration 16 | - **Simpler workflow** - Just boot a simulator with simulator-trainer and drop your tweak files 17 | 18 | simulator-trainer turns the iOS Simulator into a more powerful development environment by mounting writable overlays and providing in-process tweak injection, making the development process much more user-friendly. 19 | 20 | **If you prefer the traditional approach or need more control over the setup process**, continue with simject below. 21 | 22 | ### Setting up the simject environment 23 | 24 | 1. Ensure that you have Xcode installed and ran at least once. 25 | 2. Ensure that you have added your developer account in `Xcode > Preferences > Accounts` tab. This is required for code-signing some binaries used in simject. 26 | 3. Ensure that there is `iOS Development` certificate listed once you clicked on `Manage Certificates` button, create one if there is not. 27 | 4. Ensure that Terminal has Full Disk Access permission if you are on macOS Catalina or later. You can do this by going to `System Preferences > Security & Privacy > Privacy tab > Full Disk Access` and adding Terminal to the list. 28 | 5. Run these commands in a Terminal instance: 29 | 30 | ``` 31 | sudo xcode-select -s /Applications/Xcode.app 32 | git clone https://github.com/akemin-dayo/simject.git 33 | cd simject/ 34 | make setup 35 | ``` 36 | 37 | **Note:** During the process, you may be asked by `sudo` to enter your login password. Please note that it is normal for nothing to be displayed as you type your password. 38 | 39 | Now, we need to create a version of `CydiaSubstrate.framework` that has support for the `x86_64` or `arm64` (64-bit) and/or `i386` (32-bit) architectures. 40 | 41 | ### Getting Cydia Substrate to function properly with simject 42 | 43 | TL;DR 44 | 45 | ```bash 46 | # First time installation 47 | ./installsubstrate.sh subst # Install Substitute 48 | # ./installsubstrate.sh cs # Install Cydia Substrate 49 | 50 | # When you added a new simulator 51 | # Or, when tmpfs is unmounted 52 | ./installsubstrate.sh link 53 | ``` 54 | 55 | **Unless you want to do it manually**, you can use [`installsubstrate.sh`](./installsubstrate.sh) script to symlink `CydiaSubstrate.framework` to the appropriate directory **to every iOS runtime**. You will be asked to input your password for certain `sudo` operations. Otherwise, continue reading. 56 | 57 | If you use Xcode 10 (and above) and target iOS 12 (and above), you need to rely on [substitute](https://github.com/sbingner/substitute) rather than cycript's included `CydiaSubstrate.framework`. 58 | 59 | This is due to the incompatible `libstdc++` in iOS 12 simulator of Xcode 10. The Cydia Substrate from cycript that requires `libstdc++` will not work, but will complain `/usr/lib/libstdc++.6.dylib: mach-o, but not built for iOS simulator`. You can run the following commands to compile a working substitute framework: 60 | 61 | ``` 62 | # Xcode 10+ and iOS 12+ 63 | cd simject/ 64 | git clone https://github.com/PoomSmart/substitute.git 65 | cd substitute/ 66 | ./configure --xcode-sdk=iphonesimulator --xcode-archs=$(uname -m) && make 67 | mv -v out/libsubstitute.dylib out/CydiaSubstrate 68 | codesign -f -s - out/CydiaSubstrate 69 | mkdir -p ../CydiaSubstrate.framework 70 | mv -v out/CydiaSubstrate ../CydiaSubstrate.framework/CydiaSubstrate 71 | cd .. && rm -rf substitute 72 | ``` 73 | Otherwise, run these commands to compile a working Cydia Substrate: 74 | 75 | ``` 76 | # Xcode 9 and below 77 | cd simject/ 78 | curl -Lo /tmp/simject_cycript.zip https://cache.saurik.com/cycript/mac/cycript_0.9.594.zip 79 | unzip /tmp/simject_cycript.zip -d /tmp/simject_cycript 80 | mkdir -p CydiaSubstrate.framework 81 | mv -v /tmp/simject_cycript/Cycript.lib/libsubstrate.dylib CydiaSubstrate.framework/CydiaSubstrate 82 | rm -rfv /tmp/simject_cycript /tmp/simject_cycript.zip 83 | ``` 84 | 85 | 1. (For iOS 15 runtimes and earlier) Copy the resulting `CydiaSubstrate.framework` bundle to `/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS${SDK_VERSION}.simruntime/Contents/Resources/RuntimeRoot/Library/Frameworks/` where `${SDK_VERSION}` is your desired SDK version. If `/Library/Frameworks` does not exist, you can create it manually. 86 | * Let `${XCODE}` be `Xcode` or `Xcode-beta`. 87 | * For iOS 16+ runtimes, the destination directory is mounted as read-only. It is not possible to write to it directly unless one utilizes `tmpfs` technique. Use `./installsubstrate.sh link` to create tmpfs overlay for these runtimes. 88 | * For runtimes bundled in Xcode 11+, copy the framework to `/Applications/${XCODE}.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/Library/Frameworks/`. 89 | * For runtimes bundled in Xcode 9 - 10, copy the framework to `/Applications/${XCODE}.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/Library/Frameworks/`. 90 | 91 | 2. Note that, from the step 1, you can symlink instead of copying `CydiaSubstrate.framework` to multiple places. This is what `installsubstrate.sh` does to ease your life. 92 | 93 | 3. By default, tweaks built for simulator will not use CydiaSubstrate as logos is configured to generate hooking code using only internal Objective-C runtime methods. While hooking via `%hook` works regardless of generators, hooking via `%hookf` is not supported unless you explicitly specify the logos generator to be `MobileSubstrate` (or `libhooker`) by using [%config](https://theos.dev/docs/logos-syntax). For example, if you want to use CydiaSubstrate for hooking, add `%config(generator=MobileSubstrate)` to your `Tweak.x(m)`. Lastly, to build simulator tweaks successfully, copy `CydiaSubstrate.tbd` from this project to `$THEOS/vendor/lib/CydiaSubstrate.framework` and replace the existing one. Make a backup if necessary. Here's the command that does that: `./installsubstrate.sh theos`. 94 | 95 | ### simject usage 96 | 97 | 1. Place your dynamic libraries and accompanying property lists inside `/opt/simject` to load them in the iOS Simulator. Do not delete `simject.plist` and `simject.dylib`. 98 | 99 | 2. Execute `resim` command-line tool to make booted iOS Simulator(s) respring and be able to load tweaks. 100 | 101 | 3. IMPORTANT: Please note that you will need to run `resim` every time the iOS Simulator reboots or if SpringBoard crashes by itself. 102 | 103 | 4. `resim` can respring multiple simulators (check its usage notes), provided that the selected Xcode version is 9.0 or above. 104 | 105 | 5. Happy developing! (And don't make SpringBoard cry *too* hard... it has feelings, too! Probably.) 106 | 107 | ### Targeting the iOS Simulator 108 | 109 | 1. Open your project's `Makefile`. 110 | 111 | 2. Set the correct `TARGET` for your machine. 112 | * **For Apple Silicon Mac:** Set `TARGET = simulator:clang::12.0`. 113 | * **For Intel Mac:** Set `TARGET = simulator:clang::7.0` (a must for Xcode 10, with 64-bit compatibility) or `TARGET = simulator:clang::5.0` otherwise (with 32-bit compatibility). Note that this is just an example, you can change the SDK version used and iOS version to target if you wish. 114 | 115 | 3. Set the correct `ARCHS` for your machine. 116 | * **For Apple Silicon Mac:** Set `ARCHS = arm64`. There is no 32-bit support. 117 | * **For Intel Mac:** If you want to support both 64-bit and 32-bit iOS Simulators, set `ARCHS = x86_64 i386` to your Makefile. If not (or if you're targeting iOS 11), set `ARCHS = x86_64`. 118 | 119 | 4. `make` your project and copy `.theos/obj/iphone_simulator/${YOUR_TWEAK}.dylib` to `/opt/simject/${YOUR_TWEAK}.dylib`. 120 | 121 | 5. If there is already `/opt/simject/${YOUR_TWEAK}.dylib`, you have to delete it first before copying. 122 | 123 | 6. Also make sure to copy `${YOUR_TWEAK}.plist` to `/opt/simject/${YOUR_TWEAK}.plist`. simject will not load your tweak if you miss this step! 124 | 125 | 7. An example tweak project is available in the `simjectExampleTweak/` subfolder. Use it as reference if you want. Its Makefile is also written so that you can just run `make setup` to copy over the dylib and its plist automatically. 126 | 127 | 8. If you encountered a problem while signing dylibs: `error: The specified item could not be found in the keychain.`, you can try to fix it by following [this](https://github.com/angelXwind/simject/issues/43#issuecomment-441659203) or [this](https://github.com/angelXwind/simject/issues/42#issuecomment-440920466). 128 | 129 | 9. If you encountered a problem while signing dylibs: `iPhone Developer: no identity found`, check out [this comment by iAlex11](https://github.com/angelXwind/simject/issues/55#issuecomment-615894757) (a neat way to get your identity is to run `security find-identity -p codesigning -v | head -n 1 | xargs | cut -d " " -f 2`). 130 | 131 | ### Final notes 132 | 133 | Please understand that that testing your tweaks on the iOS Simulator is not necessarily a 100% accurate representation of how it will behave on an actual iOS device. 134 | 135 | Certain features and frameworks are usually missing from or incomplete in the iOS Simulator (such as anything related to the Weather frameworks). 136 | 137 | Yes, in *most* cases, it will work identically across both the iOS Simulator and a real iOS device, but there will always be some edge cases where this does not apply. 138 | 139 | For those who want to test their preference bundle inside the simulator, you need to compile a simulator version of preferenceloader and do some extra steps that you can consult from [here](https://github.com/PoomSmart/preferenceloader-sim). It may not be as convenient as using only simject, but it works, at least. 140 | 141 | Also, special thanks to [PoomSmart](https://github.com/PoomSmart) for his countless contributions to the simject project! Without him, I would have never even had the idea of creating such a tool. 142 | -------------------------------------------------------------------------------- /resim/resim.mm: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | BOOL iOS7 = NO; 16 | BOOL didHaveGlobalHeader = NO; 17 | 18 | void globalHeader() { 19 | if (!didHaveGlobalHeader) { 20 | printf("resim (C) 2016-2022 Karen/あけみ (angelXwind)\n"); 21 | didHaveGlobalHeader = YES; 22 | } 23 | } 24 | 25 | void printUsage() { 26 | globalHeader(); 27 | printf("\nUsage: resim [options]\n"); 28 | printf("Example: resim -d \"iPhone 5\" -v 8.1\n"); 29 | printf("\nAvailable options:\n"); 30 | printf("\t-h Shows this usage dialog\n"); 31 | printf("\t-d Specifies a device type\n"); 32 | printf("\t-v Specifies an iOS version\n"); 33 | printf("\t-i Specifies a UUID corresponding to a specific iOS Simulator\n"); 34 | #ifdef IOS7_SUPPORT 35 | printf("\t-l Enables iOS 7 compatibility mode (simject will not work with the iOS 7 runtime without this)\n"); 36 | #endif 37 | printf("\tall Resprings all booted iOS Simulators\n"); 38 | printf("\nExample usages:\n"); 39 | printf("\tRespring a booted device matching the specified device type and iOS version\n"); 40 | printf("\t\tresim -d \"iPhone 5\" -v 8.1\n"); 41 | printf("\tRespring any booted devices matching the specified iOS version\n"); 42 | printf("\t\tresim -v 8.1\n"); 43 | printf("\tRespring any booted devices matching the specified device type\n"); 44 | printf("\t\tresim -d \"iPhone 5\"\n"); 45 | printf("\tRespring a booted device with the specified UUID\n"); 46 | printf("\t\tresim -i 5AA1C45D-DB69-4C52-A75B-E9BE9C7E7770\n"); 47 | printf("\tRespring all booted iOS Simulators\n"); 48 | printf("\t\tresim all\n"); 49 | #ifdef IOS7_SUPPORT 50 | printf("\tRespring an iOS Simulator using the iOS 7 runtime (Xcode <= 6.2)\n"); 51 | printf("\t\tresim -l\n"); 52 | #endif 53 | } 54 | 55 | void safe_system(const char *cmd) { 56 | int status = system(cmd); 57 | if (WEXITSTATUS(status) != EXIT_SUCCESS) { 58 | printf("Error executing command, exiting.\n"); 59 | exit(EXIT_FAILURE); 60 | } 61 | } 62 | 63 | string exec(const char *cmd) { 64 | array buffer; 65 | string result = ""; 66 | shared_ptr pipe(popen(cmd, "r"), pclose); 67 | if (pipe) { 68 | while (!feof(pipe.get())) { 69 | if (fgets(buffer.data(), 128, pipe.get()) != NULL) { 70 | result += buffer.data(); 71 | } 72 | } 73 | } 74 | return result; 75 | } 76 | 77 | void injectHeader() { 78 | globalHeader(); 79 | printf("Injecting appropriate dynamic libraries from %s...\n", [DYLIB_DIR UTF8String]); 80 | } 81 | 82 | string anyBootedDevices() { 83 | string bootedDevices = exec("xcrun simctl list devices available | grep -E Booted | sed \"s/^[ \\t]*//\""); 84 | return bootedDevices; 85 | } 86 | 87 | void inject(const char *uuid, const char *device, const char *version, BOOL _exit, BOOL keepRunning, BOOL checkBooted) { 88 | if (uuid == NULL) { 89 | printf("ERROR: UUID is null, cannot continue.\n"); 90 | exit(EXIT_FAILURE); 91 | } 92 | if (checkBooted && anyBootedDevices().length() == 0) { 93 | printf("Error: No booted iOS Simulators were found.\n"); 94 | exit(EXIT_FAILURE); 95 | } 96 | if (device) { 97 | if (version) 98 | printf("Respringing %s (%s, %s) ...\n", uuid, device, version); 99 | else 100 | printf("Respringing %s (%s) ...\n", uuid, device); 101 | } else { 102 | printf("Respringing %s ...\n", !strcmp(uuid, "booted") ? "a booted device" : uuid); 103 | } 104 | if (fork() == 0) { 105 | #ifdef IOS7_SUPPORT 106 | if (iOS7) { 107 | if (!strcmp(uuid, "booted")) { 108 | string suuid = exec("xcrun simctl list devices | grep -E Booted | grep -oE \"\\([A-Z0-9\\-]+\\)\" | sed \"s/[()]//g\""); 109 | if (!suuid.empty()) { 110 | suuid.erase(suuid.length() - 1); 111 | } 112 | uuid = strdup(suuid.c_str()); 113 | } 114 | safe_system([[NSString stringWithFormat:@"plutil -replace bootstrap.child.DYLD_INSERT_LIBRARIES -string %@/simject.dylib %@/Library/Developer/CoreSimulator/Devices/%@/data/var/run/launchd_bootstrap.plist -s", DYLIB_DIR, NSHomeDirectory(), @(uuid)] UTF8String]); 115 | safe_system("killall launchd_sim"); 116 | } else { 117 | #endif 118 | safe_system([[NSString stringWithFormat:@"xcrun simctl spawn %s launchctl setenv DYLD_INSERT_LIBRARIES %@/simject.dylib", uuid, DYLIB_DIR] UTF8String]); 119 | safe_system([[NSString stringWithFormat:@"xcrun simctl spawn %s launchctl setenv __XPC_DYLD_INSERT_LIBRARIES %@/simject.dylib", uuid, DYLIB_DIR] UTF8String]); 120 | safe_system([[NSString stringWithFormat:@"export SIMCTL_CHILD_DYLD_INSERT_LIBRARIES=%@/simject.dylib; xcrun simctl spawn %s launchctl stop com.apple.backboardd", DYLIB_DIR, uuid] UTF8String]); 121 | #ifdef IOS7_SUPPORT 122 | } 123 | #endif 124 | exit(EXIT_SUCCESS); 125 | } else { 126 | if (_exit && !keepRunning) { 127 | exit(EXIT_SUCCESS); 128 | } 129 | } 130 | } 131 | 132 | void injectUUIDs(const char *uuid, BOOL all) { 133 | string bootedDevices = anyBootedDevices(); 134 | if (!bootedDevices.length()) { 135 | printf("Error: No booted iOS Simulators were found (%s).\n", uuid); 136 | exit(EXIT_FAILURE); 137 | } 138 | regex p("(.+) \\(([A-Z0-9\\-]+)\\) \\(Booted\\)"); 139 | smatch m; 140 | BOOL foundAny = NO; 141 | injectHeader(); 142 | while (regex_search(bootedDevices, m, p)) { 143 | const char *bootedUUID = strdup(m[2].str().c_str()); 144 | if (all || (uuid && !strcmp(bootedUUID, uuid))) { 145 | char *bootedDevice = strdup(m[1].str().c_str()); 146 | inject(bootedUUID, bootedDevice, NULL, NO, NO, NO); 147 | free(bootedDevice); 148 | foundAny = YES; 149 | } 150 | bootedDevices = m.suffix().str(); 151 | } 152 | if (!foundAny) { 153 | printf("Error: No booted iOS Simulators with an UUID matching the specified UUID was found.\n"); 154 | } 155 | exit(foundAny ? EXIT_SUCCESS : EXIT_FAILURE); 156 | } 157 | 158 | NSString *XcodePath() { 159 | char buffer[128]; 160 | size_t len = readlink("/var/db/xcode_select_link", buffer, 128); 161 | return len ? [NSString stringWithUTF8String:buffer] : @"/Applications/Xcode.app"; 162 | } 163 | 164 | double XcodeVersion() { 165 | NSBundle *simulatorBundle = [NSBundle bundleWithPath:[XcodePath() stringByAppendingPathComponent:@"/Applications/Simulator.app/"]]; 166 | if (simulatorBundle == nil) { 167 | simulatorBundle = [NSBundle bundleWithPath:[XcodePath() stringByAppendingPathComponent:@"/Applications/iOS Simulator.app/"]]; 168 | } 169 | return [[simulatorBundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey] doubleValue]; 170 | } 171 | 172 | NSArray *getRuntime(NSDictionary *devices, NSString *key, const char *version) { 173 | NSArray *runtime = key && !version ? devices[key] : devices[[NSString stringWithFormat:@"iOS %s", version]]; 174 | if (runtime == nil && version) { 175 | char *version2 = strdup(version); 176 | version2[strlen(version2) - 2] = '-'; 177 | if (key && ![key containsString:[NSString stringWithUTF8String:version2]]) { 178 | free(version2); 179 | return nil; 180 | } 181 | runtime = devices[[NSString stringWithFormat:@"com.apple.CoreSimulator.SimRuntime.iOS-%s", version2]]; 182 | free(version2); 183 | } 184 | if (runtime == nil || runtime.count == 0) { 185 | printf("ERROR: iOS %s runtime is not installed, or not supported by the currently installed simctl\n", version); 186 | return nil; 187 | } 188 | return runtime; 189 | } 190 | 191 | void injectToRuntime(NSDictionary *devices, NSArray *runtime, const char *device, const char *version, BOOL keepRunning) { 192 | if (runtime == nil) 193 | return; 194 | for (NSDictionary *entry in runtime) { 195 | const char *state = [entry[@"state"] UTF8String]; 196 | const char *name = [entry[@"name"] UTF8String]; 197 | const char *uuid = [entry[@"udid"] UTF8String]; 198 | bool passed = device ? !strcmp(name, device) : true; 199 | if (passed) { 200 | if (strcmp(state, "Booted")) { 201 | if (version) 202 | printf("ERROR: This device (%s, %s, %s) has not yet booted up.\n", name, version, uuid); 203 | else 204 | printf("ERROR: This device (%s, %s) has not yet booted up.\n", name, uuid); 205 | } else { 206 | injectHeader(); 207 | inject(uuid, name, version, YES, keepRunning, NO); 208 | } 209 | } 210 | } 211 | } 212 | 213 | int main(int argc, char *const argv[]) { 214 | double xcodeVersion = XcodeVersion(); 215 | #ifdef IOS7_SUPPORT 216 | iOS7 = xcodeVersion && (xcodeVersion < 600.0); // Should be around where Xcode 6.2 is (and only on Mavericks) 217 | #endif 218 | if (argc == 2) { 219 | if (!strcmp(argv[1], "all")) { 220 | injectUUIDs(NULL, YES); 221 | } else if (!strcmp(argv[1], "help")) { 222 | printUsage(); 223 | exit(EXIT_SUCCESS); 224 | } 225 | } 226 | int opt; 227 | char *device = NULL, *version = NULL, *uuid = NULL; 228 | int deviceFlag = 0, versionFlag = 0, uuidFlag = 0; 229 | #ifdef IOS7_SUPPORT 230 | const char *opts = "d:v:i:lh"; 231 | #else 232 | const char *opts = "d:v:i:h"; 233 | #endif 234 | while ((opt = getopt(argc, argv, opts)) != -1) { 235 | switch (opt) { 236 | case 'd': 237 | device = strdup(optarg); 238 | if (*device == '-') { 239 | free(device); 240 | device = NULL; 241 | printf("ERROR: iOS Simulator device parameter was entered incorrectly.\n"); 242 | } 243 | deviceFlag = 1; 244 | break; 245 | case 'v': { 246 | if (!regex_match(version = strdup(optarg), regex("\\d+\\.\\d"))) { 247 | free(version); 248 | version = NULL; 249 | printf("ERROR: iOS version was entered incorrectly.\n"); 250 | } 251 | versionFlag = 1; 252 | break; 253 | } 254 | case 'i': 255 | if (!regex_match(uuid = strdup(optarg), regex("[A-Z0-9]{8}\\-[A-Z0-9]{4}\\-[A-Z0-9]{4}\\-[A-Z0-9]{4}\\-[A-Z0-9]{12}"))) { 256 | free(uuid); 257 | uuid = NULL; 258 | printf("ERROR: UUID was entered incorrectly.\n"); 259 | } 260 | uuidFlag = 1; 261 | break; 262 | #ifdef IOS7_SUPPORT 263 | case 'l': 264 | iOS7 = YES; 265 | break; 266 | #endif 267 | case 'h': 268 | printUsage(); 269 | exit(EXIT_SUCCESS); 270 | default: 271 | printUsage(); 272 | exit(EXIT_FAILURE); 273 | } 274 | } 275 | if (uuidFlag || deviceFlag || versionFlag || iOS7) { 276 | if (xcodeVersion && xcodeVersion < 800.0) { 277 | printf("WARNING: The selected Xcode version does not support multiple running iOS Simulator instances simultaneously. Booting this device may cause any other existing instances of the iOS Simulator to terminate.\n"); 278 | } 279 | if (!(uuidFlag != (deviceFlag || versionFlag)) && !iOS7) { 280 | printUsage(); 281 | exit(EXIT_FAILURE); 282 | } 283 | } 284 | if (!uuidFlag && !deviceFlag && !versionFlag) { 285 | injectHeader(); 286 | inject("booted", NULL, NULL, YES, NO, YES); 287 | } 288 | if (uuidFlag) { 289 | injectUUIDs(uuid, NO); 290 | } else { 291 | NSString *devicesString = [NSString stringWithUTF8String:exec("xcrun simctl list -j devices available").c_str()]; 292 | NSError *error = nil; 293 | NSDictionary *devices = [NSJSONSerialization JSONObjectWithData:[devicesString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error][@"devices"]; 294 | if (error || devices == nil) { 295 | printf("ERROR: Could not list available iOS Simulator devices\n"); 296 | exit(EXIT_FAILURE); 297 | } 298 | if (versionFlag && deviceFlag) { 299 | injectToRuntime(devices, getRuntime(devices, nil, version), device, version, NO); 300 | } else { 301 | for (NSString *key in devices) { 302 | if (![key containsString:@"iOS"]) 303 | continue; 304 | injectToRuntime(devices, getRuntime(devices, key, version), device, version, YES); 305 | } 306 | exit(EXIT_SUCCESS); 307 | } 308 | } 309 | printf("ERROR: Could not find any booted iOS Simulator devices with the specified parameters.\n"); 310 | exit(EXIT_FAILURE); 311 | } 312 | --------------------------------------------------------------------------------