├── SimBooter ├── SimIndigoHIDServer │ ├── shared.h │ ├── README.md │ ├── FBSimulatorControl │ │ └── HID │ │ │ ├── FBSimulatorHID+Struct26.h │ │ │ ├── FBSimulatorHID+Struct26.m │ │ │ ├── FBSimulatorIndigoHID.h │ │ │ ├── FBSimulatorHID.h │ │ │ ├── FBSimulatorHIDEvent.h │ │ │ ├── FBSimulatorIndigoHID.m │ │ │ ├── FBSimulatorHID.m │ │ │ └── FBSimulatorHIDEvent.m │ ├── hook.x │ ├── Indigo26.h │ ├── main.m │ ├── Indigo.h │ └── FBControlCore │ │ └── Async │ │ ├── FBFuture.h │ │ └── FBFuture.m ├── Makefile └── main.m ├── .gitignore ├── main.m ├── vtool_and_sign.sh ├── control ├── simxpctest ├── Makefile └── main.m ├── launchd_sim_trampoline_hook ├── Makefile └── trampoline_hook.m ├── launchd_sim_hook ├── Makefile └── launchd_sim_hook.m ├── layout └── DEBIAN │ └── postinst ├── Makefile ├── SimFramebuffer ├── SimFramebuffer.h ├── Makefile ├── Resources │ └── Info.plist └── SimFramebuffer.m ├── FakeMobileCoreServices ├── Makefile ├── Resources │ └── Info.plist └── main.m ├── SimRenderServer ├── Makefile ├── main.m ├── Resources │ └── Info.plist └── hook.x ├── README.md ├── LICENSE ├── IOMobileFramebuffer.h ├── tmp.h ├── shared.h ├── PATCHING.md ├── shared.m ├── dyld.diff ├── SimBootProcess.md ├── Frameworks └── IOMobileFramebuffer.framework │ └── IOMobileFramebuffer.tbd └── entitlements.plist /SimBooter/SimIndigoHIDServer/shared.h: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .theos/ 2 | packages/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char *argv[], char *envp[]) { 4 | @autoreleasepool { 5 | printf("Hello world!\n"); 6 | return 0; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vtool_and_sign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | path="$2" 4 | 5 | vtool -arch arm64 -set-build-version $PLATFORM 11.0 11.0 -replace -output "$path" "$path" 6 | ldid -S $@ 7 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/README.md: -------------------------------------------------------------------------------- 1 | HID code borrowed from https://github.com/facebook/idb/tree/4f9c644f3532c4a72657d54faec702dfb98a4c16/FBSimulatorControl/HID. Only Reimplemented code is kept. 2 | 3 | SimIndigoHIDServer runs inside simsendport 4 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.kdt.sim26booter 2 | Name: Sim26Booter 3 | Version: 0.0.1 4 | Architecture: iphoneos-arm 5 | Description: An awesome tool of some sort!! 6 | Maintainer: khanhduytran0 7 | Author: khanhduytran0 8 | Section: System 9 | Tag: role::hacker 10 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorHID+Struct26.h: -------------------------------------------------------------------------------- 1 | // 2 | // FBSimulatorHID+Struct26.h 3 | // 4 | // 5 | // Created by Duy Tran on 3/10/25. 6 | // 7 | 8 | #import "FBSimulatorHID.h" 9 | #import "Indigo26.h" 10 | 11 | @interface FBSimulatorHID (Struct26) 12 | - (BOOL)sendIndigoMessageDirect:(IndigoMessage *)message size:(NSUInteger)size; 13 | @end 14 | -------------------------------------------------------------------------------- /simxpctest/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 2 | include $(THEOS)/makefiles/common.mk 3 | 4 | TOOL_NAME = simxpctest 5 | 6 | simxpctest_FILES = main.m 7 | simxpctest_CFLAGS = -fobjc-arc 8 | simxpctest_INSTALL_PATH = /usr/local/bin 9 | simxpctest_CODESIGN_FLAGS = bash ../vtool_and_sign.sh -S../entitlements.plist 10 | simxpctest_FRAMEWORKS = IOKit 11 | TARGET_CODESIGN = env PLATFORM=7 12 | 13 | include $(THEOS_MAKE_PATH)/tool.mk 14 | -------------------------------------------------------------------------------- /launchd_sim_trampoline_hook/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 2 | include $(THEOS)/makefiles/common.mk 3 | 4 | # Inject to launchd_sim_trampoline, short name since there is insufficient space in load command 5 | LIBRARY_NAME = ltfix 6 | ltfix_FILES = ../shared.m trampoline_hook.m 7 | ltfix_CFLAGS = -fobjc-arc 8 | ltfix_LDFLAGS = -reexport-lobjc -shared -current_version 1.0.0 -compatibility_version 1.0.0 9 | ltfix_INSTALL_PATH = /iOSSimData/override/sbin 10 | 11 | include $(THEOS_MAKE_PATH)/library.mk 12 | -------------------------------------------------------------------------------- /launchd_sim_hook/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 2 | include $(THEOS)/makefiles/common.mk 3 | 4 | # Inject to launchd_sim 5 | LIBRARY_NAME = ldfix 6 | ldfix_FILES = ../shared.m launchd_sim_hook.m 7 | ldfix_CFLAGS = -fobjc-arc 8 | ldfix_LDFLAGS = -reexport-lobjc -shared -current_version 1.0.0 -compatibility_version 1.0.0 9 | ldfix_INSTALL_PATH = /iOSSimData/override/sbin 10 | ldfix_CODESIGN_FLAGS = bash ../vtool_and_sign.sh -S 11 | TARGET_CODESIGN = env PLATFORM=7 12 | 13 | include $(THEOS_MAKE_PATH)/library.mk 14 | -------------------------------------------------------------------------------- /layout/DEBIAN/postinst: -------------------------------------------------------------------------------- 1 | set -e 2 | cd $(realpath $HOME/../..)/iOSSimData/override 3 | 4 | add_trustcache() { 5 | local path="$1" 6 | local cdhash 7 | cdhash=$(ldid -arch arm64 -h "$path" 2>/dev/null | grep CDHash= | cut -c8-) 8 | echo "Adding $path cdhash: $cdhash" 9 | jbctl trustcache add "$cdhash" 10 | } 11 | 12 | add_trustcache "sbin/ldfix.dylib" 13 | add_trustcache "sbin/ltfix.dylib" 14 | add_trustcache "System/Library/PrivateFrameworks/SimFramebuffer.framework/SimFramebuffer" 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := iphone:clang:latest:14.0 2 | ARCHS = arm64 3 | GO_EASY_ON_ME := 1 4 | # TODO: replace iOSSimData? 5 | 6 | include $(THEOS)/makefiles/common.mk 7 | 8 | # iOS subprojects 9 | SUBPROJECTS += launchd_sim_trampoline_hook 10 | 11 | # iOS simulator subprojects 12 | SUBPROJECTS += launchd_sim_hook 13 | 14 | SUBPROJECTS += FakeMobileCoreServices 15 | SUBPROJECTS += SimFramebuffer 16 | SUBPROJECTS += SimRenderServer 17 | SUBPROJECTS += SimBooter 18 | SUBPROJECTS += simxpctest 19 | include $(THEOS_MAKE_PATH)/aggregate.mk 20 | -------------------------------------------------------------------------------- /SimFramebuffer/SimFramebuffer.h: -------------------------------------------------------------------------------- 1 | // 2 | // SimFramebuffer.h 3 | // SimFramebuffer 4 | // 5 | // Created by Duy Tran on 4/9/25. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for SimFramebuffer. 11 | FOUNDATION_EXPORT double SimFramebufferVersionNumber; 12 | 13 | //! Project version string for SimFramebuffer. 14 | FOUNDATION_EXPORT const unsigned char SimFramebufferVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | -------------------------------------------------------------------------------- /SimFramebuffer/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 2 | include $(THEOS)/makefiles/common.mk 3 | 4 | FRAMEWORK_NAME = SimFramebuffer 5 | 6 | SimFramebuffer_FILES = SimFramebuffer.m 7 | SimFramebuffer_PUBLIC_HEADERS = SimFramebuffer.h 8 | SimFramebuffer_INSTALL_PATH = /iOSSimData/override/System/Library/PrivateFrameworks 9 | SimFramebuffer_CFLAGS = -fobjc-arc 10 | SimFramebuffer_CODESIGN_FLAGS = bash ../vtool_and_sign.sh -S 11 | #SimFramebuffer_LDFLAGS = -F../Frameworks 12 | #SimFramebuffer_FRAMEWORKS = IOMobileFramebuffer 13 | TARGET_CODESIGN = env PLATFORM=7 14 | 15 | include $(THEOS_MAKE_PATH)/framework.mk 16 | -------------------------------------------------------------------------------- /FakeMobileCoreServices/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 2 | include $(THEOS)/makefiles/common.mk 3 | 4 | # Borrow MobileCoreServices to inject to all binaries 5 | FRAMEWORK_NAME = MobileCoreServices 6 | MobileCoreServices_FILES = main.m 7 | MobileCoreServices_CFLAGS = -fobjc-arc 8 | MobileCoreServices_LDFLAGS = -reexport-lobjc -reexport_framework CoreServices -shared -current_version 1226.0.0 -compatibility_version 1.0.0 9 | MobileCoreServices_INSTALL_PATH = /iOSSimData/override/System/Library/Frameworks 10 | MobileCoreServices_CODESIGN_FLAGS = bash ../vtool_and_sign.sh -S 11 | TARGET_CODESIGN = env PLATFORM=7 12 | 13 | include $(THEOS_MAKE_PATH)/framework.mk 14 | -------------------------------------------------------------------------------- /SimRenderServer/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 2 | include $(THEOS)/makefiles/common.mk 3 | 4 | BUNDLE_NAME = SimRenderServer 5 | 6 | SimRenderServer_DYNAMIC_LIBRARY = 0 7 | SimRenderServer_BUNDLE_EXTENSION = xpc 8 | SimRenderServer_FILES = main.m hook.x 9 | SimRenderServer_CFLAGS = -fobjc-arc 10 | SimRenderServer_CODESIGN_FLAGS = -S../entitlements.plist 11 | SimRenderServer_INSTALL_PATH = /usr/macOS/Frameworks/MTLSimDriver.framework/XPCServices 12 | SimRenderServer_LDFLAGS = -F../Frameworks -Wl,-U,_IOSurfaceClientGetID -Wl,-U,_IOSurfaceClientSetValue 13 | SimRenderServer_FRAMEWORKS = IOMobileFramebuffer IOSurface 14 | 15 | include $(THEOS_MAKE_PATH)/bundle.mk 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sim26Booter 2 | Boot iOS 26 simulator on iPhone 3 | 4 | Only tested with Dopamine jailbreak on iOS 16.5, iPhone Xs Max. 5 | While there is no hardcoded offset at the moment, some code paths are hardcoded for Dopamine. 6 | 7 | ## What works 8 | - [x] Passing `launchd` mach port to child processes 9 | - [x] Metal XPC 10 | - [x] IOSurface 11 | - [ ] Audio 12 | - [ ] Sensors 13 | - [x] HID Touchscreen Input 14 | 15 | ## Additional info 16 | - [Simulator boot process](SimBootProcess.md) on macOS 17 | 18 | ## Credits 19 | Some code have been borrowed from: 20 | - [Dopamine](https://github.com/opa334/Dopamine) 21 | - [idb/Indigo.h](https://github.com/facebook/idb/blob/d4f493eced373c3cf20ac001e8375c56fd4e53c1/PrivateHeaders/SimulatorApp/Indigo.h) 22 | -------------------------------------------------------------------------------- /SimFramebuffer/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | SimFramebuffer 9 | CFBundleIdentifier 10 | com.apple.CoreSimulator.SimFramebuffer 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | FMWK 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SimBooter/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 2 | include $(THEOS)/makefiles/common.mk 3 | 4 | TOOL_NAME = SimBooter 5 | 6 | SimBooter_FILES = main.m \ 7 | SimIndigoHIDServer/main.m SimIndigoHIDServer/hook.x \ 8 | SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorHID.m \ 9 | SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorHID+Struct26.m \ 10 | SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorIndigoHID.m \ 11 | SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorHIDEvent.m \ 12 | SimIndigoHIDServer/FBControlCore/Async/FBFuture.m 13 | SimBooter_CFLAGS = -fobjc-arc -I./SimIndigoHIDServer 14 | SimBooter_LDFLAGS = -F../Frameworks 15 | SimBooter_FRAMEWORKS = IOKit IOMobileFramebuffer 16 | SimBooter_CODESIGN_FLAGS = -S../entitlements.plist 17 | SimBooter_INSTALL_PATH = /usr/local/bin 18 | 19 | include $(THEOS_MAKE_PATH)/tool.mk 20 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/hook.x: -------------------------------------------------------------------------------- 1 | @import Darwin; 2 | 3 | // Fix IOHIDEventSystemCreate not working 4 | 5 | typedef char* name_t; 6 | kern_return_t bootstrap_look_up2(mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, uint64_t flags); 7 | kern_return_t bootstrap_check_in(mach_port_t bp, const name_t service_name, mach_port_t *sp); 8 | 9 | %hookf(kern_return_t, bootstrap_look_up2, mach_port_t bp, const name_t service_name, mach_port_t *sp, pid_t target_pid, uint64_t flags) { 10 | if(!strcmp(service_name, "com.apple.iohideventsystem")) { 11 | return %orig(bp, "com.apple.iohideventsystem.4sim", sp, target_pid, flags); 12 | } 13 | return %orig(bp, service_name, sp, target_pid, flags); 14 | } 15 | %hookf(kern_return_t, bootstrap_check_in, mach_port_t bp, const name_t service_name, mach_port_t *sp) { 16 | if(!strcmp(service_name, "com.apple.iohideventsystem")) { 17 | return %orig(bp, "com.apple.iohideventsystem.4sim", sp); 18 | } 19 | return %orig(bp, service_name, sp); 20 | } 21 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorHID+Struct26.m: -------------------------------------------------------------------------------- 1 | // 2 | // FBSimulatorHID+Struct26.m 3 | // 4 | // 5 | // Created by Duy Tran on 3/10/25. 6 | // 7 | 8 | #import "FBSimulatorHID+Struct26.h" 9 | 10 | @implementation FBSimulatorHID_Reimplemented (Struct26) 11 | - (BOOL)sendIndigoMessageDirect:(IndigoMessage *)message size:(NSUInteger)size 12 | { 13 | if (self.replyPort == 0) { 14 | NSLog(@"The Reply Port has not been obtained yet. Call -connect: first"); 15 | return NO; 16 | } 17 | 18 | // Set the header of the message 19 | message->header.msgh_bits = 0x13; 20 | message->header.msgh_size = size; 21 | message->header.msgh_remote_port = self.replyPort; 22 | message->header.msgh_local_port = 0; 23 | message->header.msgh_voucher_port = 0; 24 | message->header.msgh_id = 0; 25 | 26 | mach_msg_return_t result = mach_msg_send((mach_msg_header_t *) message); 27 | if (result != ERR_SUCCESS) { 28 | NSLog(@"The mach_msg_send failed with error %d", result); 29 | return NO; 30 | } 31 | return YES; 32 | } 33 | @end 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Duy Tran 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /IOMobileFramebuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef IOMOBILEFRAMEBUFFER_IOMOBILEFRAMEBUFFER_H 2 | #define IOMOBILEFRAMEBUFFER_IOMOBILEFRAMEBUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef IOReturn IOMobileFramebufferReturn; 10 | typedef struct __IOMobileFramebuffer *IOMobileFramebufferRef; 11 | typedef CGSize IOMobileFramebufferDisplaySize; 12 | 13 | __BEGIN_DECLS 14 | 15 | IOMobileFramebufferReturn 16 | IOMobileFramebufferGetMainDisplay(IOMobileFramebufferRef *pointer); 17 | 18 | IOMobileFramebufferReturn 19 | IOMobileFramebufferGetDisplaySize(IOMobileFramebufferRef pointer, IOMobileFramebufferDisplaySize *size); 20 | 21 | IOMobileFramebufferReturn 22 | IOMobileFramebufferGetLayerDefaultSurface(IOMobileFramebufferRef pointer, int surface, IOSurfaceRef *buffer); 23 | 24 | IOMobileFramebufferReturn 25 | IOMobileFramebufferSwapBegin(IOMobileFramebufferRef pointer, int *token); 26 | 27 | IOMobileFramebufferReturn 28 | IOMobileFramebufferSwapEnd(IOMobileFramebufferRef pointer); 29 | 30 | IOMobileFramebufferReturn 31 | IOMobileFramebufferSwapSetLayer(IOMobileFramebufferRef pointer, int layerid, IOSurfaceRef buffer, CGRect bounds, CGRect frame, int flags); 32 | 33 | __END_DECLS 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /FakeMobileCoreServices/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 23A344014 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | MobileCoreServices 11 | CFBundleIdentifier 12 | com.apple.MobileCoreServices 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | MobileCoreServices 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1226 21 | CFBundleSupportedPlatforms 22 | 23 | iPhoneSimulator 24 | 25 | CFBundleVersion 26 | 1226 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 23A5259s 31 | DTPlatformName 32 | iphonesimulator 33 | DTPlatformVersion 34 | 26.0 35 | DTSDKBuild 36 | 23A5259s 37 | DTSDKName 38 | iphonesimulator26.0.internal 39 | DTXcode 40 | 1700 41 | DTXcodeBuild 42 | 17A6231e 43 | MinimumOSVersion 44 | 26.0 45 | UIDeviceFamily 46 | 47 | 1 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /SimRenderServer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // SimRenderServer.m 3 | // SimulatorShimFrameworks 4 | // 5 | // Created by Duy Tran on 26/9/25. 6 | // 7 | 8 | @import Darwin; 9 | @import Foundation; 10 | @import IOSurface; 11 | 12 | @interface IOSurfaceRemoteServer : NSObject 13 | - (instancetype)initWithListener:(xpc_connection_t)listener options:(NSDictionary *)options; 14 | @end 15 | 16 | void fixed_xpc_connection_enable_sim2host_4sim(xpc_connection_t connection) { 17 | if (xpc_get_type(connection) != XPC_TYPE_CONNECTION) { 18 | fprintf(stderr, "Given object not of required type.\n"); 19 | abort(); 20 | } 21 | char *connection_ptr = (__bridge void *)connection; 22 | if (*(uint32_t *)(connection_ptr + 0x40) != 0) { 23 | fprintf(stderr, "Attempt to change the sim-to-host mode on a live connection."); 24 | abort(); 25 | } 26 | // on iOS, this is set to 2 which turns out to be inaccurate 27 | // on macOS, this is set to 0 28 | *(uint32_t *)(connection_ptr + 0xC0) = 0; 29 | } 30 | 31 | // decompiled from MTLSimDriverHost.xpc with some modifications 32 | xpc_connection_t xpc_connection_create_listener(const char* name, dispatch_queue_t queue); 33 | xpc_connection_t xpc_connection_create_mach_service(const char *name, dispatch_queue_t targetq, uint64_t flags); 34 | int main(int argc, const char **argv, const char **envp) { 35 | static IOSurfaceRemoteServer *server; 36 | xpc_object_t (*xpc_connection_create_mach_service)(const char *name, dispatch_queue_t targetq, uint64_t flags) = dlsym(RTLD_DEFAULT, "xpc_connection_create_mach_service"); 37 | // com.apple.accelerator.iosurface 38 | xpc_connection_t peerConnection = xpc_connection_create_mach_service("com.apple.IOSurface.Remote", dispatch_get_main_queue(), XPC_CONNECTION_MACH_SERVICE_LISTENER); 39 | fixed_xpc_connection_enable_sim2host_4sim(peerConnection); 40 | dispatch_async(dispatch_get_main_queue(), ^{ 41 | server = [[IOSurfaceRemoteServer alloc] initWithListener:peerConnection options:@{}]; 42 | }); 43 | dispatch_main(); 44 | } 45 | -------------------------------------------------------------------------------- /FakeMobileCoreServices/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // 4 | // 5 | // Created by Duy Tran on 26/9/25. 6 | // 7 | 8 | @import Darwin; 9 | @import CoreServices; 10 | 11 | const char *MobileCoreServicesVersionString = "@(#)PROGRAM:MobileCoreServices PROJECT:CoreServices-1226\n"; 12 | const uint64_t MobileCoreServicesVersionNumber = 0x4093280000000000; 13 | 14 | void ModifyExecutableRegion(void *addr, size_t size, void(^callback)(void)) { 15 | vm_protect(mach_task_self(), (vm_address_t)addr, size, false, PROT_READ | PROT_WRITE | VM_PROT_COPY); 16 | callback(); 17 | vm_protect(mach_task_self(), (vm_address_t)addr, size, false, PROT_READ | PROT_EXEC); 18 | } 19 | 20 | void handleFaultyTextPage(int signum, struct __siginfo *siginfo, void *context) { 21 | struct __darwin_ucontext *ucontext = (struct __darwin_ucontext *) context; 22 | struct __darwin_mcontext64 *machineContext = (struct __darwin_mcontext64 *) ucontext->uc_mcontext; 23 | arm_thread_state64_t *state = &machineContext->__ss; 24 | uint32_t *pc = (uint32_t *)__darwin_arm_thread_state64_get_pc(*state); 25 | ModifyExecutableRegion((void *)pc, sizeof(uint32_t), ^{ 26 | *pc = 0xd503201f; // nop 27 | }); 28 | } 29 | 30 | #define CS_DEBUGGED 0x10000000 31 | int csops(pid_t pid, unsigned int ops, void *useraddr, size_t usersize); 32 | int fork(); 33 | int ptrace(int, int, int, int); 34 | int isJITEnabled() { 35 | int flags; 36 | csops(getpid(), 0, &flags, sizeof(flags)); 37 | return (flags & CS_DEBUGGED) != 0; 38 | } 39 | 40 | __attribute__((constructor)) static void MobileCoreServicesInit() { 41 | // enable JIT 42 | if (!isJITEnabled()) { 43 | // Enable JIT 44 | int pid = fork(); 45 | if (pid == 0) { 46 | ptrace(0, 0, 0, 0); 47 | exit(0); 48 | } else if (pid > 0) { 49 | while (wait(NULL) > 0) { 50 | usleep(1000); 51 | } 52 | } 53 | } 54 | 55 | // catch SIGILL 56 | struct sigaction sigAction; 57 | sigAction.sa_sigaction = handleFaultyTextPage; 58 | sigAction.sa_flags = SA_SIGINFO; 59 | sigaction(SIGILL, &sigAction, NULL); 60 | } 61 | -------------------------------------------------------------------------------- /SimRenderServer/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 22A380007 7 | CFBundleDevelopmentRegion 8 | English 9 | CFBundleExecutable 10 | SimRenderServer 11 | CFBundleIdentifier 12 | com.apple.IOSurface.Remote 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | SimRenderServer 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 306.6.4 21 | CFBundleSignature 22 | 1 23 | CFBundleSupportedPlatforms 24 | 25 | iPhoneOS 26 | 27 | CFBundleVersion 28 | 306.6.4 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 14E6097d 33 | DTPlatformName 34 | iphoneos 35 | DTPlatformVersion 36 | 16.5 37 | DTSDKBuild 38 | 20F48 39 | DTSDKName 40 | iphoneos16.5.internal 41 | DTXcode 42 | 1430 43 | DTXcodeBuild 44 | 14E6097d 45 | MachServices 46 | 47 | com.apple.IOSurface.Remote 48 | 49 | 50 | MinimumOSVersion 51 | 16.5 52 | UIDeviceFamily 53 | 54 | 1 55 | 56 | UIRequiredDeviceCapabilities 57 | 58 | arm64 59 | 60 | XPCService 61 | 62 | RunLoopType 63 | NSRunLoop 64 | ServiceType 65 | Application 66 | _MultipleInstances 67 | 68 | _ProcessType 69 | App 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /SimRenderServer/hook.x: -------------------------------------------------------------------------------- 1 | @import IOSurface; 2 | #import "IOMobileFramebuffer.h" 3 | 4 | typedef CFTypeRef IOSurfaceClientRef; 5 | int IOSurfaceClientGetID(IOSurfaceClientRef client); 6 | void IOSurfaceClientSetValue(IOSurfaceClientRef client, NSString *key, id value); 7 | 8 | IOMobileFramebufferRef fbConn; 9 | %hookf(void, IOSurfaceClientSetValue, IOSurfaceClientRef client, NSString *key, id value) { 10 | if([key isEqual:@"SwapCmd"]) { 11 | uint64_t val = (uint64_t)[value unsignedLongLongValue]; 12 | if(val == 0) { // SFBSwapchainSwapSubmit 13 | static int surfaceID = 0; 14 | static IOSurfaceRef surface; 15 | static CGRect frame; 16 | int surfaceIDCurr = IOSurfaceClientGetID(client); 17 | if(surfaceID != surfaceIDCurr) { 18 | surfaceID = surfaceIDCurr; 19 | surface = IOSurfaceLookup(surfaceIDCurr); 20 | frame = CGRectMake(0, 0, IOSurfaceGetWidth(surface), IOSurfaceGetHeight(surface)); 21 | } 22 | int token; 23 | IOMobileFramebufferSwapBegin(fbConn, &token); 24 | IOMobileFramebufferSwapSetLayer(fbConn, 0, surface, frame, frame, 0); 25 | IOMobileFramebufferSwapEnd(fbConn); 26 | 27 | // FIXME: cannot swap small region since subsequent swaps will clear the previous ones 28 | // int token; 29 | // IOMobileFramebufferSwapEnd(fbConn); 30 | // IOMobileFramebufferSwapBegin(fbConn, &token); 31 | // } else { 32 | // static int surfaceID = 0; 33 | // static IOSurfaceRef surface; 34 | // int surfaceIDCurr = IOSurfaceClientGetID(client); 35 | // if(surfaceID != surfaceIDCurr) { 36 | // surfaceID = surfaceIDCurr; 37 | // surface = IOSurfaceLookup(surfaceIDCurr); 38 | // } 39 | // CGRect frame = CGRectMake(val >> 36, (val >> 24) & 0xFFF, (val >> 12) & 0xFFF, val & 0xFFF); 40 | // IOMobileFramebufferSwapSetLayer(fbConn, 0, surface, frame, frame, 0); 41 | } 42 | return; 43 | } 44 | %orig(client, key, value); 45 | } 46 | 47 | %ctor { 48 | IOMobileFramebufferGetMainDisplay(&fbConn); 49 | assert(fbConn); 50 | } 51 | -------------------------------------------------------------------------------- /tmp.h: -------------------------------------------------------------------------------- 1 | // 2 | // tmp.h 3 | // 4 | // 5 | // Created by Duy Tran on 7/9/25. 6 | // 7 | 8 | void SFBConnectionCopyDisplay() { abort(); } 9 | void SFBConnectionCopyDisplayByUID() { abort(); } 10 | void SFBConnectionCreateDisplay() { abort(); } 11 | void SFBConnectionGetID() { abort(); } 12 | void SFBConnectionGetTypeID() { abort(); } 13 | void SFBConnectionRemoveDisplay() { abort(); } 14 | void SFBConnectionSetDisplayConnectedHandler() { abort(); } 15 | void SFBConnectionSetDisplayDisconnectedHandler() { abort(); } 16 | void SFBConnectionUpdateDisplay() { abort(); } 17 | void SFBDisplayCopyExtendedPropertyProtocols() { abort(); } 18 | void SFBDisplayGetCanvasSize() { abort(); } 19 | void SFBDisplayGetCanvasSizeCount() { abort(); } 20 | void SFBDisplayGetConnectionID() { abort(); } 21 | void SFBDisplayGetDotPitch() { abort(); } 22 | void SFBDisplayGetExtendedProperties() { abort(); } 23 | void SFBDisplayGetFlags() { abort(); } 24 | void SFBDisplayGetID() { abort(); } 25 | void SFBDisplayGetMaskPath() { abort(); } 26 | void SFBDisplayGetMaxLayerCount() { abort(); } 27 | void SFBDisplayGetMaxSwapchainCount() { abort(); } 28 | void SFBDisplayGetMode() { abort(); } 29 | void SFBDisplayGetModeCount() { abort(); } 30 | void SFBDisplayGetPowerState() { abort(); } 31 | void SFBDisplayGetPreferredMode() { abort(); } 32 | void SFBDisplayGetSupportedPresentationModes() { abort(); } 33 | void SFBDisplayGetSupportedSurfaceFlags() { abort(); } 34 | void SFBDisplayGetTypeID() { abort(); } 35 | void SFBDisplaySetBacklightState() { abort(); } 36 | void SFBDisplaySetBrightnessFactor() { abort(); } 37 | void SFBDisplaySetCanvasSize() { abort(); } 38 | void SFBDisplaySetCurrentMode() { abort(); } 39 | void SFBDisplaySetCurrentUIOrientation() { abort(); } 40 | void SFBSetCreateByAddingSet() { abort(); } 41 | void SFBSetCreateByIntersectingSet() { abort(); } 42 | void SFBSetCreateBySubtractingSet() { abort(); } 43 | void SFBSetCreateFromArray() { abort(); } 44 | void SFBSetGetEmpty() { abort(); } 45 | void SFBSwapchainAcquireSurfaceFence() { abort(); } 46 | void SFBSwapchainGetColorspace() { abort(); } 47 | void SFBSwapchainGetConnectionID() { abort(); } 48 | void SFBSwapchainGetDisplayID() { abort(); } 49 | void SFBSwapchainGetFramebufferSize() { abort(); } 50 | void SFBSwapchainGetHDRMode() { abort(); } 51 | void SFBSwapchainGetID() { abort(); } 52 | void SFBSwapchainGetMaxSurfacesPerOperation() { abort(); } 53 | void SFBSwapchainGetPixelFormat() { abort(); } 54 | void SFBSwapchainGetPresentationMode() { abort(); } 55 | void SFBSwapchainGetTypeID() { abort(); } 56 | void SFBSwapchainSwapCancel() { abort(); } 57 | void SFBSwapchainSwapSetCallback() { abort(); } 58 | -------------------------------------------------------------------------------- /shared.h: -------------------------------------------------------------------------------- 1 | @import Darwin; 2 | @import XPC; 3 | 4 | #define TANK_SERVER_VALIDATE 707 5 | #define TANK_SERVER_GET_SERVICE_PORT 708 6 | #define TASK_GET_SPECIAL_PORT 1000 7 | #define HOOK_MACH_MAX_REPLY_SIZE (sizeof(struct jbserver_mach_msg_checkin_reply) + MAX_TRAILER_SIZE) 8 | 9 | // https://stackoverflow.com/a/35447525 10 | typedef struct { 11 | mach_msg_header_t header; 12 | mach_msg_body_t body; 13 | mach_msg_port_descriptor_t special_port; 14 | } send_port_msg; 15 | void fill_send_port_msg(send_port_msg *msg); 16 | kern_return_t task_get_launchd_port(mach_port_t task, mach_port_t *special_port); 17 | 18 | boolean_t mig_callback_dopamine(mach_msg_header_t *message, mach_msg_header_t *reply); 19 | 20 | // interpose.h 21 | #define DYLD_INTERPOSE(_replacement,_replacee) \ 22 | __attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee \ 23 | __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee }; 24 | 25 | // Dopamine/BaseBin/libjailbreak/src/jbserver.h 26 | #define JBSERVER_MACH_MAGIC 0x444F50414D494E45 27 | #define JBSERVER_MACH_CHECKIN 0 28 | #define JBSERVER_MACH_FORK_FIX 1 29 | #define JBSERVER_MACH_TRUST_FILE 2 30 | #define JBSERVER_MACH_GET_HOST_LAUNCHD_PORT 1000 31 | struct jbserver_mach_msg { 32 | mach_msg_header_t hdr; 33 | uint64_t magic; 34 | uint64_t action; 35 | }; 36 | struct jbserver_mach_msg_reply { 37 | struct jbserver_mach_msg msg; 38 | uint64_t status; 39 | }; 40 | struct jbserver_mach_msg_checkin_reply { 41 | struct jbserver_mach_msg_reply base; 42 | bool fullyDebugged; 43 | char jbRootPath[PATH_MAX]; 44 | char bootUUID[37]; 45 | char sandboxExtensions[2000]; 46 | }; 47 | struct jbserver_mach_msg_forkfix_reply { 48 | struct jbserver_mach_msg_reply base; 49 | }; 50 | struct jbserver_mach_msg_trust_fd_reply { 51 | struct jbserver_mach_msg_reply base; 52 | }; 53 | 54 | typedef boolean_t (*dispatch_mig_callback_t)(mach_msg_header_t *message, mach_msg_header_t *reply); 55 | extern mach_msg_header_t* dispatch_mach_msg_get_msg(void *message, size_t *size_ptr); 56 | extern int xpc_pipe_try_receive(mach_port_t p, xpc_object_t *message, mach_port_t *recvp, dispatch_mig_callback_t callout, size_t maxmsgsz, uint64_t flags); 57 | extern int xpc_receive_mach_msg(void *msg, void *a2, void *a3, void *a4, xpc_object_t *xOut); 58 | 59 | mach_port_t jbclient_mach_get_launchd_port(); 60 | kern_return_t jbclient_mach_send_msg(mach_msg_header_t *hdr, struct jbserver_mach_msg_reply *reply); 61 | kern_return_t jbclient_mach_send_msg_internal(mach_msg_header_t *hdr, struct jbserver_mach_msg_reply *reply, mach_port_t launchdPort, mach_port_t replyPort, boolean_t isReceivingPort); 62 | 63 | #define JBSERVER_SIM_MACH_GET_LAUNCHD_PORT 1000 64 | -------------------------------------------------------------------------------- /launchd_sim_hook/launchd_sim_hook.m: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | #import "shared.h" 3 | 4 | mach_port_t host_launchd_port = MACH_PORT_NULL; 5 | // overrides implementation in dyld 6 | mach_port_t jbclient_mach_get_launchd_port() { 7 | assert(host_launchd_port != MACH_PORT_NULL); 8 | return host_launchd_port; 9 | } 10 | 11 | void jbserver_sim_get_launchd(mach_msg_header_t *message) { 12 | uint8_t replyBuf[HOOK_MACH_MAX_REPLY_SIZE] = {0}; 13 | send_port_msg *reply_msg = (void *)replyBuf; 14 | 15 | fill_send_port_msg(reply_msg); 16 | reply_msg->header.msgh_remote_port = message->msgh_remote_port; 17 | reply_msg->header.msgh_id = message->msgh_id + 100; 18 | reply_msg->special_port.name = jbclient_mach_get_launchd_port(); 19 | 20 | mach_msg_return_t mr = mach_msg(&reply_msg->header, MACH_SEND_MSG, reply_msg->header.msgh_size, 0, 0, 0, 0); 21 | assert(mr == MACH_MSG_SUCCESS); 22 | 23 | // Get rid of the temporary port used to send our port over 24 | mach_msg_destroy(&reply_msg->header); 25 | // xpc_receive_mach_msg will clean it up for us 26 | //mach_port_deallocate(mach_task_self(), reply_msg->header.msgh_remote_port); 27 | } 28 | 29 | // https://github.com/opa334/Dopamine/blob/314f7f2/BaseBin/launchdhook/src/xpc_hook.c#L16-L61 30 | int hooked_xpc_receive_mach_msg(void *msg, void *a2, void *a3, void *a4, xpc_object_t *xOut) { 31 | size_t msgBufSize = 0; 32 | struct jbserver_mach_msg *jbsMachMsg = (struct jbserver_mach_msg *)dispatch_mach_msg_get_msg(msg, &msgBufSize); 33 | bool wasProcessed = false; 34 | if (jbsMachMsg != NULL && msgBufSize >= sizeof(mach_msg_header_t)) { 35 | size_t msgSize = jbsMachMsg->hdr.msgh_size; 36 | if (msgSize <= msgBufSize && msgSize >= sizeof(struct jbserver_mach_msg) && jbsMachMsg->magic == JBSERVER_MACH_MAGIC) { 37 | if(jbsMachMsg->action == JBSERVER_MACH_GET_HOST_LAUNCHD_PORT) { 38 | jbserver_sim_get_launchd(&jbsMachMsg->hdr); 39 | } else { 40 | dprintf(6, "ERROR: Unexpectedly received jbserver action %d\n", jbsMachMsg->action); 41 | //abort(); 42 | } 43 | } 44 | } 45 | 46 | int r = xpc_receive_mach_msg(msg, a2, a3, a4, xOut); 47 | // if (!wasProcessed && r == 0 && xOut && *xOut) { 48 | // if (jbserver_received_xpc_message(&gGlobalServer, *xOut) == 0) { 49 | // Returning non null here makes launchd disregard this message 50 | // For jailbreak messages we have the logic to handle them 51 | //xpc_release(*xOut); 52 | // return 22; 53 | // } 54 | // } 55 | return r; 56 | } 57 | DYLD_INTERPOSE(hooked_xpc_receive_mach_msg, xpc_receive_mach_msg); 58 | 59 | __attribute__((constructor)) static void init() { 60 | // Obtain host's launchd port using our launchd_sim_trampoline_tank hook 61 | mach_port_t tank_port = MACH_PORT_NULL; 62 | task_get_bootstrap_port(mach_task_self(), &tank_port); 63 | assert(tank_port != MACH_PORT_NULL); 64 | task_get_launchd_port(tank_port, &host_launchd_port); 65 | assert(host_launchd_port != MACH_PORT_NULL); 66 | }; 67 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorIndigoHID.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | An Enumeration for the Direction of the Event. 15 | */ 16 | typedef NS_ENUM(int, FBSimulatorHIDDirection) { 17 | FBSimulatorHIDDirectionDown = 1, 18 | FBSimulatorHIDDirectionUp = 2, 19 | }; 20 | 21 | /** 22 | An Enumeration Representing a button press. 23 | */ 24 | typedef NS_ENUM(int, FBSimulatorHIDButton) { 25 | FBSimulatorHIDButtonApplePay = 1, 26 | FBSimulatorHIDButtonHomeButton = 2, 27 | FBSimulatorHIDButtonLock = 3, 28 | FBSimulatorHIDButtonSideButton = 4, 29 | FBSimulatorHIDButtonSiri = 5, 30 | }; 31 | 32 | /** 33 | Translates FBSimulatorHID Events into Indigo Structs. 34 | */ 35 | @interface FBSimulatorIndigoHID : NSObject 36 | 37 | /** 38 | The SimulatorKit Implementation. 39 | 40 | @param error an error out for any error that occurs in construction. 41 | @return a new FBSimulatorIndigoHID instance if successful, nil otherwise. 42 | */ 43 | + (nullable instancetype)simulatorKitHIDWithError:(NSError **)error; 44 | 45 | /** 46 | An implementation of FBSimulatorIndigoHID, by re-implementing SimulatorKit directly. 47 | 48 | @return a new FBSimulatorIndigoHID instance. 49 | */ 50 | + (instancetype)reimplemented; 51 | 52 | /** 53 | A Keyboard Event. 54 | 55 | @param direction the direction of the event. 56 | @param keycode the Key Code to send. The keycodes are 'Hardware Independent' as described in . 57 | @return an NSData-Wrapped IndigoMessage. The data is owned by the receiver and will be freed when the data is deallocated. 58 | */ 59 | - (NSData *)keyboardWithDirection:(FBSimulatorHIDDirection)direction keyCode:(unsigned int)keycode; 60 | 61 | /** 62 | A Button Event. 63 | 64 | @param direction the direction of the event. 65 | @param button the button. 66 | @return an NSData-Wrapped IndigoMessage. The data is owned by the receiver and will be freed when the data is deallocated. 67 | */ 68 | - (NSData *)buttonWithDirection:(FBSimulatorHIDDirection)direction button:(FBSimulatorHIDButton)button; 69 | 70 | /** 71 | A Touch Event. 72 | 73 | @param screenSize the size of the screen in pixels. 74 | @param direction the direction of the event. 75 | @param x the X-Coordinate in pixels 76 | @param y the Y-Coordinate pixels 77 | @return an NSData-Wrapped IndigoMessage. The data is owned by the receiver and will be freed when the data is deallocated. 78 | */ 79 | - (NSData *)touchScreenSize:(CGSize)screenSize direction:(FBSimulatorHIDDirection)direction x:(double)x y:(double)y; 80 | 81 | 82 | /** 83 | A Touch Event. 84 | @param screenSize the size of the screen in pixels. 85 | @param screenScale the scale of the screen e.g. @2x 86 | @param direction the direction of the event. 87 | @param x the X-Coordinate in pixels 88 | @param y the Y-Coordinate pixels 89 | @return an NSData-Wrapped IndigoMessage. The data is owned by the receiver and will be freed when the data is deallocated. 90 | */ 91 | - (NSData *)touchScreenSize:(CGSize)screenSize screenScale:(float)screenScale direction:(FBSimulatorHIDDirection)direction x:(double)x y:(double)y; 92 | 93 | @end 94 | 95 | NS_ASSUME_NONNULL_END 96 | -------------------------------------------------------------------------------- /launchd_sim_trampoline_hook/trampoline_hook.m: -------------------------------------------------------------------------------- 1 | @import Darwin; 2 | @import Foundation; 3 | #import 4 | #import "shared.h" 5 | 6 | // From jbclient_mach.c in Dopamine 7 | mach_port_t jbclient_mach_get_launchd_port(void) { 8 | mach_port_t launchdPort = MACH_PORT_NULL; 9 | task_get_bootstrap_port(task_self_trap(), &launchdPort); 10 | return launchdPort; 11 | } 12 | 13 | // launchd_sim_trampoline_tank acts like a boomerang which sends launchd_sim's own bootstrap port back to it 14 | static dispatch_mig_callback_t mig_callback_orig; 15 | static boolean_t mig_callback_orig_called; 16 | static boolean_t mig_callback_get_special_port(mach_msg_header_t *message, mach_msg_header_t *reply) { 17 | send_port_msg *reply_msg = (void *)reply; 18 | 19 | fill_send_port_msg(reply_msg); 20 | reply_msg->header.msgh_remote_port = message->msgh_remote_port; 21 | reply_msg->header.msgh_id = message->msgh_id + 100; 22 | reply_msg->special_port.name = jbclient_mach_get_launchd_port(); 23 | 24 | // mach_msg_return_t mr = mach_msg(&reply_msg->header, MACH_SEND_MSG, reply_msg->header.msgh_size, 0, 0, 0, 0); 25 | // if(mr != MACH_MSG_SUCCESS) { 26 | // printf("mig_callback_get_special_port: failed to send reply message: 0x%x\n", mr); 27 | // } 28 | // assert(mr == MACH_MSG_SUCCESS); 29 | return true; 30 | } 31 | 32 | boolean_t hooked_dispatch_mig_callback(mach_msg_header_t *message, mach_msg_header_t *reply) { 33 | //NSLog(@"tank: message msgh_bits=0x%x id=0x%x size=%u", message->msgh_bits, message->msgh_id, message->msgh_size); 34 | switch(message->msgh_id) { 35 | case 0x400000ce: // https://github.com/opa334/Dopamine/blob/314f7f2/BaseBin/libjailbreak/src/jbclient_mach.c#L33 36 | struct jbserver_mach_msg *jb_msg = (struct jbserver_mach_msg *)message; 37 | if(jb_msg->action == JBSERVER_MACH_GET_HOST_LAUNCHD_PORT) { 38 | return mig_callback_get_special_port(message, reply); 39 | } else { 40 | return mig_callback_dopamine(message, reply); 41 | } 42 | case TANK_SERVER_VALIDATE: // launchd_sim_trampoline validates tank connection 43 | case TANK_SERVER_GET_SERVICE_PORT: // launchd_sim requests its bootstrap port 44 | mig_callback_orig_called = true; 45 | return mig_callback_orig(message, reply); 46 | default: 47 | NSLog(@"tank: unhandled message id 0x%x", message->msgh_id); 48 | return false; 49 | } 50 | } 51 | 52 | int hooked_xpc_pipe_try_receive(mach_port_t p, xpc_object_t *message, mach_port_t *recvp, dispatch_mig_callback_t callout, size_t maxmsgsz, uint64_t flags) { 53 | static int hookState = -1; 54 | if(hookState == -1) { 55 | char **argv = *_NSGetArgv(); 56 | hookState = !strcmp(argv[0], "launchd_sim_trampoline_tank"); 57 | } 58 | if(!hookState) { 59 | return xpc_pipe_try_receive(p, message, recvp, callout, maxmsgsz, flags); 60 | } 61 | 62 | mig_callback_orig = callout; 63 | size_t checkin_reply_size = HOOK_MACH_MAX_REPLY_SIZE; 64 | assert(maxmsgsz < checkin_reply_size); 65 | maxmsgsz = checkin_reply_size; 66 | mig_callback_orig_called = false; 67 | 68 | kern_return_t result; 69 | do { 70 | result = xpc_pipe_try_receive(p, message, recvp, hooked_dispatch_mig_callback, maxmsgsz, flags); 71 | } while(result == KERN_SUCCESS && !mig_callback_orig_called); 72 | 73 | if(result != KERN_SUCCESS) { 74 | printf("Failed to handle something...\n"); 75 | abort(); 76 | } 77 | return result; 78 | } 79 | DYLD_INTERPOSE(hooked_xpc_pipe_try_receive, xpc_pipe_try_receive); 80 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorHID.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | //#import 11 | #import "FBControlCore/Async/FBFuture.h" 12 | 13 | #import "FBSimulatorIndigoHID.h" 14 | 15 | @class FBSimulator; 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | /** 20 | A Wrapper around the mach_port_t that is created in the booting of a Simulator. 21 | The IndigoHIDRegistrationPort is essential for backboard, otherwise UI events aren't synthesized properly. 22 | */ 23 | @interface FBSimulatorHID : NSObject 24 | 25 | #pragma mark Initializers 26 | 27 | /** 28 | Creates and returns a FBSimulatorHID Instance for the provided Simulator. 29 | Will fail if a HID Port could not be registered for the provided Simulator. 30 | Registration may need to occur prior to booting. 31 | 32 | @param simulator the Simulator to create a IndigoHIDRegistrationPort for. 33 | @return a FBSimulatorHID if successful, nil otherwise. 34 | */ 35 | + (FBFuture *)hidForSimulator:(FBSimulator *)simulator; 36 | 37 | #pragma mark Lifecycle 38 | 39 | /** 40 | Obtains the Reply Port for the Simulator. 41 | This must be obtained in order to send IndigoHID events to the Simulator. 42 | This should be obtained after the Simulator is booted. 43 | 44 | @return YES if successful, NO otherwise. 45 | */ 46 | - (FBFuture *)connect; 47 | 48 | /** 49 | Disconnects from the remote HID. 50 | */ 51 | - (void)disconnect; 52 | 53 | #pragma mark HID Manipulation 54 | 55 | /** 56 | Sends a Keyboard Event. 57 | 58 | @param direction the direction of the event. 59 | @param keycode the Key Code to send. The keycodes are 'Hardware Independent' as described in . 60 | @return A future that resolves when the event has been sent. 61 | */ 62 | - (FBFuture *)sendKeyboardEventWithDirection:(FBSimulatorHIDDirection)direction keyCode:(unsigned int)keycode; 63 | 64 | /** 65 | Sends a Button Event. 66 | 67 | @param direction the direction of the event. 68 | @param button the button. 69 | @return A future that resolves when the event has been sent. 70 | */ 71 | - (FBFuture *)sendButtonEventWithDirection:(FBSimulatorHIDDirection)direction button:(FBSimulatorHIDButton)button; 72 | 73 | /** 74 | Sends a Tap Event 75 | Will Perform the Touch Down, followed by the Touch Up 76 | 77 | @param type the event type. 78 | @param x the X-Coordinate 79 | @param y the Y-Coordinate 80 | @return A future that resolves when the event has been sent. 81 | */ 82 | - (FBFuture *)sendTouchWithType:(FBSimulatorHIDDirection)type x:(double)x y:(double)y; 83 | 84 | #pragma mark Properties 85 | 86 | /** 87 | The Queue on which messages are sent to the HID Server. 88 | */ 89 | @property (nonatomic, strong, readonly) dispatch_queue_t queue; 90 | 91 | @end 92 | 93 | @interface FBSimulatorHID_Reimplemented : FBSimulatorHID 94 | 95 | @property (nonatomic, assign, readwrite) mach_port_t registrationPort; 96 | @property (nonatomic, assign, readwrite) mach_port_t replyPort; 97 | 98 | - (instancetype)initWithIndigo:(FBSimulatorIndigoHID *)indigo mainScreenSize:(CGSize)mainScreenSize queue:(dispatch_queue_t)queue registrationPort:(mach_port_t)registrationPort; 99 | 100 | - (instancetype)initWithIndigo:(FBSimulatorIndigoHID *)indigo mainScreenSize:(CGSize)mainScreenSize mainScreenScale:(float)mainScreenScale queue:(dispatch_queue_t)queue registrationPort:(mach_port_t)registrationPort; 101 | 102 | @end 103 | 104 | NS_ASSUME_NONNULL_END 105 | -------------------------------------------------------------------------------- /PATCHING.md: -------------------------------------------------------------------------------- 1 | # Patching 2 | This file contains my patching history to the simulator runtime 3 | 4 | ## 2025-09-02 5 | - Fix `launchd_sim_trampoline_tank` crashing by passing through Dopamine calls 6 | 7 | ## 2025-09-03 8 | - Internalize device by creating `/AppleInternal` folder. 9 | - Patch to use virtual display 10 | ``` 11 | QuartzCore`__CADeviceUseVirtualMainDisplay_block_invoke: 12 | -> 0x100c142dc <+76>: adrp x3, 294 13 | 0x100c142e0 <+80>: add x3, x3, #0x1d5 ; "ca_virtual_main_display" 14 | 0x100c142e4 <+84>: mov w2, #0x0 ; =0 15 | 0x100c142e8 <+88>: bl 0x100c13948 ; CABootArgGetInt(std::__1::vector, std::__1::allocator>, std::__1::allocator, std::__1::allocator>>> const&, int, char const*) 16 | ``` 17 | 18 | ## 2025-09-11 19 | - Because `diagnosticd` is temporarily removed, force `com.apple.private.disable-log-mach-ports` 20 | ``` 21 | libsystem_trace.dylib`__client_has_mach_ports_disabled_block_invoke: 22 | 0x1099c72f8 <+0>: stp x29, x30, [sp, #-0x10]! 23 | 0x1099c72fc <+4>: mov x29, sp 24 | 0x1099c7300 <+8>: adrp x0, 20 25 | 0x1099c7304 <+12>: add x0, x0, #0xf92 ; "com.apple.private.disable-log-mach-ports" 26 | 0x1099c7308 <+16>: mov x1, #0x0 ; =0 27 | 0x1099c730c <+20>: bl 0x1099dab28 ; symbol stub for: xpc_copy_entitlement_for_token # replace: cmp xzr, xzr 28 | 0x1099c7310 <+24>: adrp x8, 25 29 | 0x1099c7314 <+28>: ldr x8, [x8, #0x5f0] 30 | -> 0x1099c7318 <+32>: cmp x0, x8 # replace: mov x0, x8 31 | 0x1099c731c <+36>: b.eq 0x1099c732c ; <+52> 32 | 0x1099c7320 <+40>: cbnz x0, 0x1099c7338 ; <+64> 33 | 0x1099c7324 <+44>: ldp x29, x30, [sp], #0x10 34 | 0x1099c7328 <+48>: ret 35 | 0x1099c732c <+52>: mov w8, #0x1 ; =1 36 | ``` 37 | 38 | ## 2025-09-12 39 | No app list because it skipped symlink 40 | ``` 41 | * thread #5, queue = 'com.apple.lsd.registrationIO', stop reason = breakpoint 9.1 42 | frame #0: 0x0000000105cf0488 InstalledContentLibrary`-[MIFileManager enumerateURLsForItemsInDirectoryAtURL:ignoreSymlinks:withBlock:] 43 | InstalledContentLibrary`-[MIFileManager enumerateURLsForItemsInDirectoryAtURL:ignoreSymlinks:withBlock:]: 44 | -> 0x105cf0488 <+0>: sub sp, sp, #0x70 45 | 0x105cf048c <+4>: stp x22, x21, [sp, #0x40] 46 | 0x105cf0490 <+8>: stp x20, x19, [sp, #0x50] 47 | 0x105cf0494 <+12>: stp x29, x30, [sp, #0x60] 48 | (lldb) po $x2 49 | file:///var/jb/iOSSimRootFS/Applications/ 50 | 51 | (lldb) po $x3 52 | 1 53 | 54 | (lldb) po $x4 55 | <__NSStackBlock__: 0x16cfc9fc0> 56 | signature: "B20@?0@"NSURL"8C16" 57 | invoke : 0x105cc44f4 (/private/preboot/31ACA0FA3DDF276A407C5C8B9B2C662B5EC7EEC4DB3E271DAAD289B2BE6214306D453EB8740C55EE18DA7FC691498195/dopamine-uBqPhv/procursus/iOSSim/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.0.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/InstalledContentLibrary.framework/InstalledContentLibrary`__52-[MIFilesystemScanner _scanAppsDirectory:withError:]_block_invoke) 58 | 59 | (lldb) reg w x3 0 60 | ``` 61 | 62 | ## 2025-09-13 63 | When modifying dyldhook, I encountered this issue when running `MachOMerger`: 64 | ``` 65 | FIXME: sortingRequired not implemented! 66 | ``` 67 | This is due to unresolved symbols in dyld 68 | 69 | ## 2025-09-16 70 | Fixed launchd mach port passing 71 | 72 | ## 2025-09-24 73 | Found the culprit of `xpc_connection_enable_sim2host_4sim` not working: iOS expects version `0`, while it sets `2`. Now Metal XPC is working. 74 | 75 | ## 2025-09-26 76 | Implemented `IOSurface` XPC server 77 | 78 | ## 2025-09-27 79 | `-[SWSystemSleepMonitorProvider registerForSystemPowerOnQueue:withDelegate:]` crashes due to missing entitlement in `backboardd` and `SpringBoard`. TODO: find out which one 80 | 81 | ## 2025-09-29 82 | Made a fake `MobileCoreServices` that is loaded to all simulator processes to patch out unsupported instructions on A12-A13 (temporary, should emulate later instead?) 83 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorHIDEvent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | //#import 11 | 12 | #import "FBSimulatorHID.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | extern double const DEFAULT_SWIPE_DELTA; 17 | 18 | /** 19 | A Value representing a call to the HID System. 20 | */ 21 | @interface FBSimulatorHIDEvent : NSObject 22 | 23 | #pragma mark Initializers 24 | 25 | /** 26 | A HID Event that is a touch-down followed by an immediate touch-up. 27 | 28 | @param x the x-coordinate from the top left. 29 | @param y the y-coordinate from the top left. 30 | @return a new HID event. 31 | */ 32 | + (instancetype)tapAtX:(double)x y:(double)y; 33 | 34 | /** 35 | A HID Event that is a down followed by an immediate up. 36 | 37 | @param button the button to use. 38 | @return a new HID Event. 39 | */ 40 | + (instancetype)shortButtonPress:(FBSimulatorHIDButton)button; 41 | 42 | /** 43 | A HID Event for the keyboard is a down followed by an immediate up. 44 | 45 | @param keyCode the Key Code to send. 46 | @return a new HID Event. 47 | */ 48 | + (instancetype)shortKeyPress:(unsigned int)keyCode; 49 | 50 | /** 51 | A HID touch down event. 52 | 53 | @param x the x-coordinate from the top left. 54 | @param y the y-coordinate from the top left. 55 | @return a new HID event. 56 | */ 57 | + (instancetype)touchDownAtX:(double)x y:(double)y; 58 | 59 | /** 60 | A HID touch up event. 61 | 62 | @param x the x-coordinate from the top left. 63 | @param y the y-coordinate from the top left. 64 | @return a new HID event. 65 | */ 66 | + (instancetype)touchUpAtX:(double)x y:(double)y; 67 | 68 | /** 69 | A HID Event that press the button down. 70 | 71 | @param button the button to use. 72 | @return a new HID Event. 73 | */ 74 | + (instancetype)buttonDown:(FBSimulatorHIDButton)button; 75 | 76 | /** 77 | A HID Event that press the button up. 78 | 79 | @param button the button to use. 80 | @return a new HID Event. 81 | */ 82 | + (instancetype)buttonUp:(FBSimulatorHIDButton)button; 83 | 84 | /** 85 | A HID Event from the keyboard that press the key up. 86 | 87 | @param keyCode the Key Code to send. 88 | @return a new HID Event. 89 | */ 90 | + (instancetype)keyUp:(unsigned int)keyCode; 91 | 92 | /** 93 | A HID Event from the keyboard that press the key down. 94 | 95 | @param keyCode the Key Code to send. 96 | @return a new HID Event. 97 | */ 98 | + (instancetype)keyDown:(unsigned int)keyCode; 99 | 100 | /** 101 | A HID Event for sequence of shortKeyPress events. 102 | 103 | @param sequence a sequence of Key Codes to send. 104 | @return a new HID Event. 105 | */ 106 | + (instancetype)shortKeyPressSequence:(NSArray *)sequence; 107 | 108 | /** 109 | A HID Event for performing swipe from one point to another point. swipe is a series of tap down events along the line between the starting point and the ending point with delta pixels between points. 110 | 111 | @param xStart x coordinate of the starting point 112 | @param yStart y coordinate of the starting point 113 | @param xEnd x coordinate of the ending point 114 | @param yEnd y coordinate of the ending point 115 | @param delta distance between tap down events 116 | @return a new HID Event. 117 | */ 118 | + (instancetype)swipe:(double)xStart yStart:(double)yStart xEnd:(double)xEnd yEnd:(double)yEnd delta:(double)delta duration:(double)duration; 119 | 120 | /** 121 | A HID Event consisting of multiple events 122 | 123 | @param events an array of events 124 | @return a composite event 125 | */ 126 | + (instancetype)eventWithEvents:(NSArray *)events; 127 | 128 | /** 129 | A HID Event that delays the next event by a set duration 130 | 131 | @param duration Amount of time to delay the next event by in seconds 132 | @return a new HID Event. 133 | */ 134 | + (instancetype)delay:(double)duration; 135 | 136 | #pragma mark Public Methods 137 | 138 | /** 139 | Materializes the event, performing it on the hid object. 140 | 141 | @param hid the hid to perform on. 142 | @return A future that resolves when the event has been sent. 143 | */ 144 | - (FBFuture *)performOnHID:(FBSimulatorHID *)hid; 145 | 146 | @end 147 | 148 | NS_ASSUME_NONNULL_END 149 | -------------------------------------------------------------------------------- /SimBooter/main.m: -------------------------------------------------------------------------------- 1 | @import Darwin; 2 | @import Foundation; 3 | #import 4 | 5 | kern_return_t bootstrap_look_up(mach_port_t bp, const char *service_name, mach_port_t *sp); 6 | void (*launch_sim_register_endpoint)(const char *launchd_sim_name, const char *service_name, mach_port_t service_port); 7 | void xpc_add_bundle(const char *, int); 8 | void xpc_connection_enable_sim2host_4sim(xpc_connection_t); 9 | void xpc_connection_set_instance(xpc_connection_t, const uuid_t); 10 | mach_port_t xpc_endpoint_copy_listener_port_4sim(xpc_object_t endpoint); 11 | mach_port_t SimulatorHIDServerInit(); 12 | NSMutableArray *xpcConnections; 13 | 14 | mach_port_t spawn_metal_simulator() { 15 | uuid_t uuid; 16 | uuid_generate(uuid); 17 | xpc_connection_t connection = xpc_connection_create("com.apple.metal.simulator", NULL); 18 | [xpcConnections addObject:connection]; 19 | xpc_connection_set_instance(connection, uuid); 20 | xpc_connection_set_event_handler(connection, ^(xpc_object_t object) { 21 | NSLog(@"Process received event: %@", [object description]); 22 | }); 23 | xpc_connection_enable_sim2host_4sim(connection); 24 | //xpc_connection_activate(connection); 25 | 26 | xpc_endpoint_t endpoint = xpc_endpoint_create(connection); 27 | mach_port_t port = xpc_endpoint_copy_listener_port_4sim(endpoint); 28 | assert(port != MACH_PORT_NULL); 29 | NSLog(@"Obtained port: 0x%x", port); 30 | return port; 31 | } 32 | 33 | mach_port_t spawn_iosurface_server() { 34 | //xuuid_t uuid; 35 | //uuid_generate(uuid); 36 | xpc_connection_t connection = xpc_connection_create("com.apple.IOSurface.Remote", NULL); 37 | [xpcConnections addObject:connection]; 38 | //xpc_connection_set_instance(connection, uuid); 39 | xpc_connection_set_event_handler(connection, ^(xpc_object_t object) { 40 | NSLog(@"Process received event: %@", [object description]); 41 | }); 42 | xpc_connection_enable_sim2host_4sim(connection); 43 | //xpc_connection_activate(connection); 44 | 45 | xpc_endpoint_t endpoint = xpc_endpoint_create(connection); 46 | mach_port_t port = xpc_endpoint_copy_listener_port_4sim(endpoint); 47 | assert(port != MACH_PORT_NULL); 48 | NSLog(@"Obtained port: 0x%x", port); 49 | return port; 50 | } 51 | 52 | void add_xpc_bundle(const char *path) { 53 | if(access(path, F_OK) != 0) { 54 | printf("XPC service not found at %s\n", path); 55 | exit(1); 56 | } 57 | xpc_add_bundle(path, 2); 58 | } 59 | 60 | void *dlopen_or_exit(const char *path) { 61 | void *handle = dlopen(path, RTLD_GLOBAL); 62 | if (!handle) { 63 | printf("Failed to dlopen %s: %s\n", path, dlerror()); 64 | exit(1); 65 | } else { 66 | printf("Successfully dlopened %s\n", path); 67 | } 68 | return handle; 69 | } 70 | 71 | void validate_launchd_sim_connection() { 72 | const char *label = getenv("LAUNCHD_SIM_LABEL"); 73 | mach_port_t port = MACH_PORT_NULL; 74 | bootstrap_look_up(bootstrap_port, label, &port); 75 | if(port == MACH_PORT_NULL) { 76 | printf("Failed to look up launchd_sim port for label %s\n", label); 77 | exit(1); 78 | } 79 | } 80 | 81 | int main(int argc, char *argv[], char *envp[]) { 82 | xpcConnections = [NSMutableArray array]; 83 | 84 | setenv("LAUNCHD_SIM_LABEL", "com.apple.CoreSimulator.SimDevice.00000000-0000-0000-0000-000000000000", 0); 85 | validate_launchd_sim_connection(); 86 | 87 | void *liblaunch_sim = dlopen_or_exit("/var/jb/iOSSimRootFS/usr/lib/system/host/liblaunch_sim.dylib"); 88 | launch_sim_register_endpoint = dlsym(liblaunch_sim, "launch_sim_register_endpoint"); 89 | assert(launch_sim_register_endpoint); 90 | 91 | add_xpc_bundle(JBROOT_PATH("/usr/macOS/Frameworks/MTLSimDriver.framework/XPCServices/SimRenderServer.xpc")); 92 | add_xpc_bundle(JBROOT_PATH("/usr/macOS/Frameworks/MTLSimDriver.framework/XPCServices/MTLSimDriverHost.xpc")); 93 | 94 | // register Indigo server 95 | mach_port_t indigo_port = SimulatorHIDServerInit(); 96 | launch_sim_register_endpoint(getenv("LAUNCHD_SIM_LABEL"), "IndigoHIDRegistrationPort", indigo_port); 97 | 98 | // register IOSurface server 99 | mach_port_t iosurface_port = spawn_iosurface_server(); 100 | launch_sim_register_endpoint(getenv("LAUNCHD_SIM_LABEL"), "com.apple.IOSurface.Remote", iosurface_port); 101 | 102 | // we create 3 separate metal ports just like how it's done on macOS to avoid one crashing and taking down others 103 | mach_port_t metal_apps_port = spawn_metal_simulator(); 104 | mach_port_t metal_backboardd_port = spawn_metal_simulator(); 105 | mach_port_t metal_springboard_port = spawn_metal_simulator(); 106 | launch_sim_register_endpoint(getenv("LAUNCHD_SIM_LABEL"), "com.apple.metal.simulator", metal_apps_port); 107 | launch_sim_register_endpoint(getenv("LAUNCHD_SIM_LABEL"), "com.apple.metal.simulator.backboardd", metal_backboardd_port); 108 | launch_sim_register_endpoint(getenv("LAUNCHD_SIM_LABEL"), "com.apple.metal.simulator.SpringBoard", metal_springboard_port); 109 | 110 | // register host launchd port 111 | launch_sim_register_endpoint(getenv("LAUNCHD_SIM_LABEL"), "com.apple.CoreSimulator.host.bootstrap_port", bootstrap_port); 112 | 113 | CFRunLoopRun(); 114 | return 0; 115 | } 116 | -------------------------------------------------------------------------------- /shared.m: -------------------------------------------------------------------------------- 1 | @import Darwin; 2 | @import Foundation; 3 | #import "shared.h" 4 | 5 | boolean_t mig_callback_dopamine(mach_msg_header_t *message, mach_msg_header_t *reply) { 6 | struct jbserver_mach_msg *jb_msg = (struct jbserver_mach_msg *)message; 7 | struct jbserver_mach_msg_reply *jb_reply = (struct jbserver_mach_msg_reply *)reply; 8 | assert(jb_msg->magic == JBSERVER_MACH_MAGIC); 9 | 10 | mach_port_t sender_reply_port = message->msgh_remote_port; 11 | switch(jb_msg->action) { 12 | case JBSERVER_MACH_CHECKIN: 13 | reply->msgh_size = sizeof(struct jbserver_mach_msg_checkin_reply) + MAX_TRAILER_SIZE; 14 | break; 15 | case JBSERVER_MACH_FORK_FIX: 16 | reply->msgh_size = sizeof(struct jbserver_mach_msg_forkfix_reply) + MAX_TRAILER_SIZE; 17 | break; 18 | case JBSERVER_MACH_TRUST_FILE: 19 | reply->msgh_size = sizeof(struct jbserver_mach_msg_trust_fd_reply) + MAX_TRAILER_SIZE; 20 | break; 21 | } 22 | 23 | // Forward the message to jbserver in host launchd 24 | kern_return_t result = jbclient_mach_send_msg(message, jb_reply); 25 | if(result != KERN_SUCCESS) { 26 | //NSLog(@"jbclient_mach_send_msg failed: 0x%x", result); 27 | return false; 28 | } 29 | 30 | // Fixup the reply message header, based on jbserver_send_mach_reply 31 | uint32_t bits = MACH_MSGH_BITS_REMOTE(message->msgh_bits); 32 | if (bits == MACH_MSG_TYPE_COPY_SEND) 33 | bits = MACH_MSG_TYPE_MOVE_SEND; 34 | reply->msgh_bits = MACH_MSGH_BITS(bits, 0); 35 | reply->msgh_remote_port = sender_reply_port; 36 | reply->msgh_local_port = MACH_PORT_NULL; 37 | reply->msgh_id = message->msgh_id + 100; 38 | 39 | return true; 40 | } 41 | 42 | // https://github.com/opa334/Dopamine/blob/314f7f2/BaseBin/libjailbreak/src/jbclient_mach.c#L17-L52 43 | kern_return_t jbclient_mach_send_msg_internal(mach_msg_header_t *hdr, struct jbserver_mach_msg_reply *reply, mach_port_t launchdPort, mach_port_t replyPort, boolean_t isReceivingPort) 44 | { 45 | //mach_port_t replyPort = mig_get_reply_port(); 46 | if (!replyPort) 47 | return KERN_FAILURE; 48 | 49 | //mach_port_t launchdPort = jbclient_mach_get_launchd_port(); 50 | if (!launchdPort) 51 | return KERN_FAILURE; 52 | 53 | hdr->msgh_bits |= MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, (isReceivingPort ? MACH_MSG_TYPE_MAKE_SEND : MACH_MSG_TYPE_MAKE_SEND_ONCE)); 54 | 55 | // size already set 56 | hdr->msgh_remote_port = launchdPort; 57 | hdr->msgh_local_port = replyPort; 58 | hdr->msgh_voucher_port = 0; 59 | hdr->msgh_id = 0x40000000 | 206; 60 | // 206: magic value to make WebContent work (seriously, this is the only ID that the WebContent sandbox allows) 61 | 62 | kern_return_t kr = mach_msg(hdr, MACH_SEND_MSG, hdr->msgh_size, 0, 0, 0, 0); 63 | if (kr != KERN_SUCCESS) { 64 | return kr; 65 | } 66 | 67 | kr = mach_msg(&reply->msg.hdr, MACH_RCV_MSG, 0, reply->msg.hdr.msgh_size, replyPort, 0, 0); 68 | if (kr != KERN_SUCCESS) { 69 | return kr; 70 | } 71 | 72 | // Get rid of any rights we might have received 73 | if(isReceivingPort) 74 | mach_port_deallocate(task_self_trap(), replyPort); 75 | return KERN_SUCCESS; 76 | } 77 | kern_return_t jbclient_mach_send_msg(mach_msg_header_t *hdr, struct jbserver_mach_msg_reply *reply) 78 | { 79 | return jbclient_mach_send_msg_internal(hdr, reply, jbclient_mach_get_launchd_port(), mig_get_reply_port(), false); 80 | } 81 | 82 | void fill_send_port_msg(send_port_msg *msg) { 83 | msg->header.msgh_local_port = MACH_PORT_NULL; 84 | msg->header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0) | 85 | MACH_MSGH_BITS_COMPLEX; 86 | msg->header.msgh_size = sizeof(*msg); 87 | 88 | msg->body.msgh_descriptor_count = 1; 89 | msg->special_port.disposition = MACH_MSG_TYPE_COPY_SEND; 90 | msg->special_port.type = MACH_MSG_PORT_DESCRIPTOR; 91 | } 92 | 93 | mach_port_t setup_recv_port(void) 94 | { 95 | mach_port_t p = MACH_PORT_NULL; 96 | kern_return_t kr = _kernelrpc_mach_port_allocate_trap(task_self_trap(), MACH_PORT_RIGHT_RECEIVE, &p); 97 | assert(kr == KERN_SUCCESS); 98 | kr = _kernelrpc_mach_port_insert_right_trap(task_self_trap(), p, p, MACH_MSG_TYPE_MAKE_SEND); 99 | assert(kr == KERN_SUCCESS); 100 | return p; 101 | } 102 | kern_return_t task_get_launchd_port(mach_port_t task, mach_port_t *special_port) { 103 | struct jbserver_mach_msg msg; 104 | msg.hdr.msgh_size = sizeof(msg); 105 | msg.hdr.msgh_bits = 0; 106 | msg.action = JBSERVER_MACH_GET_HOST_LAUNCHD_PORT; 107 | msg.magic = JBSERVER_MACH_MAGIC; 108 | 109 | struct { 110 | mach_msg_header_t Head; 111 | /* start of the kernel processed data */ 112 | mach_msg_body_t msgh_body; 113 | mach_msg_port_descriptor_t special_port; 114 | /* end of the kernel processed data */ 115 | mach_msg_trailer_t trailer; 116 | } reply; 117 | reply.Head.msgh_size = sizeof(reply); 118 | 119 | mach_port_t launchdPort = MACH_PORT_NULL; 120 | task_get_bootstrap_port(task_self_trap(), &launchdPort); 121 | kern_return_t kr = jbclient_mach_send_msg_internal(&msg.hdr, (struct jbserver_mach_msg_reply *)&reply, launchdPort, setup_recv_port(), true); 122 | if (kr != KERN_SUCCESS) return kr; 123 | 124 | *special_port = reply.special_port.name; 125 | return KERN_SUCCESS; 126 | } 127 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/Indigo26.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #pragma pack(push, 4) 8 | 9 | //------------------------------- 10 | // Event union 11 | //------------------------------- 12 | 13 | typedef union IndigoEvent { 14 | 15 | // --- 1,2 (Keyboard) --- 16 | struct { 17 | uint16_t usagePage; 18 | uint16_t usage; 19 | bool down; 20 | uint32_t flags; 21 | } keyboard; 22 | 23 | // --- 4 (Translation) --- 24 | struct { 25 | double x, y, z; 26 | uint32_t options; 27 | } translation; 28 | 29 | // --- 5 (Rotation) --- 30 | struct { 31 | double x, y, z; 32 | uint32_t options; 33 | } rotation; 34 | 35 | // --- 6 (Scroll / Mouse) --- 36 | struct { 37 | double x, y, z; 38 | uint32_t phase; // IOHIDEvent phase 39 | uint8_t momentum; // scroll momentum 40 | uint32_t buttonMask; 41 | double pressure; // only if mouse-with-pressure 42 | } scrollOrMouse; 43 | 44 | // --- 7 (Scale) --- 45 | struct { 46 | double x, y, z; 47 | uint32_t options; 48 | } scale; 49 | 50 | // --- 9 (Velocity) --- 51 | struct { 52 | double vx, vy, vz; 53 | uint32_t options; 54 | } velocity; 55 | 56 | // --- 11 (Digitizer Finger) --- 57 | struct { 58 | uint32_t index; 59 | uint32_t identity; 60 | uint32_t eventMask; 61 | double x, y, z; 62 | double tipPressure; 63 | double twist; 64 | uint32_t inRange; 65 | uint32_t touch; 66 | uint32_t options; 67 | unsigned int field12; // 0x20 + 0x10 + 0x40 = 0x70 68 | unsigned int field13; // 0x20 + 0x10 + 0x44 = 0x74 69 | double quality; 70 | double density; 71 | double irregularity; 72 | double minorRadius; 73 | double majorRadius; 74 | //double accuracy; 75 | } digitizerFinger; 76 | 77 | // --- 17 (Relative Pointer) --- 78 | struct { 79 | double dx, dy, dz; 80 | uint32_t buttonMask; 81 | uint32_t identity; 82 | uint32_t options; 83 | } relativePointer; 84 | 85 | // --- 23 (Dock Swipe) --- 86 | struct { 87 | uint32_t swipeMask; 88 | double dx, dy, dz; 89 | uint32_t options; 90 | } dockSwipe; 91 | 92 | // --- 32 (Force) --- 93 | struct { 94 | uint32_t identity; 95 | uint32_t mask; 96 | double level; 97 | double secondary; 98 | uint32_t options; 99 | } force; 100 | 101 | // --- 35 (Game Controller) --- 102 | struct { 103 | double dpad[4]; // up, down, left, right 104 | double face[4]; // A, B, X, Y 105 | double shoulder[4]; // L1, L2, R1, R2 106 | double joystick[4]; // lx, ly, rx, ry 107 | } gameController; 108 | 109 | // --- 31297 (Button) --- 110 | struct { 111 | uint32_t buttonMask; 112 | uint32_t state; 113 | } button; 114 | 115 | // --- 300 (Paloma Pose) --- 116 | struct { 117 | uint32_t id; 118 | uint32_t phase; 119 | float translation[3]; 120 | float orientation[4]; // quaternion 121 | } palomaPose; 122 | 123 | // --- 301–302 (Paloma Collection) --- 124 | struct { 125 | uint32_t id; 126 | uint32_t count; 127 | // vendor defined + embedded translation/orientation 128 | // appended as sub-events 129 | } palomaCollection; 130 | 131 | // --- Fallback / Vendor defined --- 132 | struct { 133 | const void *data; 134 | uint32_t length; 135 | uint32_t options; 136 | } vendorDefined; 137 | 138 | } IndigoEvent; 139 | 140 | 141 | //------------------------------- 142 | // Indigo payload wrapper 143 | //------------------------------- 144 | 145 | typedef struct { 146 | uint32_t type; // 0x00 (same as eventType?) 147 | uint64_t timestamp; // 0x04 mach_absolute_time 148 | uint32_t flags; // 0x0C IOHIDEvent flags 149 | union IndigoEvent event; // 0x10+ 150 | } IndigoPayload; 151 | 152 | 153 | //------------------------------- 154 | // Top-level Indigo Mach message 155 | //------------------------------- 156 | 157 | typedef struct { 158 | mach_msg_header_t header; // 0x00 159 | uint32_t innerSize; // 0x18 160 | uint8_t eventType; // 0x1C (see IndigoEventType below) 161 | // padding here to 0x20 162 | IndigoPayload payload; // 0x20 163 | } IndigoMessage; 164 | 165 | 166 | //------------------------------- 167 | // Event type enum 168 | //------------------------------- 169 | 170 | typedef enum { 171 | INDIGO_EVENT_KEYBOARD = 2, 172 | INDIGO_EVENT_TRANSLATION = 4, 173 | INDIGO_EVENT_ROTATION = 5, 174 | INDIGO_EVENT_SCROLL_OR_MOUSE = 6, 175 | INDIGO_EVENT_SCALE = 7, 176 | INDIGO_EVENT_VELOCITY = 9, 177 | INDIGO_EVENT_DIGITIZER_FINGER= 11, 178 | INDIGO_EVENT_REL_POINTER = 17, 179 | INDIGO_EVENT_DOCK_SWIPE = 23, 180 | INDIGO_EVENT_FORCE = 32, 181 | INDIGO_EVENT_GAME_CONTROLLER = 35, 182 | INDIGO_EVENT_BUTTON = 31297, 183 | INDIGO_EVENT_PALOMA_POSE = 300, 184 | INDIGO_EVENT_PALOMA_COLLECTION=301, 185 | INDIGO_EVENT_VENDOR_DEFINED = 0xFFFF 186 | } IndigoEventType; 187 | 188 | #pragma pack(pop) 189 | -------------------------------------------------------------------------------- /simxpctest/main.m: -------------------------------------------------------------------------------- 1 | @import Darwin; 2 | @import Metal; 3 | @import QuartzCore; 4 | #import 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void* _xpc_serializer_pack(uint64_t x0, xpc_object_t x1_inputMsg, uint64_t x2_xpcVersion, uint64_t x3); 11 | 12 | kern_return_t bootstrap_look_up(mach_port_t bp, const char *service_name, mach_port_t *sp); 13 | void xpc_add_bundle(char *, int); 14 | void xpc_connection_set_instance(xpc_connection_t, const uuid_t); 15 | void launch_sim_register_endpoint(const char *launchd_sim_name, const char *service_name, mach_port_t service_port); 16 | void xpc_connection_enable_sim2host_4sim(xpc_connection_t, int); 17 | 18 | void handle_event(void* target, void* refcon, IOHIDServiceRef service, IOHIDEventRef event) { 19 | int type = IOHIDEventGetType(event); 20 | NSLog(@"Got event of type %d", type); 21 | switch(type) { 22 | case kIOHIDEventTypeDigitizer: 23 | NSLog(@"Got digitizer event: %@", event); 24 | break; 25 | } 26 | } 27 | 28 | IOHIDEventSystemClientRef IOHIDEventSystemClientCreate( CFAllocatorRef ); 29 | int main(int argc, char *argv[], char *envp[]) { 30 | printf("Test IOHID\n"); 31 | IOHIDEventSystemRef systemRef = IOHIDEventSystemCreate(NULL); 32 | IOHIDEventSystemOpen(systemRef, handle_event, NULL, NULL, NULL); 33 | IOHIDEventSystemClientRef eventSystemClient = IOHIDEventSystemClientCreate(kCFAllocatorDefault); 34 | IOHIDEventSystemClientScheduleWithRunLoop(IOHIDEventSystemClient(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 35 | 36 | void *handle = dlopen("/System/Library/PrivateFrameworks/BackBoardHIDEventFoundation.framework/BackBoardHIDEventFoundation", RTLD_GLOBAL); 37 | assert(handle); 38 | void (*IndigoHIDSystemSpawnLoopback)(IOHIDEventSystemRef) = dlsym(handle, "IndigoHIDSystemSpawnLoopback"); 39 | assert(IndigoHIDSystemSpawnLoopback); 40 | IndigoHIDSystemSpawnLoopback(systemRef); 41 | 42 | #if 0 43 | xpc_connection_t connection; 44 | xpc_object_t dict; 45 | xpc_object_t object; 46 | xpc_object_t (*xpc_connection_create_mach_service)(const char *name, dispatch_queue_t targetq, uint64_t flags) = dlsym(RTLD_DEFAULT, "xpc_connection_create_mach_service"); 47 | assert(xpc_connection_create_mach_service); 48 | 49 | printf("Test IOSurface\n"); 50 | // xpc_connection_t connection = xpc_connection_create_mach_service("com.apple.IOSurface.Remote", NULL, 0); 51 | // xpc_connection_enable_sim2host_4sim(connection, 1); 52 | // //xpc_connection_set_instance(connection, uuid); 53 | // xpc_connection_set_event_handler(connection, ^(xpc_object_t object) { 54 | // printf("Process received event: %s", [object description].UTF8String); 55 | // }); 56 | // xpc_connection_resume(connection); 57 | // 58 | // dict = xpc_dictionary_create(NULL, NULL, 0); 59 | // xpc_dictionary_set_uint64(dict, "Method", 0); 60 | // object = xpc_connection_send_message_with_reply_sync(connection, dict); 61 | // printf("Received synced event: %s\n", [object description].UTF8String); 62 | // printf("XPC connection now: %s\n", [connection description].UTF8String); 63 | // sleep(1); 64 | 65 | CGRect frame = CGRectMake(0, 0, 100, 100); 66 | // IOMobileFramebufferRef fbConn; 67 | // IOMobileFramebufferGetMainDisplay(&fbConn); 68 | // IOMobileFramebufferGetDisplaySize(fbConn, &frame.size); 69 | // printf("Got framebuffer size: %fx%f\n", frame.size.width, frame.size.height); 70 | 71 | int bytesPerElement = 8; 72 | NSDictionary *surfaceProps = @{ 73 | //@"IOSurfaceAllocSize": @(totalBytes), 74 | @"IOSurfaceCacheMode": @1024, 75 | @"IOSurfaceWidth": @(frame.size.width), 76 | @"IOSurfaceHeight": @(frame.size.height), 77 | @"IOSurfaceMapCacheAttribute": @0, 78 | @"IOSurfaceMemoryRegion": @"PurpleGfxMem", 79 | @"IOSurfacePixelSizeCastingAllowed": @0, 80 | @"IOSurfaceBytesPerElement": @(bytesPerElement), 81 | @"IOSurfacePixelFormat": @((uint32_t)'BGRA'), 82 | }; 83 | IOSurfaceRef surface = IOSurfaceCreate((__bridge CFDictionaryRef)surfaceProps); 84 | printf("Got surface: %p\n", surface); 85 | 86 | 87 | 88 | printf("Test metal\n"); 89 | 90 | // char *frameworkPath = "/var/jb/usr/macOS/Frameworks/MTLSimDriver.framework/XPCServices/MTLSimDriverHost.xpc"; 91 | // xpc_add_bundle(frameworkPath, 2); 92 | // xpc_add_bundle(frameworkPath, 2); 93 | 94 | //uuid_t uuid; 95 | //uuid_generate(uuid); 96 | connection = xpc_connection_create_mach_service("com.apple.metal.simulator", NULL, 0); 97 | xpc_connection_enable_sim2host_4sim(connection, 1); 98 | //xpc_connection_set_instance(connection, uuid); 99 | xpc_connection_set_event_handler(connection, ^(xpc_object_t object) { 100 | printf("Process received event: %s", [object description].UTF8String); 101 | }); 102 | xpc_connection_resume(connection); 103 | 104 | dict = xpc_dictionary_create(NULL, NULL, 0); 105 | xpc_dictionary_set_uint64(dict, "requestType", 9); // XPCCompilerConnection::checkConnectionActive(bool&) 106 | object = xpc_connection_send_message_with_reply_sync(connection, dict); 107 | printf("Received synced event: %s\n", [object description].UTF8String); 108 | printf("XPC connection now: %s\n", [connection description].UTF8String); 109 | #endif 110 | 111 | CFRunLoopRun(); 112 | 113 | return 0; 114 | } 115 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // SimIndigoHIDServer.m 3 | // SimulatorShimFrameworks 4 | // 5 | // Created by Duy Tran on 30/9/25. 6 | // 7 | 8 | @import Darwin; 9 | @import Foundation; 10 | #include 11 | #include 12 | #include 13 | #import "FBSimulatorControl/HID/FBSimulatorHID.h" 14 | #import "FBSimulatorControl/HID/FBSimulatorHID+Struct26.h" 15 | #import "Indigo26.h" 16 | 17 | FBSimulatorHID_Reimplemented *instance; 18 | 19 | void handle_touch_event(IOHIDEventRef parentEvent) { 20 | NSArray *childrens = (__bridge NSArray *)IOHIDEventGetChildren(parentEvent); 21 | for (id child in childrens) { 22 | IOHIDEventRef event = (__bridge IOHIDEventRef)child; 23 | #pragma pack(push, 4) 24 | struct { 25 | IndigoMessage msg; 26 | IndigoPayload fingerPayload; 27 | } msg = { 28 | .msg = { 29 | .innerSize = sizeof(IndigoPayload), 30 | .eventType = 2, // IndigoEventTypeTouch 31 | .payload = { 32 | .type = INDIGO_EVENT_DIGITIZER_FINGER, 33 | .timestamp = mach_absolute_time(), 34 | .flags = 0, 35 | .event.digitizerFinger = { 36 | .index = 0x00400002, //IOHIDEventGetIntegerValue(parentEvent, (IOHIDEventField)kIOHIDEventFieldDigitizerIndex), 37 | .identity = IOHIDEventGetIntegerValue(parentEvent, (IOHIDEventField)kIOHIDEventFieldDigitizerIdentity), 38 | .eventMask = IOHIDEventGetIntegerValue(parentEvent, (IOHIDEventField)kIOHIDEventFieldDigitizerEventMask), 39 | .options = 0x32, 40 | .field12 = 1, 41 | .field13 = 2 42 | } 43 | } 44 | }, 45 | // .padding = 0x1ff, 46 | .fingerPayload = { 47 | .type = INDIGO_EVENT_DIGITIZER_FINGER, 48 | .timestamp = msg.msg.payload.timestamp, 49 | .flags = 0, 50 | .event.digitizerFinger = { 51 | .index = IOHIDEventGetIntegerValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerIndex), 52 | .identity = IOHIDEventGetIntegerValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerIdentity), 53 | .eventMask = IOHIDEventGetIntegerValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerEventMask), 54 | //.buttonMask = IOHIDEventGetIntegerValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerButtonMask), 55 | .x = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerX), 56 | .y = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerY), 57 | .z = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerZ), 58 | // FIXME: is this right? 59 | .tipPressure = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerPressure), 60 | .twist = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerTwist), 61 | .minorRadius = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerMinorRadius), 62 | .majorRadius = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerMajorRadius), 63 | .quality = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerQuality), 64 | .density = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerDensity), 65 | .irregularity = IOHIDEventGetFloatValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerIrregularity), 66 | // FIXME: is this right? 67 | .inRange = IOHIDEventGetIntegerValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerRange), 68 | .touch = IOHIDEventGetIntegerValue(event, (IOHIDEventField)kIOHIDEventFieldDigitizerTouch), 69 | .options = 0x32, 70 | .field12 = 1, 71 | .field13 = 2 72 | } 73 | } 74 | }; 75 | #pragma pack(pop) 76 | //memcpy(&msg.fingerPayload, &msg.msg.payload, sizeof(IndigoPayload)); 77 | BOOL sent = [instance sendIndigoMessageDirect:&msg size:sizeof(msg)]; 78 | if(!sent) { 79 | // Attempt to reconnect 80 | [instance connect]; 81 | } 82 | } 83 | } 84 | 85 | void handle_event(void* target, void* refcon, IOHIDServiceRef service, IOHIDEventRef event) { 86 | int type = IOHIDEventGetType(event); 87 | switch(type) { 88 | case kIOHIDEventTypeDigitizer: 89 | handle_touch_event(event); 90 | break; 91 | // TODO: implement these events 92 | // case kIOHIDEventTypeVendorDefined: 93 | // case kIOHIDEventTypeOrientation: 94 | // case kIOHIDEventTypeAmbientLightSensor: 95 | // case kIOHIDEventTypeAccelerometer: 96 | // case kIOHIDEventTypeProximity: 97 | // case kIOHIDEventTypeTemperature: 98 | // case 20://kIOHIDEventTypeGyro: 99 | // case 21://kIOHIDEventTypeCompass: 100 | // case 31://kIOHIDEventTypeAtmosphericPressure: 101 | // return; 102 | default: 103 | //printf("Received event of type %2d from service %p.\n", type, event); 104 | break; 105 | } 106 | } 107 | 108 | mach_port_t SimulatorHIDServerInit() { 109 | // Create and open an event system 110 | IOHIDEventSystemRef systemRef = IOHIDEventSystemCreate(NULL); 111 | IOHIDEventSystemOpen(systemRef, handle_event, NULL, NULL, NULL); 112 | 113 | // Setup our Mach listener 114 | dispatch_semaphore_t sema = dispatch_semaphore_create(0); 115 | [[FBSimulatorHID 116 | hidForSimulator:(id)[NSObject class]] 117 | onQueue:dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) doOnResolved:^(FBSimulatorHID *hid) { 118 | instance = hid; 119 | NSLog(@"HID server: %@", hid); 120 | dispatch_semaphore_signal(sema); 121 | }]; 122 | 123 | // wait until we have a valid port 124 | dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); 125 | assert(instance != nil); 126 | [instance connect]; 127 | 128 | return instance.registrationPort; 129 | 130 | } 131 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/Indigo.h: -------------------------------------------------------------------------------- 1 | // https://github.com/facebook/idb/blob/main/PrivateHeaders/SimulatorApp/Indigo.h 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | 10 | /** 11 | Structures from class-dumping Simulator.app 12 | The notions of what these fields are is from tracing the messages sent at runtime. 13 | */ 14 | 15 | //#import 16 | typedef mach_msg_header_t MachMessageHeader; 17 | 18 | #pragma pack(push, 4) 19 | 20 | /** 21 | A Quad that is sent via Indigo. 22 | This is equivalent to NSEdgeInsets, but packed. 23 | */ 24 | typedef struct { 25 | double field1; // 0x0 26 | double field2; // 0x8 27 | double field3; // 0x10 28 | double field4; // 0x18 29 | } IndigoQuad; 30 | 31 | /** 32 | An Event for Digitizer Events. 33 | 34 | The 'Location' of the touch is in the xRatio and yRatio slots. 35 | This is 0 > x > 1 and 0 > y > 1, representing the distance from the top left. 36 | The top left corner is xRatio=0.0, yRatio=0.0 37 | The bottom right corner is xRatio=1.0, yRatio=1.0 38 | The center is xRatio=0.5, yRatio=0.5 39 | 40 | The 9th and 10th Slot Represent a touch-up or touch-down. 41 | The struct is then partially repeated in the 10th slot. 42 | */ 43 | typedef struct { 44 | unsigned int index; // 0x20 + 0x10 + 0x0 = 0x30 45 | unsigned int identity; // 0x20 + 0x10 + 0x4 = 0x34 46 | unsigned int eventMask; // 0x20 + 0x10 + 0x8 = 0x38 47 | double xRatio; // 0x20 + 0x10 + 0xc = 0x3c 48 | double yRatio; // 0x20 + 0x10 + 0x14 = 0x44 49 | double zRatio; // 0x20 + 0x10 + 0x1c = 0x4c 50 | double tipPressure; // 0x20 + 0x10 + 0x24 = 0x54 51 | double twist; // 0x20 + 0x10 + 0x2c = 0x5c 52 | unsigned int inRange; // 0x20 + 0x10 + 0x34 = 0x64 53 | unsigned int touch; // 0x20 + 0x10 + 0x38 = 0x68 54 | unsigned int options; // 0x20 + 0x10 + 0x3c = 0x6c 55 | unsigned int field12; // 0x20 + 0x10 + 0x40 = 0x70 56 | unsigned int field13; // 0x20 + 0x10 + 0x44 = 0x74 57 | double field14; // 0x20 + 0x10 + 0x48 = 0x78 58 | double field15; // 0x20 + 0x10 + 0x50 = 0x80 59 | double field16; // 0x20 + 0x10 + 0x58 = 0x88 60 | double field17; // 0x20 + 0x10 + 0x60 = 0x90 61 | double field18; // 0x20 + 0x10 + 0x68 = 0x98 62 | } IndigoTouch; 63 | 64 | /* 65 | 66 | could you write a structure out of these info: 67 | ``` 68 | IOHIDEventRef IOHIDEventCreateDigitizerFingerEvent(CFAllocatorRef allocator, AbsoluteTime timeStamp, uint32_t index, uint32_t identity, uint32_t eventMask, IOHIDFloat x, IOHIDFloat y, IOHIDFloat z, IOHIDFloat tipPressure, IOHIDFloat twist, Boolean range, Boolean touch, IOOptionBits options); 69 | IOHIDEventCreateDigitizerFingerEvent( kCFAllocatorDefault, *(_QWORD *)(a1 + 4), *(unsigned int *)(a1 + 16), *(unsigned int *)(a1 + 20), *(unsigned int *)(a1 + 24), *(unsigned int *)(a1 + 68), *(unsigned int *)(a1 + 72), *(unsigned int *)(a1 + 12), *(double *)(a1 + 28), *(double *)(a1 + 36), *(double *)(a1 + 44), *(double *)(a1 + 52), *(double *)(a1 + 60)); 70 | ``` 71 | */ 72 | 73 | /** 74 | The Indigo Event for a wheel event. 75 | */ 76 | typedef struct { 77 | unsigned int field1; // 0x20 + 0x10 + 0x0 = 0x30 78 | double field2; // 0x20 + 0x10 + 0x4 = 0x34 79 | double field3; // 0x20 + 0x10 + 0xc = 0x3c 80 | double field4; // 0x20 + 0x10 + 0xc = 0x44 81 | unsigned int field5; // 0x20 + 0x10 + 0xc = 0x4c 82 | } IndigoWheel; 83 | 84 | /** 85 | The Indigo Event for a button event. 86 | */ 87 | typedef struct { 88 | unsigned int eventSource; // 0x20 + 0x10 + 0x0 = 0x30 89 | unsigned int eventType; // 0x20 + 0x10 + 0x4 = 0x34. 90 | unsigned int eventTarget; // 0x20 + 0x10 + 0x8 = 0x38 91 | unsigned int keyCode; // 0x20 + 0x10 + 0xc = 0x3c 92 | unsigned int field5; // 0x20 + 0x10 + 0x10 = 0x40 93 | } IndigoButton; 94 | 95 | #define ButtonEventSourceApplePay 0x1f4 96 | #define ButtonEventSourceHomeButton 0x0 97 | #define ButtonEventSourceLock 0x1 98 | #define ButtonEventSourceKeyboard 0x2710 99 | #define ButtonEventSourceSideButton 0xbb8 100 | #define ButtonEventSourceSiri 0x400002 101 | 102 | #define ButtonEventTargetHardware 0x33 103 | #define ButtonEventTargetKeyboard 0x64 104 | 105 | /** 106 | These are Derived from NSEventTypeKeyDown & NSEventTypeKeyUp. 107 | Subtracted by 10/0xa 108 | */ 109 | #define ButtonEventTypeDown 0x1 110 | #define ButtonEventTypeUp 0x2 111 | 112 | /** 113 | An Indigo Event for the accelerometer. 114 | */ 115 | typedef struct { 116 | unsigned int field1; // 0x20 + 0x10 + 0x0 = 0x30 117 | unsigned char field2[40]; // 0x20 + 0x10 + 0x4 = 0x34 118 | } IndigoAccelerometer; 119 | 120 | /** 121 | An Indigo Event for force touch. 122 | */ 123 | typedef struct { 124 | unsigned int field1; // 0x20 + 0x10 + 0x0 = 0x30 125 | double field2; // 0x20 + 0x10 + 0x4 = 0x34 126 | unsigned int field3; // 0x20 + 0x10 + 0xc = 0x3c 127 | double field4; // 0x20 + 0x10 + 0x10 = 0x40 128 | } IndigoForce; 129 | 130 | /** 131 | An Indigo Event for a Game Controller. 132 | */ 133 | typedef struct { 134 | IndigoQuad dpad; // 0x20 + 0x10 + 0x0 = 0x30 135 | IndigoQuad face; // 0x20 + 0x10 + 0x20 = 0x50 136 | IndigoQuad shoulder; // 0x20 + 0x10 + 0x40 = 0x70 137 | IndigoQuad joystick; // 0x20 + 0x10 + 0x60 = 0x90 138 | } IndigoGameController; 139 | 140 | /** 141 | A Union of all possible event types. 142 | The eventType to use is identified in the header. 143 | */ 144 | typedef union { 145 | IndigoTouch touch; 146 | IndigoWheel wheel; 147 | IndigoButton button; 148 | IndigoAccelerometer accelerometer; 149 | IndigoForce force; 150 | IndigoGameController gameController; 151 | } IndigoEvent; 152 | 153 | /** 154 | The Payload embedded inside an Message. 155 | Embedded below the usual mach_msg headers. 156 | */ 157 | typedef struct { 158 | unsigned int type; // 0x20 + 0x0 = 0x20: 159 | unsigned long long timestamp; // 0x20 + 0x04 = 0x24: This is a mach_absolute_time (uint64_t). 160 | unsigned int flags; // 0x20 + 0x10 = 0x2c 161 | IndigoEvent event; // 0x20 + 0x10 = 0x30 162 | } IndigoPayload; 163 | 164 | /** 165 | The Indigo Message sent over the wire with mach_msg_send. 166 | */ 167 | typedef struct { 168 | MachMessageHeader header; // 0x0 169 | unsigned int innerSize; // 0x18 170 | unsigned char eventType; // 0x1c 171 | IndigoPayload payload; // 0x20 172 | } IndigoMessage; 173 | 174 | #define IndigoEventTypeButton 1 175 | #define IndigoEventTypeTouch 2 176 | #define IndigoEventTypeUnknown 3 177 | 178 | #pragma pack(pop) 179 | -------------------------------------------------------------------------------- /dyld.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tmp/dyld.orig.txt b/tmp/dyld.curr.txt 2 | index 218c79a..e62e4af 100644 3 | --- a/tmp/dyld.orig.txt 4 | +++ b/tmp/dyld.curr.txt 5 | @@ -1,4 +1,4 @@ 6 | -/tmp/dyld.orig.mod: 7 | +/var/mobile/Documents/ModDyld/dyld.macOS.old: 8 | (__TEXT,__text) section 9 | _mach_msg_destroy: 10 | 0000000000005000 pacibsp 11 | @@ -8859,7 +8859,7 @@ __ZN5dyld413ProcessConfig7ProcessC2EPKNS_10KernelArgsERNS_15SyscallDelegateERN3l 12 | 000000000000d700 cbnz w0, 0xd708 13 | 000000000000d704 str wzr, [x19, #0x9c] 14 | 000000000000d708 adrp x1, 119 ; 0x84000 15 | -000000000000d70c add x1, x1, #0xb50 ; literal pool for: "/System/Library/PrivateFrameworks/CoreFP.framework/Versions/A/fairplayd" 16 | +000000000000d70c add x1, x1, #0xb50 ; literal pool for: "/usr/sbin/fairplayd.H2" 17 | 000000000000d710 mov x0, x20 18 | 000000000000d714 bl __platform_strcmp 19 | 000000000000d718 cbnz w0, 0xd720 20 | @@ -9534,8 +9534,8 @@ __ZN5dyld413ProcessConfig8SecurityC2ERNS0_7ProcessERNS_15SyscallDelegateE: 21 | 000000000000e154 ldp x22, x21, [sp], #0x30 22 | 000000000000e158 retab 23 | __ZN5dyld413ProcessConfig8Security7getAMFIERKNS0_7ProcessERNS_15SyscallDelegateE: 24 | -000000000000e15c pacibsp 25 | -000000000000e160 sub sp, sp, #0x40 26 | +000000000000e15c mov x0, #0xff 27 | +000000000000e160 ret 28 | 000000000000e164 stp x22, x21, [sp, #0x10] 29 | 000000000000e168 stp x20, x19, [sp, #0x20] 30 | 000000000000e16c stp x29, x30, [sp, #0x30] 31 | @@ -9897,7 +9897,7 @@ __ZN5dyld411CacheFinderC2ERKNS_13ProcessConfig7ProcessERKNS1_7LoggingERNS_15Sysc 32 | 000000000000e6f0 ldr x0, [x19, #0x8] 33 | 000000000000e6f4 ldr w8, [x20, #0x3c] 34 | 000000000000e6f8 adrp x9, 119 ; 0x85000 35 | -000000000000e6fc add x9, x9, #0xde5 ; literal pool for: "/System/Library/dyld/" 36 | +000000000000e6fc add x9, x9, #0xde5 ; literal pool for: "/var/sy/Library/dyld/" 37 | 000000000000e700 adrp x10, 119 ; 0x85000 38 | 000000000000e704 add x10, x10, #0xdbe ; literal pool for: "/System/DriverKit/System/Library/dyld/" 39 | 000000000000e708 cmp w8, #0xa 40 | @@ -44863,8 +44863,8 @@ __ZNK5dyld415SyscallDelegate9amfiFlagsEbb: 41 | 000000000002ff6c add sp, sp, #0x20 42 | 000000000002ff70 retab 43 | __ZNK5dyld415SyscallDelegate15internalInstallEv: 44 | -000000000002ff74 pacibsp 45 | -000000000002ff78 stp x29, x30, [sp, #-0x10]! 46 | +000000000002ff74 mov x0, #0x1 47 | +000000000002ff78 ret 48 | 000000000002ff7c mov x29, sp 49 | 000000000002ff80 mov w0, #0x10 50 | 000000000002ff84 bl _csr_check 51 | @@ -57401,7 +57401,7 @@ __ZN5dyld3L18preflightCacheFileERKNS_18SharedCacheOptionsEPNS_19SharedCacheLoadI 52 | 000000000003bfa0 retab 53 | 000000000003bfa4 add x24, sp, #0xed8 54 | 000000000003bfa8 ldr w25, [sp, #0xee8] 55 | -000000000003bfac cmp w25, #0xe0 56 | +000000000003bfac cmp w25, #0x10, lsl #12 57 | 000000000003bfb0 b.lo 0x3bfe0 58 | 000000000003bfb4 ldr w8, [x22, #0xc] 59 | 000000000003bfb8 ldr w9, [sp, #0xfb0] 60 | @@ -64608,7 +64608,7 @@ __ZN5dyld44APIs35dyld_shared_cache_find_iterate_textEPKhPPKcU13block_pointerFvPK 61 | 0000000000042e4c orr x8, x8, x9 62 | 0000000000042e50 cbz x8, 0x42f98 63 | 0000000000042e54 adrp x2, 67 ; 0x85000 64 | -0000000000042e58 add x2, x2, #0xde5 ; literal pool for: "/System/Library/dyld/" 65 | +0000000000042e58 add x2, x2, #0xde5 ; literal pool for: "/var/sy/Library/dyld/" 66 | 0000000000042e5c add x3, sp, #0x60 67 | 0000000000042e60 mov x0, x21 68 | 0000000000042e64 mov x1, x20 69 | @@ -64619,7 +64619,7 @@ __ZN5dyld44APIs35dyld_shared_cache_find_iterate_textEPKhPPKcU13block_pointerFvPK 70 | 0000000000042e78 adrp x25, 92 ; 0x9e000 71 | 0000000000042e7c add x25, x25, #0x6d8 72 | 0000000000042e80 adrp x24, 67 ; 0x85000 73 | -0000000000042e84 add x24, x24, #0xde5 ; literal pool for: "/System/Library/dyld/" 74 | +0000000000042e84 add x24, x24, #0xde5 ; literal pool for: "/var/sy/Library/dyld/" 75 | 0000000000042e88 ldr x1, [x25, x26] 76 | 0000000000042e8c strb wzr, [sp, #0x68] 77 | 0000000000042e90 add x0, sp, #0x68 78 | @@ -88054,7 +88054,7 @@ __ZNK5dyld311GradedArchs5gradeEjjb: 79 | 00000000000595a0 ldr w11, [x10, #0x4] 80 | 00000000000595a4 cmp w11, w9 81 | 00000000000595a8 b.ne 0x595b8 82 | -00000000000595ac ldrb w10, [x10, #0x8] 83 | +00000000000595ac mov w10, #0x0 84 | 00000000000595b0 cbz w10, 0x595cc 85 | 00000000000595b4 tbnz w3, #0x0, 0x595cc 86 | 00000000000595b8 add x8, x8, #0xc 87 | @@ -88436,8 +88436,8 @@ ____ZNK5dyld39MachOFile16builtForPlatformENS_8PlatformEb_block_invoke: 88 | 0000000000059b60 strb w9, [x8, #0x18]! 89 | 0000000000059b64 ret 90 | __ZNK5dyld39MachOFile19loadableIntoProcessENS_8PlatformEPKc: 91 | -0000000000059b68 pacibsp 92 | -0000000000059b6c stp x24, x23, [sp, #-0x40]! 93 | +0000000000059b68 mov x0, #0x1 94 | +0000000000059b6c ret 95 | 0000000000059b70 stp x22, x21, [sp, #0x10] 96 | 0000000000059b74 stp x20, x19, [sp, #0x20] 97 | 0000000000059b78 stp x29, x30, [sp, #0x30] 98 | @@ -90774,7 +90774,7 @@ __ZN5dyld39MachOFile15compatibleSliceER11DiagnosticsPKvmPKcNS_8PlatformEbRKNS_11 99 | 000000000005bef4 add x29, sp, #0x40 100 | 000000000005bef8 sub sp, sp, #0x260 101 | 000000000005befc mov x23, x6 102 | -000000000005bf00 mov x24, x5 103 | +000000000005bf00 mov x24, #0x1 104 | 000000000005bf04 mov x22, x4 105 | 000000000005bf08 mov x21, x3 106 | 000000000005bf0c mov x25, x2 107 | @@ -109715,10 +109715,10 @@ _OUTLINED_FUNCTION_1: 108 | 000000000006e2c4 b ___assert_rtn 109 | _amfi_check_dyld_policy_self: 110 | 000000000006e2c8 cbz x1, 0x6e334 111 | -000000000006e2cc pacibsp 112 | -000000000006e2d0 sub sp, sp, #0x40 113 | -000000000006e2d4 stp x20, x19, [sp, #0x20] 114 | -000000000006e2d8 stp x29, x30, [sp, #0x30] 115 | +000000000006e2cc mov x8, #0xff 116 | +000000000006e2d0 str x8, [x1] 117 | +000000000006e2d4 mov x0, #0x0 118 | +000000000006e2d8 ret 119 | 000000000006e2dc add x29, sp, #0x30 120 | 000000000006e2e0 mov x19, x1 121 | 000000000006e2e4 str xzr, [x1] 122 | @@ -113336,7 +113336,7 @@ _ignition_get_shared_cache_directory: 123 | 0000000000071a44 ldr x8, [x0] 124 | 0000000000071a48 ldr w8, [x8, #0x24] 125 | 0000000000071a4c adrp x9, 20 ; 0x85000 126 | -0000000000071a50 add x9, x9, #0xde5 ; literal pool for: "/System/Library/dyld/" 127 | +0000000000071a50 add x9, x9, #0xde5 ; literal pool for: "/var/sy/Library/dyld/" 128 | 0000000000071a54 adrp x10, 20 ; 0x85000 129 | 0000000000071a58 add x10, x10, #0xdbe ; literal pool for: "/System/DriverKit/System/Library/dyld/" 130 | 0000000000071a5c cmp w8, #0xa 131 | @@ -123035,7 +123035,7 @@ _mach_msg_overwrite: 132 | 000000000007ae58 ccmp x9, #0x0, #0x4, eq 133 | 000000000007ae5c b.eq 0x7ae64 134 | 000000000007ae60 str w6, [x0, #0x8] 135 | -000000000007ae64 orr x1, x13, #0x800000000 136 | +000000000007ae64 orr x1, x13, #0x400000000 137 | 000000000007ae68 tbnz x13, #0x20, 0x7aeb8 138 | 000000000007ae6c mov x8, #0x0 139 | 000000000007ae70 mov w7, w5 140 | -------------------------------------------------------------------------------- /SimBootProcess.md: -------------------------------------------------------------------------------- 1 | ## Simulator boot process 2 | > [!NOTE] 3 | > `93463F5E-ECBC-4762-ABC7-7CE6E88FED59` is an example simulator data UUID. 4 | 5 | ### `com.apple.CoreSimulator.CoreSimulatorService` 6 | - When it receives a request to boot a simulator, it will submit a launch job to `launchd` to spawn `simulator-trampoline`. 7 | ```c 8 | * thread #5, queue = 'com.apple.CoreSimulator.SimDevice.bootstrapQueue.93463F5E-ECBC-4762-ABC7-7CE6E88FED59', stop reason = breakpoint 3.1 9 | * frame #0: 0x00000001934863e8 ServiceManagement`SMJobSubmit 10 | frame #1: 0x000000010021f7b0 CoreSimulator`-[SimDevice createLaunchdJobWithBinpref:extraEnvironment:disabledJobs:error:] + 4732 11 | frame #2: 0x0000000100221db0 CoreSimulator`-[SimDevice _onBootstrapQueue_bootWithOptions:deathMonitorPort:error:] + 932 12 | 13 | (lldb) po $x1 14 | { 15 | ExitTimeOut = 33; 16 | Label = "com.apple.CoreSimulator.SimDevice.93463F5E-ECBC-4762-ABC7-7CE6E88FED59"; 17 | LaunchOnlyOnce = 1; 18 | LegacyTimers = 1; 19 | LimitLoadToSessionType = Background; 20 | MachServices = { 21 | "com.apple.CoreSimulator.SimDevice.93463F5E-ECBC-4762-ABC7-7CE6E88FED59" = { 22 | ResetAtClose = 1; 23 | }; 24 | }; 25 | POSIXSpawnType = Interactive; 26 | Program = "/Library/Developer/PrivateFrameworks/CoreSimulator.framework/Resources/simulator-trampoline"; 27 | ProgramArguments = ( 28 | "simulator-trampoline", 29 | "/Library/Developer/CoreSimulator/Volumes/iOS_20E247/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 16.4.simruntime/Contents/Resources/RuntimeRoot/sbin/launchd_sim_trampoline", 30 | "/Library/Developer/CoreSimulator/Volumes/iOS_20E247/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 16.4.simruntime/Contents/Resources/RuntimeRoot/sbin/launchd_sim", 31 | "/Users/duy/Library/Developer/CoreSimulator/Devices/93463F5E-ECBC-4762-ABC7-7CE6E88FED59/data/var/run/launchd_bootstrap.plist" 32 | ); 33 | } 34 | ``` 35 | 36 | - At this point, `simulator-trampoline` has not been spawned yet, but you can ask launchd to spawn it early: 37 | ```sh 38 | launchctl kickstart user/501/com.apple.CoreSimulator.SimDevice.93463F5E-ECBC-4762-ABC7-7CE6E88FED59 39 | ``` 40 | - It will then spawn `simulator-trampoline` and pass some host mach ports to it using `liblaunch_sim.dylib`'s `launch_sim_register_endpoint` via `SimLaunchHost.*` 41 | > [!NOTE] 42 | > Many optional ports are excluded from this list, but I still left the important ones: 43 | > - `IndigoHIDRegistrationPort`: proxy HID events. Without this port, keyboard and touch input won't work. 44 | > - `com.apple.metal.simulator`: proxy Metal via XPC. Without this port, simulator will fallback to software rendering. 45 | ```c 46 | * thread #5, queue = 'com.apple.CoreSimulator.SimDevice.bootstrapQueue.93463F5E-ECBC-4762-ABC7-7CE6E88FED59', stop reason = breakpoint 1.1 47 | * frame #0: 0x0000000100238374 CoreSimulator`-[SimLaunchHostClient startNewSession:endpointsToRegister:bindPort:deathQueue:deathHandler:error:] 48 | frame #1: 0x000000010022065c CoreSimulator`-[SimDevice startLaunchdWithDeathPort:error:] + 812 49 | frame #2: 0x0000000100221ec4 CoreSimulator`-[SimDevice _onBootstrapQueue_bootWithOptions:deathMonitorPort:error:] + 1208 50 | 51 | (lldb) po $x3 52 | { 53 | IndigoHIDRegistrationPort = ""; 54 | "com.apple.CoreSimulator.SimFramebufferServer" = ""; 55 | "com.apple.IOSurface.Remote" = ""; 56 | "com.apple.SystemConfiguration.configd" = ""; 57 | "com.apple.metal.simulator" = ""; 58 | } 59 | ``` 60 | - Afterwards, `simulator-trampoline` starts 61 | 62 | ### `simulator-trampoline` 63 | - It performs signature validation on `launchd_sim` using `SecStaticCodeCheckValidity`. This can be bypassed by setting a default which requires patching internal OS check: 64 | ```sh 65 | defaults write com.apple.CoreSimulator DisableLaunchdSignatureCheck -bool true 66 | ``` 67 | 68 | - It then sends a request to `SimulatorTrampoline.xpc` to set responsibility something idk. This can also be bypassed by setting a default: 69 | ```sh 70 | defaults write com.apple.CoreSimulator DisableResponsibility -bool true 71 | ``` 72 | 73 | - Finally, it execve's `launchd_sim_trampoline` with the remaining arguments passed previously. 74 | 75 | ### `launchd_sim_trampoline` 76 | - It enables case sensitive filesystem by using a hidden policy flag as described in [this writeup](https://worthdoingbadly.com/casesensitive-iossim/). 77 | - It calls `bootstrap_check_in("com.apple.CoreSimulator.SimDevice.93463F5E-ECBC-4762-ABC7-7CE6E88FED59")` to register itself with the host `launchd`. 78 | - ~~It `setenv("XPC_SIMULATOR_HOLDING_TANK_FD_HACK")`~~ something I haven't looked into yet 79 | - It spawns another process of itself with `argv[0] = "launchd_sim_trampoline_tank"` 80 | - It obtains `launchd_sim_trampoline_tank`'s mach port using `bootstrap_look_up2("com.apple.xpc.sim.launchd.rendezvous")`, and proceeds to validate it by sending a message with `msgh_id = 707` 81 | - Finally, it passes `launchd_sim_trampoline_tank`'s mach port to `posix_spawnattr_setspecialport_np(TASK_BOOTSTRAP_PORT)` and jumps to `launchd_sim` with the remaining plist path argument 82 | 83 | ### `launchd_sim_trampoline_tank` 84 | - Spawned by `launchd_sim_trampoline` to stash the `launchd_sim_trampoline`'s bootstrap port in order to be retrieved later by `launchd_sim`. 85 | - It calls `bootstrap_check_in2("com.apple.xpc.sim.launchd.rendezvous")` to allow `launchd_sim_trampoline` to validate and set it as the temporary bootstrap port when jumping to `launchd_sim` 86 | - I haven't looked into this much, but it handles at least 2 `msgh_id`: 87 | + `707`: used to validate the connection with `launchd_sim_trampoline` before `execve`'ing to `launchd_sim` 88 | + `708`: used by `launchd_sim` to retrieve the stashed bootstrap port. After sending the reply, it will exit. 89 | 90 | ### `launchd_sim` 91 | - Here comes the interesting part: `launchd_sim` is the real `launchd` for the simulator. 92 | - It first retrieves the stashed bootstrap port from `launchd_sim_trampoline_tank` by sending a message with `msgh_id = 708` and save it somewhere, then the temporary bootstrap port is discarded. Then it waits for `launchd_sim_trampoline_tank` to exit 93 | - It reads the plist file passed as argument to get the simulator's environment and other configurations 94 | - Finally it enters the main loop to spawn and handle launch jobs 95 | 96 | ## Misc notes 97 | ### How Metal XPC is spawned 98 | - SimRenderServer is responsible for spawning 3 `SimMetalHost.xpc` processes. 99 | ``` 100 | (lldb) po (char*)$x0 101 | "com.apple.CoreSimulator.SimRenderingServices.SimMetalHost" 102 | Process 69822 stopped 103 | * thread #5, queue = 'ROCKSessionManager.invocationQueue.43CC8A06-C7E9-4C0D-8DAD-955F83895B0A', stop reason = breakpoint 2.1 104 | frame #0: 0x0000000185b86fe0 libxpc.dylib`xpc_connection_create 105 | libxpc.dylib`xpc_connection_create: 106 | -> 0x185b86fe0 <+0>: pacibsp 107 | 0x185b86fe4 <+4>: stp x20, x19, [sp, #-0x20]! 108 | 0x185b86fe8 <+8>: stp x29, x30, [sp, #0x10] 109 | 0x185b86fec <+12>: add x29, sp, #0x10 110 | Target 0: (SimRenderServer) stopped. 111 | (lldb) bt 112 | * thread #5, queue = 'ROCKSessionManager.invocationQueue.43CC8A06-C7E9-4C0D-8DAD-955F83895B0A', stop reason = breakpoint 2.1 113 | * frame #0: 0x0000000185b86fe0 libxpc.dylib`xpc_connection_create 114 | frame #1: 0x0000000102ec323c SimRenderServer`___lldb_unnamed_symbol1616 + 1188 115 | frame #2: 0x0000000102ec98f4 SimRenderServer`SimRenderServer.MetalDeviceDescriptor.init(device: __C.SimDeviceIOInterface, implementationURL: Foundation.URL) -> SimRenderServer.MetalDeviceDescriptor + 804 116 | frame #3: 0x0000000102ec92e0 SimRenderServer`static SimRenderServer.MetalDeviceDescriptor.makeDescriptor(device: __C.SimDeviceIOInterface) -> Swift.Optional + 744 117 | frame #4: 0x0000000102ec7550 SimRenderServer`SimRenderServer.RenderServerBundle.createDefaultPorts(forDevice: __C.SimDeviceIOInterface) throws -> Swift.Array<__C.SimDeviceIOPortInterface> + 716 118 | ``` 119 | -------------------------------------------------------------------------------- /Frameworks/IOMobileFramebuffer.framework/IOMobileFramebuffer.tbd: -------------------------------------------------------------------------------- 1 | --- !tapi-tbd-v3 2 | archs: [ armv7, armv7s, arm64, arm64e ] 3 | platform: ios 4 | flags: [ flat_namespace ] 5 | install-name: /System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/IOMobileFramebuffer 6 | current-version: 1 7 | compatibility-version: 1 8 | exports: 9 | - archs: [ armv7, armv7s, arm64, arm64e ] 10 | symbols: [ _IOMobileFrameBufferEnableDebugTracing, 11 | _IOMobileFrameBufferEnableVBLTraces, 12 | _IOMobileFrameBufferGetDebugTraces, 13 | _IOMobileFrameBufferGetMirroringCapability, 14 | _IOMobileFrameBufferPrintDebugTraces, 15 | _IOMobileFrameBufferSetLogLevel, 16 | _IOMobileFrameBufferprintDisplayRegs, 17 | _IOMobileFramebufferALSSEnableWindows, 18 | _IOMobileFramebufferALSSGetRGBCoeffs, 19 | _IOMobileFramebufferALSSGetWindows, 20 | _IOMobileFramebufferALSSGetWindowsSums, 21 | _IOMobileFramebufferALSSSetRGBCoeffs, 22 | _IOMobileFramebufferALSSSetWindows, 23 | _IOMobileFramebufferChangeFrameInfo, 24 | _IOMobileFramebufferCopyLayerDisplayedSurface, 25 | _IOMobileFramebufferCopyProperty, 26 | _IOMobileFramebufferCreateDisplayList, 27 | _IOMobileFramebufferCreateStatistics, 28 | _IOMobileFramebufferDisableCRCNotifications, 29 | _IOMobileFramebufferDisableHotPlugDetectNotifications, 30 | _IOMobileFramebufferDisablePowerNotifications, 31 | _IOMobileFramebufferDisableVSyncNotifications, 32 | _IOMobileFramebufferEnableCRCNotifications, 33 | _IOMobileFramebufferEnableDisableDithering, 34 | _IOMobileFramebufferEnableDisableVideoPowerSavings, 35 | _IOMobileFramebufferEnableHotPlugDetectNotifications, 36 | _IOMobileFramebufferEnableMirroring, 37 | _IOMobileFramebufferEnablePowerNotifications, 38 | _IOMobileFramebufferEnableStatistics, 39 | _IOMobileFramebufferEnableVSyncNotifications, 40 | _IOMobileFramebufferFactoryPortal, 41 | _IOMobileFramebufferFrameInfo, 42 | _IOMobileFramebufferGetBlock, 43 | _IOMobileFramebufferGetBrightnessControlCapabilities, 44 | _IOMobileFramebufferGetBrightnessControlInfo, 45 | _IOMobileFramebufferGetBufBlock, 46 | _IOMobileFramebufferGetCRCNotifyMessageCount, 47 | _IOMobileFramebufferGetCRCRunLoopSource, 48 | _IOMobileFramebufferGetCanvasSizes, 49 | _IOMobileFramebufferGetColorRemapMode, 50 | _IOMobileFramebufferGetCurrentAbsoluteTime, 51 | _IOMobileFramebufferGetDigitalOutState, 52 | _IOMobileFramebufferGetDisplayArea, 53 | _IOMobileFramebufferGetDisplaySize, 54 | _IOMobileFramebufferGetDotPitch, 55 | _IOMobileFramebufferGetFrameworkInfo, 56 | _IOMobileFramebufferGetGammaTable, 57 | _IOMobileFramebufferGetHDCPAuthenticationProtocol, 58 | _IOMobileFramebufferGetHDCPDownstreamState, 59 | _IOMobileFramebufferGetHDCPRunLoopSource, 60 | _IOMobileFramebufferGetHotPlugRunLoopSource, 61 | _IOMobileFramebufferGetID, 62 | _IOMobileFramebufferGetLayerDefaultSurface, 63 | _IOMobileFramebufferGetLinkQuality, 64 | _IOMobileFramebufferGetMainDisplay, 65 | _IOMobileFramebufferGetMatrix, 66 | _IOMobileFramebufferGetMirrorError, 67 | _IOMobileFramebufferGetProtectionOptions, 68 | _IOMobileFramebufferGetRunLoopSource, 69 | _IOMobileFramebufferGetSecondaryDisplay, 70 | _IOMobileFramebufferGetServiceObject, 71 | _IOMobileFramebufferGetSupportedDigitalOutModes, 72 | _IOMobileFramebufferGetTypeID, 73 | _IOMobileFramebufferGetVSyncRunLoopSource, 74 | _IOMobileFramebufferGetWirelessSurface, 75 | _IOMobileFramebufferGetWirelessSurfaceWithOptions, 76 | _IOMobileFramebufferHDCPGetReply, 77 | _IOMobileFramebufferHDCPSendRequest, 78 | _IOMobileFramebufferInstallVirtualDisplay, 79 | _IOMobileFramebufferInstallVirtualDisplays, 80 | _IOMobileFramebufferIsMainDisplay, 81 | _IOMobileFramebufferKernelTests, 82 | _IOMobileFramebufferOpen, 83 | _IOMobileFramebufferOpenByName, 84 | _IOMobileFramebufferReadyForSwap, 85 | _IOMobileFramebufferRequestPowerChange, 86 | _IOMobileFramebufferSPLCGetBrightness, 87 | _IOMobileFramebufferSPLCSetBrightness, 88 | _IOMobileFramebufferScheduleWithDispatchQueue, 89 | _IOMobileFramebufferSetBlock, 90 | _IOMobileFramebufferSetBrightnessControlCallback, 91 | _IOMobileFramebufferSetBrightnessCorrection, 92 | _IOMobileFramebufferSetCanvasSize, 93 | _IOMobileFramebufferSetColorRemapMode, 94 | _IOMobileFramebufferSetContrast, 95 | _IOMobileFramebufferSetDebugFlags, 96 | _IOMobileFramebufferSetDigitalOutMode, 97 | _IOMobileFramebufferSetDisplayDevice, 98 | _IOMobileFramebufferSetDroppable, 99 | _IOMobileFramebufferSetFlags, 100 | _IOMobileFramebufferSetGammaTable, 101 | _IOMobileFramebufferSetIdleBuffer, 102 | _IOMobileFramebufferSetLine21Data, 103 | _IOMobileFramebufferSetMatrix, 104 | _IOMobileFramebufferSetMirrorContentRegion, 105 | _IOMobileFramebufferSetParameter, 106 | _IOMobileFramebufferSetRenderingAngle, 107 | _IOMobileFramebufferSetTVOutMode, 108 | _IOMobileFramebufferSetTVOutSignalType, 109 | _IOMobileFramebufferSetUnderrunColor, 110 | _IOMobileFramebufferSetVideoDACGain, 111 | _IOMobileFramebufferSetWSSInfo, 112 | _IOMobileFramebufferSetWhiteOnBlackMode, 113 | _IOMobileFramebufferSupportedFrameInfo, 114 | _IOMobileFramebufferSurfaceIsReplaceable, 115 | _IOMobileFramebufferSwapActiveRegion, 116 | _IOMobileFramebufferSwapBegin, 117 | _IOMobileFramebufferSwapCancel, 118 | _IOMobileFramebufferSwapCancelAll, 119 | _IOMobileFramebufferSwapDirtyRegion, 120 | _IOMobileFramebufferSwapEnd, 121 | _IOMobileFramebufferSwapSetBackgroundColor, 122 | _IOMobileFramebufferSwapSetBrightness, 123 | _IOMobileFramebufferSwapSetBrightnessLimit, 124 | _IOMobileFramebufferSwapSetColorMatrix, 125 | _IOMobileFramebufferSwapSetDisplayEdr, 126 | _IOMobileFramebufferSwapSetGammaTable, 127 | _IOMobileFramebufferSwapSetICCCurve, 128 | _IOMobileFramebufferSwapSetICCMatrix, 129 | _IOMobileFramebufferSwapSetLayer, 130 | _IOMobileFramebufferSwapSetParams, 131 | _IOMobileFramebufferSwapSetTimestamp, 132 | _IOMobileFramebufferSwapSetTimestamps, 133 | _IOMobileFramebufferSwapSetUISubRegion, 134 | _IOMobileFramebufferSwapSetVideoDestEdgeAlpha, 135 | _IOMobileFramebufferSwapSignal, 136 | _IOMobileFramebufferSwapSubtitleRegion, 137 | _IOMobileFramebufferSwapUIEdgeBlendMode, 138 | _IOMobileFramebufferSwapWait, 139 | _IOMobileFramebufferSwapWaitWithTimeout, 140 | _IOMobileFramebufferSwapWorkaroundSettings, 141 | _IOMobileFramebufferUnscheduleFromDispatchQueue, 142 | _IOMobileFramebufferWaitSurface, _kIOMFB_TotalSwaps, 143 | _kIOMFB_TotalVBLs ] 144 | ... 145 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorIndigoHID.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "FBSimulatorIndigoHID.h" 9 | 10 | //#import 11 | //#import 12 | #import "FBControlCore/Async/FBFuture.h" 13 | #import "Indigo.h" 14 | 15 | #import 16 | #import 17 | 18 | #include 19 | #include 20 | 21 | //#import "FBSimulatorControlFrameworkLoader.h" 22 | 23 | typedef struct { 24 | IndigoMessage *(*MessageForButton)(int keyCode, int op, int target); 25 | IndigoMessage *(*MessageForKeyboardArbitrary)(int keyCode, int op); 26 | IndigoMessage *(*MessageForMouseNSEvent)(CGPoint *point0, CGPoint *point1, int target, int eventType, BOOL something); 27 | } IndigoCalls; 28 | 29 | @interface FBSimulatorIndigoHID_Reimplemented : FBSimulatorIndigoHID 30 | 31 | @end 32 | 33 | @implementation FBSimulatorIndigoHID 34 | 35 | #pragma mark Initializers 36 | 37 | + (instancetype)reimplemented 38 | { 39 | return [FBSimulatorIndigoHID_Reimplemented new]; 40 | } 41 | 42 | #pragma mark Public 43 | 44 | - (NSData *)keyboardWithDirection:(FBSimulatorHIDDirection)direction keyCode:(unsigned int)keyCode 45 | { 46 | size_t messageSize; 47 | IndigoMessage *message = [self keyboardMessageWithDirection:direction keyCode:keyCode messageSizeOut:&messageSize]; 48 | return [NSData dataWithBytesNoCopy:message length:messageSize freeWhenDone:YES]; 49 | } 50 | 51 | - (NSData *)buttonWithDirection:(FBSimulatorHIDDirection)direction button:(FBSimulatorHIDButton)button 52 | { 53 | size_t messageSize; 54 | IndigoMessage *message = [self buttonMessageWithDirection:direction button:button messageSizeOut:&messageSize]; 55 | return [NSData dataWithBytesNoCopy:message length:messageSize freeWhenDone:YES]; 56 | } 57 | 58 | - (NSData *)touchScreenSize:(CGSize)screenSize direction:(FBSimulatorHIDDirection)direction x:(double)x y:(double)y 59 | { 60 | return [self touchScreenSize:screenSize screenScale:1.0 direction:direction x:x y:y]; 61 | } 62 | 63 | - (NSData *)touchScreenSize:(CGSize)screenSize screenScale:(float)screenScale direction:(FBSimulatorHIDDirection)direction x:(double)x y:(double)y 64 | { 65 | // Convert Screen Offset to Ratio for Indigo. 66 | CGPoint point = [self.class screenRatioFromPoint:CGPointMake(x, y) screenSize:screenSize screenScale:screenScale]; 67 | size_t messageSize; 68 | IndigoMessage *message = [self touchMessageWithPoint:point direction:direction messageSizeOut:&messageSize]; 69 | return [NSData dataWithBytesNoCopy:message length:messageSize freeWhenDone:YES]; 70 | } 71 | 72 | #pragma mark Event Generation 73 | 74 | - (IndigoMessage *)keyboardMessageWithDirection:(FBSimulatorHIDDirection)direction keyCode:(unsigned int)keycode messageSizeOut:(size_t *)messageSizeOut 75 | { 76 | NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); 77 | return nil; 78 | } 79 | 80 | - (IndigoMessage *)buttonMessageWithDirection:(FBSimulatorHIDDirection)direction button:(FBSimulatorHIDButton)button messageSizeOut:(size_t *)messageSizeOut 81 | { 82 | NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); 83 | return nil; 84 | } 85 | 86 | - (IndigoMessage *)touchMessageWithPoint:(CGPoint)point direction:(FBSimulatorHIDDirection)direction messageSizeOut:(size_t *)messageSizeOut 87 | { 88 | NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); 89 | return nil; 90 | } 91 | 92 | + (unsigned int)eventSourceForButton:(FBSimulatorHIDButton)button 93 | { 94 | switch (button) { 95 | case FBSimulatorHIDButtonApplePay: 96 | return ButtonEventSourceApplePay; 97 | case FBSimulatorHIDButtonHomeButton: 98 | return ButtonEventSourceHomeButton; 99 | case FBSimulatorHIDButtonLock: 100 | return ButtonEventSourceLock; 101 | case FBSimulatorHIDButtonSideButton: 102 | return ButtonEventSourceSideButton; 103 | case FBSimulatorHIDButtonSiri: 104 | return ButtonEventSourceSiri; 105 | } 106 | NSAssert(NO, @"Button Code %lul is not known", (unsigned long)button); 107 | } 108 | 109 | + (unsigned int)eventTypeForDirection:(FBSimulatorHIDDirection)direction 110 | { 111 | switch (direction) { 112 | case FBSimulatorHIDDirectionDown: 113 | return ButtonEventTypeDown; 114 | case FBSimulatorHIDDirectionUp: 115 | return ButtonEventTypeUp; 116 | } 117 | NSAssert(NO, @"Direction Code %lul is not known", (unsigned long)direction); 118 | } 119 | 120 | + (CGPoint)screenRatioFromPoint:(CGPoint)point screenSize:(CGSize)screenSize screenScale:(float)screenScale 121 | { 122 | return CGPointMake( 123 | (point.x * screenScale) / screenSize.width, 124 | (point.y * screenScale) / screenSize.height 125 | ); 126 | } 127 | 128 | + (IndigoMessage *)touchMessageWithPayload:(IndigoTouch *)payload messageSizeOut:(size_t *)messageSizeOut 129 | { 130 | // Sizes for the message + payload. 131 | // The size should be 320/0x140 132 | size_t messageSize = sizeof(IndigoMessage) + sizeof(IndigoPayload); 133 | if (messageSizeOut) { 134 | *messageSizeOut = messageSize; 135 | } 136 | // The stride should be 0x90 137 | size_t stride = sizeof(IndigoPayload); 138 | 139 | // Create and set the common values 140 | IndigoMessage *message = calloc(0x1, messageSize); 141 | message->innerSize = sizeof(IndigoPayload); 142 | message->eventType = IndigoEventTypeTouch; 143 | message->payload.type = 0x0000000b; 144 | message->payload.timestamp = mach_absolute_time(); 145 | 146 | // Copy in the Digitizer Payload from the caller. 147 | void *destination = &(message->payload.event.button); 148 | void *source = payload; 149 | memcpy(destination, source, sizeof(IndigoTouch)); 150 | 151 | // Duplicate the first IndigoPayload. 152 | // Also need to set the bits at (0x30 + 0x90) to 0x1. 153 | // On 32-Bit Archs this is equivalent this is done with a long to stomp over both fields: 154 | // uintptr_t mem = (uintptr_t) message; 155 | // mem += 0xc0; 156 | // int64_t *val = (int64_t *)mem; 157 | // *val = 0x200000001; 158 | source = &(message->payload); 159 | destination = source; 160 | destination += stride; 161 | IndigoPayload *second = (IndigoPayload *) destination; 162 | memcpy(destination, source, stride); 163 | 164 | // Adjust the second payload slightly. 165 | second->event.touch.index = 0x00000001; 166 | second->event.touch.identity = 0x00000002; 167 | 168 | return message; 169 | } 170 | 171 | @end 172 | 173 | @implementation FBSimulatorIndigoHID_Reimplemented 174 | 175 | #pragma mark Event Generation 176 | 177 | - (IndigoMessage *)keyboardMessageWithDirection:(FBSimulatorHIDDirection)direction keyCode:(unsigned int)keycode messageSizeOut:(size_t *)messageSizeOut 178 | { 179 | IndigoButton payload; 180 | payload.eventSource = ButtonEventSourceKeyboard; 181 | payload.eventType = [FBSimulatorIndigoHID_Reimplemented eventTypeForDirection:direction]; 182 | payload.eventTarget = ButtonEventTargetKeyboard; 183 | payload.keyCode = keycode; 184 | payload.field5 = 0x000000cc; 185 | 186 | // Then Up/Down. 187 | switch (direction) { 188 | case FBSimulatorHIDDirectionDown: 189 | payload.eventType = ButtonEventTypeDown; 190 | break; 191 | case FBSimulatorHIDDirectionUp: 192 | payload.eventType = ButtonEventTypeUp; 193 | break; 194 | } 195 | return [FBSimulatorIndigoHID_Reimplemented buttonMessageWithPayload:&payload messageSizeOut:messageSizeOut]; 196 | } 197 | 198 | - (IndigoMessage *)buttonMessageWithDirection:(FBSimulatorHIDDirection)direction button:(FBSimulatorHIDButton)button messageSizeOut:(size_t *)messageSizeOut 199 | { 200 | IndigoButton payload; 201 | payload.keyCode = 0; 202 | payload.field5 = 0; 203 | payload.eventSource = [FBSimulatorIndigoHID eventSourceForButton:button]; 204 | payload.eventType = [FBSimulatorIndigoHID eventTypeForDirection:direction]; 205 | payload.eventTarget = ButtonEventTargetHardware; 206 | 207 | return [FBSimulatorIndigoHID_Reimplemented buttonMessageWithPayload:&payload messageSizeOut:messageSizeOut]; 208 | } 209 | 210 | - (IndigoMessage *)touchMessageWithPoint:(CGPoint)point direction:(FBSimulatorHIDDirection)direction messageSizeOut:(size_t *)messageSizeOut 211 | { 212 | // Set the Common Values between down-and-up. 213 | IndigoTouch payload; 214 | payload.eventMask = 0x00400002; 215 | payload.index = 0x1; 216 | payload.identity = 0x3; 217 | 218 | // Points are the ratio between the top-left and bottom right. 219 | payload.xRatio = point.x; 220 | payload.yRatio = point.y; 221 | 222 | // Zero some more fields. 223 | payload.zRatio = 0; 224 | payload.tipPressure = 0; 225 | payload.twist = 0; 226 | 227 | // Setting the Values Signifying touch-down. 228 | switch (direction) { 229 | case FBSimulatorHIDDirectionDown: 230 | payload.inRange = 0x1; 231 | payload.touch = 0x1; 232 | break; 233 | case FBSimulatorHIDDirectionUp: 234 | payload.inRange = 0x0; 235 | payload.touch = 0x0; 236 | break; 237 | default: 238 | break; 239 | } 240 | 241 | // Setting some other fields 242 | payload.options = 0x32; 243 | payload.field12 = 0x1; 244 | payload.field13 = 0x2; 245 | payload.field14 = 0; 246 | payload.field15 = 0; 247 | payload.field16 = 0; 248 | payload.field17 = 0; 249 | payload.field18 = 0; 250 | 251 | return [FBSimulatorIndigoHID_Reimplemented touchMessageWithPayload:&payload messageSizeOut:messageSizeOut]; 252 | } 253 | 254 | + (IndigoMessage *)buttonMessageWithPayload:(IndigoButton *)payload messageSizeOut:(size_t *)messageSizeOut 255 | { 256 | // Create the Message. 257 | //The size should be 176/0xb0 258 | size_t messageSize = sizeof(IndigoMessage); 259 | if (messageSizeOut) { 260 | *messageSizeOut = messageSize; 261 | } 262 | IndigoMessage *message = calloc(0x1 , messageSize); 263 | 264 | // Set the down payload of the message. 265 | message->innerSize = sizeof(IndigoPayload); 266 | message->eventType = IndigoEventTypeButton; 267 | message->payload.type = 0x2; 268 | message->payload.timestamp = mach_absolute_time(); 269 | 270 | // Copy the contents of the payload. 271 | void *destination = &message->payload.event.button; 272 | void *source = (void *) payload; 273 | memcpy(destination, source, sizeof(IndigoButton)); 274 | return message; 275 | } 276 | 277 | @end 278 | -------------------------------------------------------------------------------- /entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.AppleNVMeEAN.allow 6 | 7 | com.apple.CommCenter.fine-grained 8 | 9 | spi 10 | 11 | com.apple.QuartzCore.debug 12 | 13 | com.apple.QuartzCore.displayable-context 14 | 15 | com.apple.QuartzCore.global-capture 16 | 17 | com.apple.QuartzCore.occlusion-override 18 | 19 | com.apple.QuartzCore.secure-mode 20 | 21 | com.apple.RootedSimulatorPath 22 | 23 | com.apple.afk.user 24 | 25 | com.apple.aop.hid-device.user-client 26 | 27 | orientation 28 | 29 | send-command 30 | 31 | 32 | 33 | com.apple.appledfr.client 34 | 35 | com.apple.backboard.client 36 | 37 | com.apple.backboardd 38 | 39 | com.apple.backboardd.launchapplications 40 | 41 | com.apple.backboardd.replacesystemapp 42 | 43 | com.apple.bluetooth.internal 44 | 45 | com.apple.bluetooth.nsxpc 46 | 47 | com.apple.bluetooth.system 48 | 49 | com.apple.carkit.layer-metadata 50 | 51 | com.apple.coreduetd.allow 52 | 53 | com.apple.coreduetd.context 54 | 55 | com.apple.developer.kernel.extended-virtual-addressing 56 | 57 | com.apple.developer.kernel.increased-debugging-memory-limit 58 | 59 | com.apple.developer.kernel.increased-memory-limit 60 | 61 | com.apple.diagnosticpipeline.request 62 | 63 | com.apple.frontboard.launchapplications 64 | 65 | com.apple.frontboardservices.display-layout-monitor 66 | 67 | com.apple.gasgauge.user-access-device 68 | 69 | com.apple.hid.manager.user-access-custom-queue-size 70 | 71 | com.apple.hid.manager.user-access-device 72 | 73 | com.apple.hid.manager.user-access-keyboard 74 | 75 | com.apple.hid.manager.user-access-privileged 76 | 77 | com.apple.hid.multitouch.user-access 78 | 79 | com.apple.hid.system.server-access 80 | 81 | com.apple.hid.system.user-access-service 82 | 83 | com.apple.hid.transport.user-access 84 | 85 | com.apple.hidpreferences.privileged 86 | 87 | com.apple.idle-timer-services 88 | 89 | com.apple.internal.display-params-access-privileged 90 | 91 | com.apple.iohideventsystem.server 92 | 93 | com.apple.keystore.fdr-access 94 | 95 | com.apple.keystore.sik.access 96 | 97 | com.apple.locationd.activity 98 | 99 | com.apple.nano.nanoregistry.generalaccess 100 | 101 | com.apple.private.AmbientDisplay.messaging 102 | 103 | com.apple.private.IOSurface.protected-access 104 | 105 | com.apple.private.MobileGestalt.AllowedProtectedKeys 106 | 107 | CoverglassSerialNumber 108 | AmbientLightSensorSerialNumber 109 | BootManifestHash 110 | UniqueChipID 111 | SysCfgDict 112 | WirelessBoardSnum 113 | BatterySerialNumber 114 | RoswellChipID 115 | 116 | com.apple.private.ZhuGeSupport.CopyValue 117 | 118 | com.apple.private.accessories.showallconnections 119 | 120 | com.apple.private.aea.haptics.user-access 121 | 122 | com.apple.private.allow-explicit-graphics-priority 123 | 124 | com.apple.private.amfi.can-execute-cdhash 125 | 126 | com.apple.private.applepearl.allow 127 | 128 | com.apple.private.applesmc.user-access 129 | 130 | com.apple.private.bmk.allow 131 | 132 | com.apple.private.cecd.control 133 | 134 | com.apple.private.cecd.observer 135 | 136 | com.apple.private.colorsync.privileged 137 | 138 | com.apple.private.dfr.brightness-access 139 | 140 | com.apple.private.domain-extension 141 | 142 | com.apple.private.gain-map-access 143 | 144 | com.apple.private.gpuwrangler 145 | 146 | com.apple.private.graphics-restart-no-kill 147 | 148 | com.apple.private.hid.client.admin 149 | 150 | com.apple.private.hid.client.event-dispatch 151 | 152 | com.apple.private.hid.client.event-filter 153 | 154 | com.apple.private.hid.client.event-monitor 155 | 156 | com.apple.private.hid.manager.client 157 | 158 | com.apple.private.iog.displayassertion 159 | 160 | com.apple.private.iokit.displayservice 161 | 162 | com.apple.private.iokit.rootdomain-set-property 163 | 164 | com.apple.private.iomfb.set-block 165 | 166 | com.apple.private.iosurfaceinfo 167 | 168 | com.apple.private.kernel.work-interval 169 | 170 | com.apple.private.mobile_storage.allowedSPI 171 | 172 | CopyDevices 173 | 174 | com.apple.private.ppm.client 175 | 176 | com.apple.private.security.no-container 177 | 178 | com.apple.private.security.no-sandbox 179 | 180 | com.apple.private.security.storage.AppBundles 181 | 182 | com.apple.private.security.storage.AppDataContainers 183 | 184 | com.apple.private.tcc.allow 185 | 186 | kTCCServiceListenEvent 187 | 188 | com.apple.private.tcc.manager 189 | 190 | com.apple.private.tcc.manager.access.read 191 | 192 | kTCCServiceSensorKitMotionHeartRate 193 | kTCCServiceSensorKitWatchOnWristState 194 | kTCCServiceSensorKitWatchHeartRate 195 | 196 | com.apple.private.tcc.manager.check-by-audit-token 197 | 198 | kTCCServiceAccessibility 199 | kTCCServiceListenEvent 200 | kTCCServicePostEvent 201 | kTCCServiceScreenCapture 202 | 203 | com.apple.private.ubiquity-kvstore-access 204 | 205 | com.apple.springboard 206 | com.apple.Accessibility 207 | com.apple.Accessibility.SwitchControl 208 | com.apple.Accessibility.TouchAccommodations 209 | com.apple.AssistiveTouch 210 | com.apple.HearingAids 211 | com.apple.SpeakSelection 212 | com.apple.VoiceOverTouch 213 | com.apple.ZoomTouch 214 | 215 | com.apple.private.vfs.allow-low-space-writes 216 | 217 | com.apple.private.xpc.launchd.app-server 218 | 219 | com.apple.private.xpc.launchd.per-user-lookup 220 | 221 | com.apple.runningboard.assertions.frontboard 222 | 223 | com.apple.runningboard.assertions.fuseboard 224 | 225 | com.apple.runningboard.hereditarygrantoriginator 226 | 227 | com.apple.runningboard.launchprocess 228 | 229 | com.apple.runningboard.primitiveattribute 230 | 231 | com.apple.runningboard.process-state 232 | 233 | com.apple.runningboard.targetidentities 234 | 235 | com.apple.runningboard.terminatemanagedprocesses 236 | 237 | com.apple.runningboard.terminateprocess 238 | 239 | com.apple.security.exception.mach-lookup.global-name 240 | 241 | com.apple.security.exception.sysctl.read-only 242 | 243 | com.apple.security.iokit-user-client-class 244 | 245 | AppleMultitouchDeviceUserClient 246 | AppleMultitouchSPIUserClient 247 | AGXCommandQueue 248 | AGXDevice 249 | AGXDeviceUserClient 250 | AGXSharedUserClient 251 | AppleCredentialManagerUserClient 252 | AppleJPEGDriverUserClient 253 | ApplePPMUserClient 254 | AppleSPUHIDDeviceUserClient 255 | AppleSPUHIDDriverUserClient 256 | IOAccelContext 257 | IOAccelContext2 258 | IOAccelDevice 259 | IOAccelDevice2 260 | IOAccelSharedUserClient 261 | IOAccelSharedUserClient2 262 | IOAccelSubmitter2 263 | IOHIDEventServiceFastPathUserClient 264 | IOHIDEventServiceUserClient 265 | IOHIDLibUserClient 266 | IOMobileFramebufferUserClient 267 | IOReportUserClient 268 | IOSurfaceAcceleratorClient 269 | IOSurfaceRootUserClient 270 | RootDomainUserClient 271 | IOGPUDeviceUserClient 272 | 273 | com.apple.security.system-container 274 | 275 | com.apple.sensorkit.writer.allow 276 | 277 | com.apple.private.SensorKit.PPG 278 | com.apple.private.SensorKit.ECG 279 | com.apple.SensorKit.onWristState 280 | 281 | com.apple.springboard-ui.client 282 | 283 | com.apple.springboard.opensensitiveurl 284 | 285 | com.apple.springboard.statusbarstyleoverrides 286 | 287 | com.apple.springboard.statusbarstyleoverrides.coordinator 288 | 289 | UIStatusBarStyleOverrideDeveloperTools 290 | UIStatusBarStyleOverridePlaygrounds 291 | 292 | com.apple.symptom_diagnostics.report 293 | 294 | com.apple.systemstatus.domains 295 | 296 | media 297 | location 298 | 299 | com.apple.tailspin.dump-output 300 | 301 | get-task-allow 302 | 303 | keychain-access-groups 304 | 305 | com.apple.springboard 306 | com.apple.Accessibility 307 | 308 | platform-application 309 | 310 | 311 | 312 | -------------------------------------------------------------------------------- /SimFramebuffer/SimFramebuffer.m: -------------------------------------------------------------------------------- 1 | // 2 | // SimFramebuffer.m 3 | // SimulatorShimFrameworks 4 | // 5 | // Created by Duy Tran on 4/9/25. 6 | // 7 | 8 | @import CoreGraphics; 9 | @import Foundation; 10 | @import IOSurface; 11 | //#import "IOMobileFramebuffer.h" 12 | 13 | const uint64_t simFramebufferGetStructName = 0xa9017bfda9be4ff4; 14 | 15 | #pragma pack(push,1) 16 | typedef struct { 17 | uint32_t mode_id; // 0x00 = 1 18 | uint32_t width; // 0x04 = 1206 (maybe width in pixels) 19 | uint32_t height; // 0x08 = 2622 (maybe height in pixels) 20 | uint32_t scale; // 0x0c = 3 (scale factor?) 21 | uint32_t unknown0; //refresh; // 0x10 = 1 (Hz? or just flag) 22 | char pixel_format[4]; // 0x14 = "ARGB" 23 | uint32_t unknown1; //color_depth; // 0x18 = 1 (bits/component?) 24 | uint32_t timing; // 0x1c = 6000 (maybe Hz*100? 60.00 Hz) 25 | uint8_t flags[3]; // 0x20 = 01 01 01 (boolean flags) 26 | } SFBDisplayMode; 27 | #pragma pack(pop) 28 | 29 | @interface SFBConnection : NSObject 30 | @property(nonatomic) NSString *name; 31 | @property(nonatomic) NSArray *displays; 32 | @end 33 | @interface SFBDisplay : NSObject 34 | @property(nonatomic) int type; 35 | @property(nonatomic) NSString *displayUID; 36 | @property(nonatomic) NSString *name; 37 | @property(nonatomic) CGSize size; 38 | //@property(nonatomic) IOMobileFramebufferRef fbConn; 39 | @property(nonatomic) IOSurfaceRef surface; 40 | @end 41 | @interface SFBSwapchain : SFBDisplay 42 | // For convenience, we make SFBSwapchain a subclass of SFBDisplay 43 | @end 44 | 45 | @implementation SFBConnection 46 | - (instancetype)initWithName:(NSString *)name { 47 | self = [super init]; 48 | self.name = name; 49 | 50 | SFBDisplay *display = [SFBDisplay new]; 51 | self.displays = @[display]; 52 | return self; 53 | } 54 | @end 55 | 56 | @implementation SFBDisplay 57 | - (instancetype)init { 58 | self = [super init]; 59 | self.type = 0; 60 | self.displayUID = @"PurpleMain"; 61 | self.name = @"LCD"; 62 | self.size = CGSizeMake(atof((char*)getenv("SIMULATOR_MAINSCREEN_WIDTH")), atof((char*)getenv("SIMULATOR_MAINSCREEN_HEIGHT"))); 63 | return self; 64 | } 65 | @end 66 | 67 | SFBConnection *SFBConnectionCreate(id x0, NSString *name) { 68 | static SFBConnection *sharedConn = nil; 69 | if(!sharedConn) { 70 | sharedConn = [[SFBConnection alloc] initWithName:name]; 71 | } 72 | return sharedConn; 73 | } 74 | BOOL SFBConnectionConnect(SFBConnection *conn, NSError **error) { 75 | return YES; 76 | } 77 | NSArray *SFBConnectionCopyDisplays(SFBConnection *conn, NSError **error) { 78 | return conn.displays; 79 | } 80 | void SFBConnectionSetDisplayUpdatedHandler(SFBConnection *conn, dispatch_queue_t queue, void (^handler)(SFBDisplay *)) { 81 | // no-op 82 | } 83 | 84 | int SFBDisplayGetType(SFBDisplay *display) { 85 | return display.type; 86 | } 87 | NSString *SFBDisplayGetDisplayUID(SFBDisplay *display) { 88 | return display.displayUID; 89 | } 90 | NSString *SFBDisplayGetName(SFBDisplay *display) { 91 | return display.name; 92 | } 93 | void SFBDisplayGetCurrentMode(SFBDisplay *display, SFBDisplayMode *mode) { 94 | assert(display); 95 | assert(mode); 96 | mode->mode_id = 1; 97 | mode->width = display.size.width; 98 | mode->height = display.size.height; 99 | mode->scale = 3; 100 | mode->unknown0 = 1; 101 | memcpy(mode->pixel_format, "ARGB", 4); 102 | mode->unknown1 = 1; 103 | mode->timing = 6000; // 60.00 Hz 104 | mode->flags[0] = 1; 105 | mode->flags[1] = 1; 106 | mode->flags[2] = 1; 107 | } 108 | CGSize SFBDisplayGetDeviceSize(SFBDisplay *mode) { 109 | return mode.size; 110 | } 111 | CGSize SFBDisplayGetCurrentCanvasSize(SFBDisplay *mode) { 112 | return mode.size; 113 | } 114 | SFBSwapchain *SFBDisplayCreateSwapchain(SFBDisplay *display, CGSize size, int x1_scaleMaybe, int x2, NSError **error) { 115 | return (id)display; 116 | } 117 | 118 | // here we borrow IOSurfaceSetValue 119 | BOOL SFBSwapchainSwapBegin(SFBSwapchain *swapchain, NSUInteger time, NSError **error) { 120 | // int token; 121 | // return IOMobileFramebufferSwapBegin(swapchain.fbConn, &token) == 0; 122 | //IOSurfaceSetValue(@"SwapCmd", @(1)); 123 | return YES; 124 | } 125 | BOOL SFBSwapchainSwapAddSurface(SFBSwapchain *swapchain, IOSurfaceRef surface, CGRect rect1, CGRect rect2, NSUInteger x2, NSUInteger x3, NSError **error) { 126 | // return IOMobileFramebufferSwapSetLayer(swapchain.fbConn, 0, surface, rect1, rect2, 0) == 0; 127 | // do note that both rect1 and rect2 are the same 128 | // for optimization we pack them into a single 48-bit integer 129 | swapchain.surface = surface; 130 | // uint64_t packedFrame = ((uint64_t)rect1.origin.x << 36) | ((uint64_t)rect1.origin.y << 24) | ((uint64_t)rect1.size.width << 12) | ((uint64_t)rect1.size.height & 0xfff); 131 | // IOSurfaceSetValue(surface, (__bridge CFTypeRef _Nonnull)@"SwapCmd", (__bridge CFTypeRef _Nonnull)@(packedFrame)); 132 | return YES; 133 | } 134 | BOOL SFBSwapchainSwapSubmit(SFBSwapchain *swapchain, NSError **error) { 135 | // return IOMobileFramebufferSwapEnd(swapchain.display.fbConn) == 0; 136 | IOSurfaceSetValue(swapchain.surface, (__bridge CFTypeRef _Nonnull)@"SwapCmd", @(0)); 137 | return YES; 138 | } 139 | BOOL SFBSwapchainSwapSetCallback(SFBSwapchain *swapchain, void *context, dispatch_queue_t queue, void (^callback)(NSError *error, SFBSwapchain *swapchain, uint64_t arg1, uint64_t arg2, void *context)) { 140 | // I'm not really sure what this callback is for, so let's just pretend like this function failed 141 | return NO; 142 | } 143 | 144 | // stubs 145 | void SFBConnectionCopyDisplay() { abort(); } 146 | void SFBConnectionCopyDisplayByUID() { abort(); } 147 | void SFBConnectionCreateDisplay() { abort(); } 148 | void SFBConnectionGetID() { abort(); } 149 | void SFBConnectionGetTypeID() { abort(); } 150 | void SFBConnectionRemoveDisplay() { abort(); } 151 | void SFBConnectionSetDisplayConnectedHandler() { abort(); } 152 | void SFBConnectionSetDisplayDisconnectedHandler() { abort(); } 153 | void SFBConnectionUpdateDisplay() { abort(); } 154 | void SFBDisplayCopyExtendedPropertyProtocols() { abort(); } 155 | void SFBDisplayGetCanvasSize() { abort(); } 156 | void SFBDisplayGetCanvasSizeCount() { abort(); } 157 | void SFBDisplayGetConnectionID() { abort(); } 158 | void SFBDisplayGetDotPitch() { abort(); } 159 | void SFBDisplayGetExtendedProperties() { abort(); } 160 | void SFBDisplayGetFlags() { abort(); } 161 | void SFBDisplayGetID() { abort(); } 162 | void SFBDisplayGetMaskPath() { abort(); } 163 | void SFBDisplayGetMaxLayerCount() { abort(); } 164 | void SFBDisplayGetMaxSwapchainCount() { abort(); } 165 | void SFBDisplayGetMode() { abort(); } 166 | void SFBDisplayGetModeCount() { abort(); } 167 | void SFBDisplayGetPowerState() { abort(); } 168 | void SFBDisplayGetPreferredMode() { abort(); } 169 | void SFBDisplayGetSupportedPresentationModes() { abort(); } 170 | void SFBDisplayGetSupportedSurfaceFlags() { abort(); } 171 | void SFBDisplayGetTypeID() { abort(); } 172 | void SFBDisplaySetBacklightState() { abort(); } 173 | void SFBDisplaySetBrightnessFactor() { abort(); } 174 | void SFBDisplaySetCanvasSize() { abort(); } 175 | void SFBDisplaySetCurrentMode() { abort(); } 176 | void SFBDisplaySetCurrentUIOrientation() { abort(); } 177 | void SFBSetCreateByAddingSet() { abort(); } 178 | void SFBSetCreateByIntersectingSet() { abort(); } 179 | void SFBSetCreateBySubtractingSet() { abort(); } 180 | void SFBSetCreateFromArray() { abort(); } 181 | void SFBSetGetEmpty() { abort(); } 182 | void SFBSwapchainAcquireSurfaceFence() { abort(); } 183 | void SFBSwapchainGetColorspace() { abort(); } 184 | void SFBSwapchainGetConnectionID() { abort(); } 185 | void SFBSwapchainGetDisplayID() { abort(); } 186 | void SFBSwapchainGetFramebufferSize() { abort(); } 187 | void SFBSwapchainGetHDRMode() { abort(); } 188 | void SFBSwapchainGetID() { abort(); } 189 | void SFBSwapchainGetMaxSurfacesPerOperation() { abort(); } 190 | void SFBSwapchainGetPixelFormat() { abort(); } 191 | void SFBSwapchainGetPresentationMode() { abort(); } 192 | void SFBSwapchainGetTypeID() { abort(); } 193 | void SFBSwapchainSwapCancel() { abort(); } 194 | 195 | /* 196 | OBJC_CLASS_$__SFBSwapSurfaceFence 197 | OBJC_METACLASS_$__SFBSwapSurfaceFence 198 | _SFBConnectionAssertQueue 199 | _SFBConnectionGetByIDAndRetain 200 | _SFBDisplayAddExtendedProperties 201 | _SFBDisplayAddExtendedPropertiesProtocol 202 | _SFBDisplayAddMaskPath 203 | _SFBDisplayAddMode 204 | _SFBDisplayCreate 205 | _SFBErrorCreate 206 | _SFBErrorCreateFromMach 207 | _SFBErrorCreateFromSimReplyError 208 | _SFBErrorCreateNotConnected 209 | _SFBGetServerPort 210 | _SFBSetFramebufferServerEnabled 211 | _SFBSetServerPort 212 | _SFBSwapchainCreate 213 | _SFBSwapchainGetByIDAndRetain 214 | _SFBSwapchainInvokeCallback 215 | _SimFramebufferCrashBuffer 216 | _SimFramebufferSetCrashMessage 217 | __SFBConnectionAllocated 218 | _displayCreatedOrUpdatedCallback 219 | _displayRemovedCallback 220 | _displaysCopyFromReply 221 | _nsToMachTime 222 | _presentCallback 223 | kSFBDisplayOptionKeyApplyMask 224 | kSFBDisplayOptionKeyMaskPath 225 | kSFBErrorDomain 226 | kSFBSwapchainOptionUseFences 227 | simFramebufferClientGetErrorDescription 228 | simFramebufferMessageAddCheckin 229 | simFramebufferMessageAddCheckinReply 230 | simFramebufferMessageAddData 231 | simFramebufferMessageAddDisplayExtendedProperties 232 | simFramebufferMessageAddDisplayExtendedProtocolName 233 | simFramebufferMessageAddDisplayMaskPath 234 | simFramebufferMessageAddDisplayMode 235 | simFramebufferMessageAddDisplayProperties 236 | simFramebufferMessageAddDisplaySetBacklightState 237 | simFramebufferMessageAddDisplaySetBrightnessFactor 238 | simFramebufferMessageAddDisplaySetCanvasSize 239 | simFramebufferMessageAddDisplaySetCurrentMode 240 | simFramebufferMessageAddDisplaySetCurrentUIOrientation 241 | simFramebufferMessageAddErrorReply 242 | simFramebufferMessageAddSwapchain 243 | simFramebufferMessageAddSwapchainCancel 244 | simFramebufferMessageAddSwapchainPresent 245 | simFramebufferMessageAddSwapchainPresentCallback 246 | simFramebufferMessageCopyDisplayMaskPath 247 | simFramebufferMessageCreate 248 | simFramebufferMessageCreateDescription 249 | simFramebufferMessageDealloc 250 | simFramebufferMessageEnumerate 251 | simFramebufferMessageEnumerateOfType 252 | simFramebufferMessageEnumerateOfTypeWithBlock 253 | simFramebufferMessageEnumerateProtocolNames 254 | simFramebufferMessageEnumerateProtocolNamesWithBlock 255 | simFramebufferMessageEnumerateWithBlock 256 | simFramebufferMessageGetDescriptor 257 | simFramebufferMessageGetMachHeader 258 | simFramebufferMessageGetSessionID 259 | simFramebufferMessageReceive 260 | simFramebufferMessageReceiveWithAuditToken 261 | simFramebufferMessageSendOneShot 262 | simFramebufferMessageSendReply 263 | simFramebufferMessageSendWithReply 264 | simFramebufferMessageSendWithReplyWithBlock 265 | simFramebufferMessageSendWithReplyWithBlockAsync 266 | simFramebufferMessageSize 267 | simFramebufferMessageValidateAllowedType 268 | simFramebufferMessageValidateAllowedTypes 269 | simFramebufferServerPortName 270 | */ 271 | @interface SFBSwapSurfaceFence : NSObject 272 | @end 273 | @implementation SFBSwapSurfaceFence 274 | @end 275 | 276 | 277 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorHID.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "FBSimulatorHID.h" 9 | #import "Indigo.h" 10 | #import "IOMobileFramebuffer.h" 11 | 12 | #import 13 | 14 | #import 15 | #import 16 | 17 | #import 18 | #import 19 | 20 | @interface FBSimulatorHID () 21 | 22 | @property (nonatomic, strong, readonly) FBSimulatorIndigoHID *indigo; 23 | @property (nonatomic, assign, readonly) CGSize mainScreenSize; 24 | @property (nonatomic, assign, readonly) float mainScreenScale; 25 | 26 | @end 27 | 28 | @implementation FBSimulatorHID 29 | 30 | #pragma mark Initializers 31 | 32 | static const char *SimulatorHIDClientClassName = "SimulatorKit.SimDeviceLegacyHIDClient"; 33 | 34 | + (dispatch_queue_t)workQueue 35 | { 36 | return dispatch_queue_create("com.facebook.fbsimulatorcontrol.hid", DISPATCH_QUEUE_SERIAL); 37 | } 38 | 39 | + (FBFuture *)hidForSimulator:(FBSimulator *)simulator 40 | { 41 | return [self reimplementedHidPortForSimulator:simulator]; 42 | } 43 | 44 | + (FBFuture *)reimplementedHidPortForSimulator:(FBSimulator *)simulator 45 | { 46 | // We have to create this before boot, return early if this isn't true. 47 | // if (simulator.state != FBiOSTargetStateShutdown) { 48 | // NSLog(@"Simulator must be shut down to create a HID port is %@", simulator.stateString); 49 | // abort(); 50 | // } 51 | CGSize mainScreenSize; 52 | IOMobileFramebufferRef fbConn; 53 | IOMobileFramebufferGetMainDisplay(&fbConn); 54 | IOMobileFramebufferGetDisplaySize(fbConn, &mainScreenSize); 55 | float scale = 3; //mainScreen.scale; 56 | dispatch_queue_t queue = self.workQueue; 57 | 58 | return [[self 59 | onQueue:queue registrationPortForSimulator:simulator] 60 | onQueue:queue map:^(NSNumber *registrationPortNumber) { 61 | mach_port_t registrationPort = registrationPortNumber.unsignedIntValue; 62 | return [[FBSimulatorHID_Reimplemented alloc] initWithIndigo:FBSimulatorIndigoHID.reimplemented mainScreenSize:mainScreenSize mainScreenScale:scale queue:queue registrationPort:registrationPort]; 63 | }]; 64 | } 65 | 66 | + (FBFuture *)onQueue:(dispatch_queue_t)queue registrationPortForSimulator:(FBSimulator *)simulator 67 | { 68 | return [FBFuture onQueue:queue resolve:^{ 69 | // As with the 'PurpleFBServer', a 'IndigoHIDRegistrationPort' is needed in order for the synthesis of touch events to work appropriately. 70 | // If this is not set you will see the following logger message in the System log upon booting the simulator 71 | // 'backboardd[10667]: BKHID: Unable to open Indigo HID system' 72 | // The dissasembly for backboardd shows that this will happen when the call to 'IndigoHIDSystemSpawnLoopback' fails. 73 | // Simulator.app creates a Mach Port for the 'IndigoHIDRegistrationPort' and therefore succeeds in the above call. 74 | // As with 'PurpleFBServer' this can be registered with 'register-head-services' 75 | // The first step is to create the mach port 76 | NSError *innerError = nil; 77 | mach_port_t registrationPort = 0; 78 | mach_port_t machTask = mach_task_self(); 79 | kern_return_t result = mach_port_allocate(machTask, MACH_PORT_RIGHT_RECEIVE, ®istrationPort); 80 | if (result != KERN_SUCCESS) { 81 | NSLog(@"Failed to create a Mach Port for IndigoHIDRegistrationPort with code %d", result); 82 | abort(); 83 | } 84 | result = mach_port_insert_right(machTask, registrationPort, registrationPort, MACH_MSG_TYPE_MAKE_SEND); 85 | if (result != KERN_SUCCESS) { 86 | NSLog(@"Failed to 'insert_right' the mach port with code %d", result); 87 | abort(); 88 | } 89 | // Then register it as the 'IndigoHIDRegistrationPort' 90 | // this will be done later 91 | // if (![simulator.device registerPort:registrationPort service:@"IndigoHIDRegistrationPort" error:&innerError]) { 92 | // NSLog(@"Failed to register %d as the IndigoHIDRegistrationPort", registrationPort); 93 | // abort(); 94 | // } 95 | return [FBFuture futureWithResult:@(registrationPort)]; 96 | }]; 97 | } 98 | 99 | - (instancetype)initWithIndigo:(FBSimulatorIndigoHID *)indigo mainScreenSize:(CGSize)mainScreenSize queue:(dispatch_queue_t)queue 100 | { 101 | return [self initWithIndigo:indigo mainScreenSize:mainScreenSize mainScreenScale:1.0 queue:queue]; 102 | } 103 | 104 | - (instancetype)initWithIndigo:(FBSimulatorIndigoHID *)indigo mainScreenSize:(CGSize)mainScreenSize mainScreenScale:(float)mainScreenScale queue:(dispatch_queue_t)queue 105 | { 106 | self = [super init]; 107 | if (!self) { 108 | return nil; 109 | } 110 | 111 | _indigo = indigo; 112 | _mainScreenSize = mainScreenSize; 113 | _queue = queue; 114 | _mainScreenScale = mainScreenScale; 115 | 116 | return self; 117 | } 118 | 119 | #pragma mark Lifecycle 120 | 121 | - (FBFuture *)connect 122 | { 123 | NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); 124 | return nil; 125 | } 126 | 127 | - (void)disconnect 128 | { 129 | NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); 130 | } 131 | 132 | - (void)dealloc 133 | { 134 | [self disconnect]; 135 | } 136 | 137 | #pragma mark NSObject 138 | 139 | - (NSString *)description 140 | { 141 | NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); 142 | return nil; 143 | } 144 | 145 | #pragma mark HID Manipulation 146 | 147 | - (FBFuture *)sendKeyboardEventWithDirection:(FBSimulatorHIDDirection)direction keyCode:(unsigned int)keycode 148 | { 149 | return [self sendIndigoMessageDataOnWorkQueue:[self.indigo keyboardWithDirection:direction keyCode:keycode]]; 150 | } 151 | 152 | - (FBFuture *)sendButtonEventWithDirection:(FBSimulatorHIDDirection)direction button:(FBSimulatorHIDButton)button 153 | { 154 | return [self sendIndigoMessageDataOnWorkQueue:[self.indigo buttonWithDirection:direction button:button]]; 155 | } 156 | 157 | - (FBFuture *)sendTouchWithType:(FBSimulatorHIDDirection)type x:(double)x y:(double)y 158 | { 159 | return [self sendIndigoMessageDataOnWorkQueue:[self.indigo touchScreenSize:self.mainScreenSize screenScale:self.mainScreenScale direction:type x:x y:y]]; 160 | } 161 | 162 | #pragma mark Private 163 | 164 | - (FBFuture *)sendIndigoMessageDataOnWorkQueue:(NSData *)data 165 | { 166 | return [FBFuture onQueue:self.queue resolve:^{ 167 | return [self sendIndigoMessageData:data]; 168 | }]; 169 | } 170 | 171 | - (FBFuture *)sendIndigoMessageData:(NSData *)data 172 | { 173 | NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); 174 | return nil; 175 | } 176 | 177 | @end 178 | 179 | @implementation FBSimulatorHID_Reimplemented 180 | 181 | #pragma mark Initializers 182 | 183 | - (instancetype)initWithIndigo:(FBSimulatorIndigoHID *)indigo mainScreenSize:(CGSize)mainScreenSize queue:(dispatch_queue_t)queue registrationPort:(mach_port_t)registrationPort 184 | { 185 | return [self initWithIndigo:indigo mainScreenSize:mainScreenSize mainScreenScale:1.0 queue:queue registrationPort:registrationPort]; 186 | } 187 | 188 | - (instancetype)initWithIndigo:(FBSimulatorIndigoHID *)indigo mainScreenSize:(CGSize)mainScreenSize mainScreenScale:(float)mainScreenScale queue:(dispatch_queue_t)queue registrationPort:(mach_port_t)registrationPort 189 | { 190 | self = [super initWithIndigo:indigo mainScreenSize:mainScreenSize mainScreenScale:mainScreenScale queue:queue]; 191 | if (!self) { 192 | return nil; 193 | } 194 | 195 | _registrationPort = registrationPort; 196 | _replyPort = 0; 197 | 198 | return self; 199 | } 200 | 201 | #pragma mark NSObject 202 | 203 | - (NSString *)description 204 | { 205 | if (self.registrationPort == 0) { 206 | return @"Indigo HID Port: Unregistered"; 207 | } 208 | if (self.replyPort == 0) { 209 | return [NSString stringWithFormat:@"Indigo HID Port: Registered %d but no reply port", self.registrationPort]; 210 | } 211 | return [NSString stringWithFormat:@"Indigo HID Port: Registration Port %u, reply port %d", self.registrationPort, self.replyPort]; 212 | } 213 | 214 | #pragma mark Lifecycle 215 | 216 | - (FBFuture *)connect 217 | { 218 | if (self.registrationPort == 0) { 219 | NSLog(@"Cannot connect when there is no registration port"); 220 | abort(); 221 | } 222 | if (self.replyPort != 0) { 223 | NSLog(@"Called connect when already connected, will disconnect and reconnect"); 224 | self.replyPort = 0; 225 | //return FBFuture.empty; 226 | } 227 | 228 | return [FBFuture onQueue:self.queue resolve:^ FBFuture * { 229 | // Attempt to perform the handshake. 230 | mach_msg_size_t size = 0x400; 231 | // FBControlCoreGlobalConfiguration.regularTimeout = 30 232 | mach_msg_timeout_t timeout = ((unsigned int) 1) * 1000; 233 | mach_msg_header_t *handshakeHeader = calloc(1, sizeof(mach_msg_header_t)); 234 | handshakeHeader->msgh_bits = 0; 235 | handshakeHeader->msgh_size = size; 236 | handshakeHeader->msgh_remote_port = 0; 237 | handshakeHeader->msgh_local_port = self.registrationPort; 238 | 239 | kern_return_t result = mach_msg(handshakeHeader, MACH_RCV_LARGE | MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0x0, size, self.registrationPort, timeout, 0x0); 240 | if (result != KERN_SUCCESS) { 241 | free(handshakeHeader); 242 | NSLog(@"Failed to get the Indigo Reply Port %d", result); 243 | return FBFuture.empty; 244 | } else { 245 | NSLog(@"Successfully received the Indigo Reply Port: %d", handshakeHeader->msgh_remote_port); 246 | } 247 | // We have the registration port, so we can now set it. 248 | self.replyPort = handshakeHeader->msgh_remote_port; 249 | free(handshakeHeader); 250 | return FBFuture.empty; 251 | }]; 252 | } 253 | 254 | - (void)disconnect 255 | { 256 | if (self.registrationPort == 0) { 257 | return; 258 | } 259 | mach_port_destroy(mach_task_self(), self.registrationPort); 260 | self.registrationPort = 0; 261 | self.replyPort = 0; 262 | } 263 | 264 | - (void)dealloc 265 | { 266 | [self disconnect]; 267 | } 268 | 269 | #pragma mark Private 270 | 271 | - (FBFuture *)sendIndigoMessageData:(NSData *)data 272 | { 273 | if (self.replyPort == 0) { 274 | NSLog(@"The Reply Port has not been obtained yet. Call -connect: first"); 275 | abort(); 276 | } 277 | 278 | // Extract the message 279 | IndigoMessage *message = (IndigoMessage *) data.bytes; 280 | mach_msg_size_t size = (mach_msg_size_t) data.length; 281 | 282 | // Set the header of the message 283 | message->header.msgh_bits = 0x13; 284 | message->header.msgh_size = size; 285 | message->header.msgh_remote_port = self.replyPort; 286 | message->header.msgh_local_port = 0; 287 | message->header.msgh_voucher_port = 0; 288 | message->header.msgh_id = 0; 289 | 290 | mach_msg_return_t result = mach_msg_send((mach_msg_header_t *) message); 291 | if (result != ERR_SUCCESS) { 292 | NSLog(@"The mach_msg_send failed with error %d", result); 293 | abort(); 294 | } 295 | return FBFuture.empty; 296 | } 297 | 298 | @end 299 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBSimulatorControl/HID/FBSimulatorHIDEvent.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "FBSimulatorHIDEvent.h" 9 | 10 | #import "Indigo.h" 11 | 12 | #import "FBSimulatorHID.h" 13 | 14 | const double DEFAULT_SWIPE_DELTA = 10.0; 15 | 16 | @interface FBSimulatorHIDEvent () 17 | 18 | + (FBSimulatorHIDDirection)directionFromDirectionString:(NSString *)DirectionString; 19 | + (NSString *)directionStringFromDirection:(FBSimulatorHIDDirection)Direction; 20 | 21 | @end 22 | 23 | @interface FBSimulatorHIDEvent_Composite : FBSimulatorHIDEvent 24 | 25 | @property (nonatomic, copy, readonly) NSArray *events; 26 | 27 | @end 28 | 29 | @implementation FBSimulatorHIDEvent_Composite 30 | 31 | static NSString *const KeyEvents = @"events"; 32 | 33 | - (instancetype)initWithEvents:(NSArray *)events 34 | { 35 | self = [super init]; 36 | if (!self) { 37 | return nil; 38 | } 39 | 40 | _events = events; 41 | return self; 42 | } 43 | 44 | - (FBFuture *)performOnHID:(FBSimulatorHID *)hid 45 | { 46 | return [self performEvents:self.events onHid:hid]; 47 | } 48 | 49 | - (FBFuture *)performEvents:(NSArray *)events onHid:(FBSimulatorHID *)hid 50 | { 51 | if (events.count == 0) { 52 | return FBFuture.empty; 53 | } 54 | FBSimulatorHIDEvent *event = events.firstObject; 55 | NSArray *next = events.count == 1 ? @[] : [events subarrayWithRange:NSMakeRange(1, events.count - 1)]; 56 | return [[event 57 | performOnHID:hid] 58 | onQueue:dispatch_get_main_queue() fmap:^(id _){ 59 | return [self performEvents:next onHid:hid]; 60 | }]; 61 | } 62 | 63 | - (NSString *)description 64 | { 65 | // [FBCollectionInformation oneLineDescriptionFromArray:self.events] 66 | return [NSString stringWithFormat:@"Composite %@", self.events]; 67 | } 68 | 69 | - (BOOL)isEqual:(FBSimulatorHIDEvent_Composite *)event 70 | { 71 | if (![event isKindOfClass:self.class]) { 72 | return NO; 73 | } 74 | return [self.events isEqualToArray:event.events]; 75 | } 76 | 77 | - (NSUInteger)hash 78 | { 79 | return self.events.hash; 80 | } 81 | 82 | @end 83 | 84 | @interface FBSimulatorHIDEvent_Touch : FBSimulatorHIDEvent 85 | 86 | @property (nonatomic, assign, readonly) FBSimulatorHIDDirection direction; 87 | @property (nonatomic, assign, readonly) double x; 88 | @property (nonatomic, assign, readonly) double y; 89 | 90 | @end 91 | 92 | @implementation FBSimulatorHIDEvent_Touch 93 | 94 | static NSString *const KeyX = @"x"; 95 | static NSString *const KeyY = @"y"; 96 | 97 | - (instancetype)initWithDirection:(FBSimulatorHIDDirection)direction x:(double)x y:(double)y 98 | { 99 | self = [super init]; 100 | if (!self) { 101 | return nil; 102 | } 103 | _direction = direction; 104 | _x = x; 105 | _y = y; 106 | return self; 107 | } 108 | 109 | - (FBFuture *)performOnHID:(FBSimulatorHID *)hid 110 | { 111 | return [hid sendTouchWithType:self.direction x:self.x y:self.y]; 112 | } 113 | 114 | - (NSString *)description 115 | { 116 | return [NSString stringWithFormat: 117 | @"Touch %@ at (%lu,%lu)", 118 | [FBSimulatorHIDEvent directionStringFromDirection:self.direction], 119 | (unsigned long)self.x, 120 | (unsigned long)self.y 121 | ]; 122 | } 123 | 124 | - (BOOL)isEqual:(FBSimulatorHIDEvent_Touch *)event 125 | { 126 | if (![event isKindOfClass:self.class]) { 127 | return NO; 128 | } 129 | return self.direction == event.direction && self.x == event.x && self.y == event.y; 130 | } 131 | 132 | - (NSUInteger)hash 133 | { 134 | return (NSUInteger) self.direction | ((NSUInteger) self.x ^ (NSUInteger) self.y); 135 | } 136 | 137 | @end 138 | 139 | static NSString *const KeyButton = @"button"; 140 | static NSString *const ButtonApplePay = @"apple_pay"; 141 | static NSString *const ButtonHomeButton = @"home"; 142 | static NSString *const ButtonLock = @"lock"; 143 | static NSString *const ButtonSideButton = @"side"; 144 | static NSString *const ButtonSiri = @"siri"; 145 | 146 | @interface FBSimulatorHIDEvent_Button : FBSimulatorHIDEvent 147 | 148 | @property (nonatomic, assign, readonly) FBSimulatorHIDDirection type; 149 | @property (nonatomic, assign, readonly) FBSimulatorHIDButton button; 150 | 151 | @end 152 | 153 | @implementation FBSimulatorHIDEvent_Button 154 | 155 | - (instancetype)initWithDirection:(FBSimulatorHIDDirection)type button:(FBSimulatorHIDButton)button 156 | { 157 | self = [super init]; 158 | if (!self) { 159 | return nil; 160 | } 161 | 162 | _type = type; 163 | _button = button; 164 | return self; 165 | } 166 | 167 | - (FBFuture *)performOnHID:(FBSimulatorHID *)hid 168 | { 169 | return [hid sendButtonEventWithDirection:self.type button:self.button]; 170 | } 171 | 172 | - (NSString *)description 173 | { 174 | return [NSString stringWithFormat: 175 | @"Button %@ %@", 176 | [FBSimulatorHIDEvent_Button buttonStringFromButton:self.button], 177 | [FBSimulatorHIDEvent directionStringFromDirection:self.type] 178 | ]; 179 | } 180 | 181 | - (BOOL)isEqual:(FBSimulatorHIDEvent_Button *)event 182 | { 183 | if (![event isKindOfClass:self.class]) { 184 | return NO; 185 | } 186 | return self.type == event.type && self.button == event.button; 187 | } 188 | 189 | - (NSUInteger)hash 190 | { 191 | return (NSUInteger) self.type ^ (NSUInteger) self.button; 192 | } 193 | 194 | + (NSString *)buttonStringFromButton:(FBSimulatorHIDButton)button 195 | { 196 | switch (button) { 197 | case FBSimulatorHIDButtonApplePay: 198 | return ButtonApplePay; 199 | case FBSimulatorHIDButtonHomeButton: 200 | return ButtonHomeButton; 201 | case FBSimulatorHIDButtonLock: 202 | return ButtonLock; 203 | case FBSimulatorHIDButtonSideButton: 204 | return ButtonSideButton; 205 | case FBSimulatorHIDButtonSiri: 206 | return ButtonSiri; 207 | default: 208 | return nil; 209 | } 210 | } 211 | 212 | + (FBSimulatorHIDButton)buttonFromButtonString:(NSString *)buttonString 213 | { 214 | if ([buttonString isEqualToString:ButtonApplePay]) { 215 | return FBSimulatorHIDButtonApplePay; 216 | } 217 | if ([buttonString isEqualToString:ButtonHomeButton]) { 218 | return FBSimulatorHIDButtonHomeButton; 219 | } 220 | if ([buttonString isEqualToString:ButtonSideButton]) { 221 | return FBSimulatorHIDButtonSideButton; 222 | } 223 | if ([buttonString isEqualToString:ButtonSiri]) { 224 | return FBSimulatorHIDButtonSiri; 225 | } 226 | if ([buttonString isEqualToString:ButtonLock]) { 227 | return FBSimulatorHIDButtonLock; 228 | } 229 | return 0; 230 | } 231 | 232 | @end 233 | 234 | static NSString *const KeyKeycode = @"keycode"; 235 | 236 | @interface FBSimulatorHIDEvent_Keyboard : FBSimulatorHIDEvent 237 | 238 | @property (nonatomic, assign, readonly) FBSimulatorHIDDirection direction; 239 | @property (nonatomic, assign, readonly) unsigned int keyCode; 240 | 241 | @end 242 | 243 | @implementation FBSimulatorHIDEvent_Keyboard 244 | 245 | - (instancetype)initWithDirection:(FBSimulatorHIDDirection)direction keyCode:(unsigned int)keyCode 246 | { 247 | self = [super init]; 248 | if (!self) { 249 | return nil; 250 | } 251 | 252 | _direction = direction; 253 | _keyCode = keyCode; 254 | return self; 255 | } 256 | 257 | - (FBFuture *)performOnHID:(FBSimulatorHID *)hid 258 | { 259 | return [hid sendKeyboardEventWithDirection:self.direction keyCode:self.keyCode]; 260 | } 261 | 262 | - (NSString *)description 263 | { 264 | return [NSString stringWithFormat: 265 | @"Keyboard Code=%d %@", 266 | self.keyCode, 267 | [FBSimulatorHIDEvent directionStringFromDirection:self.direction] 268 | ]; 269 | } 270 | 271 | - (BOOL)isEqual:(FBSimulatorHIDEvent_Keyboard *)event 272 | { 273 | if (![event isKindOfClass:self.class]) { 274 | return NO; 275 | } 276 | return self.direction == event.direction && self.keyCode == event.keyCode; 277 | } 278 | 279 | - (NSUInteger)hash 280 | { 281 | return (NSUInteger) self.direction ^ (NSUInteger) self.keyCode; 282 | } 283 | 284 | @end 285 | 286 | @interface FBSimulatorHIDEvent_Delay : FBSimulatorHIDEvent 287 | 288 | @property (nonatomic, assign, readonly) double duration; 289 | 290 | @end 291 | 292 | @implementation FBSimulatorHIDEvent_Delay 293 | 294 | static NSString *const KeyDuration = @"duration"; 295 | 296 | - (instancetype)initWithDuration:(double)duration 297 | { 298 | self = [super init]; 299 | if (!self) { 300 | return nil; 301 | } 302 | _duration = duration; 303 | return self; 304 | } 305 | 306 | - (FBFuture *)performOnHID:(FBSimulatorHID *)hid 307 | { 308 | return [FBFuture futureWithDelay:self.duration future:FBFuture.empty]; 309 | } 310 | 311 | - (NSString *)description 312 | { 313 | return [NSString stringWithFormat:@"Delay for %f", self.duration]; 314 | } 315 | 316 | - (BOOL)isEqual:(FBSimulatorHIDEvent_Delay *)event 317 | { 318 | if (![event isKindOfClass:self.class]) { 319 | return NO; 320 | } 321 | return self.duration == event.duration; 322 | } 323 | 324 | - (NSUInteger)hash 325 | { 326 | return (NSUInteger) self.duration; 327 | } 328 | 329 | @end 330 | 331 | @implementation FBSimulatorHIDEvent 332 | 333 | #pragma mark Initializers 334 | 335 | + (instancetype)eventWithEvents:(NSArray *)events 336 | { 337 | return [[FBSimulatorHIDEvent_Composite alloc] initWithEvents:events]; 338 | } 339 | 340 | + (instancetype)touchDownAtX:(double)x y:(double)y 341 | { 342 | return [[FBSimulatorHIDEvent_Touch alloc] initWithDirection:FBSimulatorHIDDirectionDown x:x y:y]; 343 | } 344 | 345 | + (instancetype)touchUpAtX:(double)x y:(double)y 346 | { 347 | return [[FBSimulatorHIDEvent_Touch alloc] initWithDirection:FBSimulatorHIDDirectionUp x:x y:y]; 348 | } 349 | 350 | + (instancetype)buttonDown:(FBSimulatorHIDButton)button 351 | { 352 | return [[FBSimulatorHIDEvent_Button alloc] initWithDirection:FBSimulatorHIDDirectionDown button:button]; 353 | } 354 | 355 | + (instancetype)buttonUp:(FBSimulatorHIDButton)button 356 | { 357 | return [[FBSimulatorHIDEvent_Button alloc] initWithDirection:FBSimulatorHIDDirectionUp button:button]; 358 | } 359 | 360 | + (instancetype)keyDown:(unsigned int)keyCode 361 | { 362 | return [[FBSimulatorHIDEvent_Keyboard alloc] initWithDirection:FBSimulatorHIDDirectionDown keyCode:keyCode]; 363 | } 364 | 365 | + (instancetype)keyUp:(unsigned int)keyCode 366 | { 367 | return [[FBSimulatorHIDEvent_Keyboard alloc] initWithDirection:FBSimulatorHIDDirectionUp keyCode:keyCode]; 368 | } 369 | 370 | + (instancetype)tapAtX:(double)x y:(double)y 371 | { 372 | return [self eventWithEvents:@[ 373 | [self touchDownAtX:x y:y], 374 | [self touchUpAtX:x y:y], 375 | ]]; 376 | } 377 | 378 | + (instancetype)shortButtonPress:(FBSimulatorHIDButton)button 379 | { 380 | return [self eventWithEvents:@[ 381 | [self buttonDown:button], 382 | [self buttonUp:button], 383 | ]]; 384 | } 385 | 386 | + (instancetype)shortKeyPress:(unsigned int)keyCode 387 | { 388 | return [self eventWithEvents:@[ 389 | [self keyDown:keyCode], 390 | [self keyUp:keyCode], 391 | ]]; 392 | } 393 | 394 | + (instancetype)shortKeyPressSequence:(NSArray *)sequence 395 | { 396 | NSMutableArray *events = [NSMutableArray array]; 397 | 398 | for (id keyCode in sequence) { 399 | [events addObject:[self keyDown:[keyCode unsignedIntValue]]]; 400 | [events addObject:[self keyUp:[keyCode unsignedIntValue]]]; 401 | } 402 | 403 | return [self eventWithEvents:events]; 404 | } 405 | 406 | + (instancetype)swipe:(double)xStart yStart:(double)yStart xEnd:(double)xEnd yEnd:(double)yEnd delta:(double)delta duration:(double)duration 407 | { 408 | NSMutableArray *events = [NSMutableArray array]; 409 | double distance = sqrt(pow(yEnd - yStart, 2) + pow(xEnd - xStart, 2)); 410 | if (delta <= 0.0) { 411 | delta = DEFAULT_SWIPE_DELTA; 412 | } 413 | int steps = (int)(distance / delta); 414 | 415 | double dx = (xEnd - xStart) / steps; 416 | double dy = (yEnd - yStart) / steps; 417 | 418 | double stepDelay = duration/(steps + 1); 419 | 420 | for (int i = 0 ; i <= steps ; ++i) { 421 | [events addObject:[self touchDownAtX:(xStart + dx * i) y:(yStart + dy * i)]]; 422 | [events addObject:[self delay:stepDelay]]; 423 | } 424 | 425 | [events addObject:[self touchUpAtX:xEnd y:yEnd]]; 426 | 427 | return [self eventWithEvents:events]; 428 | } 429 | 430 | + (instancetype)delay:(double)duration 431 | { 432 | return [[FBSimulatorHIDEvent_Delay alloc] initWithDuration:duration]; 433 | } 434 | 435 | #pragma mark NSCopying 436 | 437 | - (id)copyWithZone:(NSZone *)zone 438 | { 439 | // All values are immutable. 440 | return self; 441 | } 442 | 443 | #pragma mark Public Methods 444 | 445 | - (FBFuture *)performOnHID:(FBSimulatorHID *)hid 446 | { 447 | NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); 448 | return nil; 449 | } 450 | 451 | #pragma mark Private Methods 452 | 453 | static NSString *const DirectionDown = @"down"; 454 | static NSString *const DirectionUp = @"up"; 455 | 456 | + (FBSimulatorHIDDirection)directionFromDirectionString:(NSString *)directionString 457 | { 458 | if ([directionString isEqualToString:DirectionDown]) { 459 | return FBSimulatorHIDDirectionDown; 460 | } 461 | if ([directionString isEqualToString:DirectionUp]) { 462 | return FBSimulatorHIDDirectionUp; 463 | } 464 | return 0; 465 | } 466 | 467 | + (NSString *)directionStringFromDirection:(FBSimulatorHIDDirection)direction 468 | { 469 | switch (direction) { 470 | case FBSimulatorHIDDirectionDown: 471 | return DirectionDown; 472 | case FBSimulatorHIDDirectionUp: 473 | return DirectionUp; 474 | default: 475 | return nil; 476 | } 477 | } 478 | 479 | @end 480 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBControlCore/Async/FBFuture.h: -------------------------------------------------------------------------------- 1 | // 2 | // FBFuture.h 3 | // 4 | // 5 | // Created by Duy Tran on 3/10/25. 6 | // 7 | 8 | 9 | /* 10 | * Copyright (c) Meta Platforms, Inc. and affiliates. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE file in the root directory of this source tree. 14 | */ 15 | 16 | #import 17 | 18 | NS_ASSUME_NONNULL_BEGIN 19 | 20 | @class FBFutureContext; 21 | 22 | @protocol FBControlCoreLogger; 23 | 24 | /** 25 | A State for the Future. 26 | */ 27 | typedef NS_ENUM(NSUInteger, FBFutureState) { 28 | FBFutureStateRunning = 1, /* The Future hasn't resolved yet */ 29 | FBFutureStateDone = 2, /* The Future has resolved successfully */ 30 | FBFutureStateFailed = 3, /* The Future has resolved in error */ 31 | FBFutureStateCancelled = 4, /* The Future has been cancelled */ 32 | }; 33 | 34 | /** 35 | Loop status for onQueue:resolveOrFailWhen: 36 | */ 37 | typedef NS_ENUM(NSUInteger, FBFutureLoopState) { 38 | FBFutureLoopContinue = 1, /* FBFuture resolveOrFailWhen will continue */ 39 | FBFutureLoopFinished = 2, /* FBFuture resolveOrFailWhen has finished */ 40 | FBFutureLoopFailed = 3, /* FBFuture resolveOrFailWhen has failed */ 41 | }; 42 | 43 | extern dispatch_time_t FBCreateDispatchTimeFromDuration(NSTimeInterval inDuration); 44 | 45 | /** 46 | A Future Operation 47 | */ 48 | @interface FBFuture : NSObject 49 | 50 | #pragma mark Initializers 51 | 52 | /** 53 | Constructs a Future that wraps a result 54 | 55 | @param result the result. 56 | @return a new Future. 57 | */ 58 | + (FBFuture *)futureWithResult:(T)result; 59 | 60 | /** 61 | Constructs a Future that wraps an error. 62 | 63 | @param error the error to wrap. 64 | @return a new Future. 65 | */ 66 | + (FBFuture *)futureWithError:(NSError *)error; 67 | 68 | /** 69 | Construct a Future that resolves with a delay. 70 | 71 | @param delay the delay to resolve the future in. 72 | @param future the future to resolve 73 | @return the Future wrapped in a delay. 74 | */ 75 | + (FBFuture *)futureWithDelay:(NSTimeInterval)delay future:(FBFuture *)future; 76 | 77 | /** 78 | Constructs a Future that resolves successfully when the resolveWhen block returns YES. 79 | onQueue:resolveWhen: is a shortcut to onQueue:resolveOrFailWhen: 80 | 81 | @param queue to resolve on. 82 | @param resolveWhen a block determining when the future should resolve. 83 | @return a new Future that resolves when the resolution block returns YES. 84 | */ 85 | + (FBFuture *)onQueue:(dispatch_queue_t)queue resolveWhen:(BOOL (^)(void))resolveWhen; 86 | 87 | /** 88 | Constructs a Future that resolves when the resolveOrFailWhen block returns FBFutureLoopState. 89 | resolveOrFailWhen block will be executed every 100ms to determine when the future should be resolved: 90 | 91 | * If resolveOrFailWhen block returns FBFutureLoopContinue, the future will continue in Running state 92 | * If resolveOrFailWhen block returns FBFutureLoopFinished and errorOut is not set, the future will resolve successfully 93 | * If resolveOrFailWhen block returns FBFutureLoopFailed and error out is set, the future will resolve on a failure. 94 | 95 | @param queue to resolve on. 96 | @param resolveOrFailWhen a block determining when the future should resolve. Future will resolve into a failure if `errorOut` is not nil. 97 | @return a new Future that resolves when the resolution block returns FBFutureLoopFinished or FBFutureLoopFailed. 98 | */ 99 | + (FBFuture *)onQueue:(dispatch_queue_t)queue resolveOrFailWhen:(FBFutureLoopState (^)(NSError ** errorOut))resolveOrFailWhen; 100 | 101 | /** 102 | Constructs a Future that resolves successfully when the resolveUntil block resolves a Future that resolves a value. 103 | Each resolution will occur one-after-another. 104 | 105 | @param queue to resolve on. 106 | @param resolveUntil a block that returns a future to resolve. 107 | @return a new Future that resolves when the future returned by the block resolves a value. 108 | */ 109 | + (FBFuture *)onQueue:(dispatch_queue_t)queue resolveUntil:(FBFuture *(^)(void))resolveUntil; 110 | 111 | /** 112 | Resolve a future synchronously, by value. 113 | 114 | @param resolve the the block to resolve the future. 115 | @return the receiver, for chaining. 116 | */ 117 | + (instancetype)resolveValue:( T(^)(NSError **) )resolve; 118 | 119 | /** 120 | Resolve a future asynchronously, by value. 121 | 122 | @param queue to resolve on. 123 | @param resolve the the block to resolve the future. 124 | @return the receiver, for chaining. 125 | */ 126 | + (instancetype)onQueue:(dispatch_queue_t)queue resolveValue:( T(^)(NSError **) )resolve; 127 | 128 | /** 129 | Resolve a future asynchronously, by returning a future. 130 | 131 | @param queue to resolve on. 132 | @param resolve the the block to resolve the future. 133 | @return the receiver, for chaining. 134 | */ 135 | + (instancetype)onQueue:(dispatch_queue_t)queue resolve:( FBFuture *(^)(void) )resolve; 136 | 137 | /** 138 | Constructs a future from an array of futures. 139 | The future will resolve when all futures in the array have resolved. 140 | If any future resolves in an error, the first error will be propogated. Any pending futures will not be cancelled. 141 | If any future resolves in cancellation, the cancellation will be propogated. Any pending futures will not be cancelled. 142 | 143 | @param futures the futures to compose. 144 | @return a new future with the resolved results of all the composed futures. 145 | */ 146 | + (FBFuture *> *)futureWithFutures:(NSArray *> *)futures NS_SWIFT_UNAVAILABLE("Use BridgeFuture.values instead"); 147 | 148 | /** 149 | Constructrs a Future from an Array of Futures. 150 | The future which resolves the first will be returned. 151 | All other futures will be cancelled. 152 | 153 | @param futures the futures to compose. 154 | @return a new Future with the first future that resolves. 155 | */ 156 | + (FBFuture *)race:(NSArray *> *)futures NS_SWIFT_NAME(init(race:)); 157 | 158 | /** 159 | A resolved future, with an insignificant value. 160 | This can be used to communicate "success", where an errored future would indicate failure. 161 | 162 | @return a new Future that's resolved with an NSNull value. 163 | */ 164 | + (FBFuture *)empty; 165 | 166 | #pragma mark Cancellation 167 | 168 | /** 169 | Cancels the asynchronous operation. 170 | This will always start the process of cancellation. 171 | Some cancellation is immediate, the returned future may resolve immediatey. 172 | 173 | However, other cancellation operations are asynchronous, where the future will not resolve immediately. 174 | If you wish to wait for the cancellation to have been fully resolved, chain on the future returned. 175 | 176 | @return a Future that resolves when cancellation of all handlers has been processed. 177 | */ 178 | - (FBFuture *)cancel; 179 | 180 | /** 181 | Respond to the cancellation of the receiver. 182 | Since the cancellation handler can itself return a future, asynchronous cancellation is permitted. 183 | This can be called multiple times for the same Future if multiple cleanup operations need to occur. 184 | 185 | Make sure that the future that is returned from this block is itself not the same reference as the receiver. 186 | Otherwise the `cancel` call will itself resolve as 'cancelled'. 187 | 188 | @param queue the queue to notify on. 189 | @param handler the block to invoke if cancelled. 190 | @return the Receiver, for chaining. 191 | */ 192 | - (instancetype)onQueue:(dispatch_queue_t)queue respondToCancellation:(FBFuture *(^)(void))handler; 193 | 194 | #pragma mark Completion Notification 195 | 196 | /** 197 | Notifies of the resolution of the Future. 198 | 199 | @param queue the queue to notify on. 200 | @param handler the block to invoke. 201 | @return the Receiver, for chaining. 202 | */ 203 | - (instancetype)onQueue:(dispatch_queue_t)queue notifyOfCompletion:(void (^)(FBFuture *))handler; 204 | 205 | /** 206 | Notifies of the successful resolution of the Future. 207 | The handler will resolve before the chained Future. 208 | 209 | @param queue the queue to notify on. 210 | @param handler the block to invoke. 211 | @return the Receiver, for chaining. 212 | */ 213 | - (instancetype)onQueue:(dispatch_queue_t)queue doOnResolved:(void (^)(T))handler; 214 | 215 | #pragma mark Deriving new Futures 216 | 217 | /** 218 | Chain Futures based on any non-cancellation resolution of the receiver. 219 | All completion events are called in the chained future block (Done, Error, Cancelled). 220 | 221 | @param queue the queue to chain on. 222 | @param chain the chaining handler, called on all completion events. 223 | @return a chained future 224 | */ 225 | - (FBFuture *)onQueue:(dispatch_queue_t)queue chain:(FBFuture * (^)(FBFuture *future))chain; 226 | 227 | /** 228 | FlatMap a successful resolution of the receiver to a new Future. 229 | 230 | @param queue the queue to chain on. 231 | @param fmap the function to re-map the result to a new future, only called on success. 232 | @return a flatmapped future 233 | */ 234 | - (FBFuture *)onQueue:(dispatch_queue_t)queue fmap:(FBFuture * (^)(T result))fmap; 235 | 236 | /** 237 | Map a future's result to a new value, based on a successful resolution of the receiver. 238 | 239 | @param queue the queue to map on. 240 | @param map the mapping block, only called on success. 241 | @return a mapped future 242 | */ 243 | - (FBFuture *)onQueue:(dispatch_queue_t)queue map:(id (^)(T result))map; 244 | 245 | 246 | /** 247 | Returns a copy of this future that'll resolve on a specific queue 248 | 249 | @param queue the queue to resolve on 250 | @returns a copy of this future that'll resolve on the specified queue 251 | */ 252 | - (FBFuture *)onQueue:(dispatch_queue_t)queue; 253 | 254 | /** 255 | Attempt to handle an error. 256 | 257 | @param queue the queue to handle on. 258 | @param handler the block to invoke. 259 | @return a Future that will attempt to handle the error 260 | */ 261 | - (FBFuture *)onQueue:(dispatch_queue_t)queue handleError:(FBFuture * (^)(NSError *))handler; 262 | 263 | /** 264 | Replaces the value on a successful future. 265 | 266 | @param replacement the replacement 267 | @return a future with the replacement. 268 | */ 269 | - (FBFuture *)mapReplace:(id)replacement; 270 | 271 | /** 272 | Once the receiver has resolved in any state, chains to another Future. 273 | This is un-conditional, if the receiver resolves in error the replacement will still be used. 274 | 275 | @param replacement the replacement 276 | @return a future with the replacement. 277 | */ 278 | - (FBFuture *)chainReplace:(FBFuture *)replacement; 279 | 280 | /** 281 | Shields the future from failure, replacing it with the provided value. 282 | 283 | @param replacement the replacement 284 | @return a future with the replacement. 285 | */ 286 | - (FBFuture *)fallback:(T)replacement; 287 | 288 | /** 289 | Delays delivery of the completion of the receiver. 290 | A chaining convenience over -[FBFuture futureWithDelay:future:] 291 | 292 | @param delay the delay to resolve the future in. 293 | @return a delayed future. 294 | */ 295 | - (FBFuture *)delay:(NSTimeInterval)delay; 296 | 297 | #pragma mark Creating Context 298 | 299 | /** 300 | Creates an 'context object' that allows for the value contained by a future to be torn-down when the context is done. 301 | This is useful for resource cleanup, where closing a resource needs to be managed. 302 | The teardown will always be called, regardless of the terminating condition of any chained future. 303 | The state passed in the teardown callback is the state of the resolved future from any chaining that may happen. 304 | The teardown will only be called if the receiver has resolved, as this is how the context value is resolved. 305 | 306 | @param queue the queue to perform the teardown on. 307 | @param action the teardown action to invoke. This block will be executed after the context object is done. This also includes the state that the resultant future ended in. 308 | @return a 'context object' that manages the tear-down of the receiver's value. This teardown can be asynchronous, and is indicated via the return-value of the contextualTeardown block, 309 | */ 310 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue contextualTeardown:( FBFuture * (^)(T, FBFutureState))action; 311 | 312 | /** 313 | Creates an 'context object' from a block. 314 | 315 | @param queue the queue to perform the teardown on. 316 | @param fmap the 'context object' to add. 317 | @return a 'contex object' that manages the tear-down of the receiver's value. 318 | */ 319 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue pushTeardown:(FBFutureContext *(^)(T))fmap; 320 | 321 | #pragma mark Metadata 322 | 323 | /** 324 | Rename the future. 325 | 326 | @param name the name of the Future. 327 | @return the receiver, for chaining. 328 | */ 329 | - (FBFuture *)named:(NSString *)name; 330 | 331 | /** 332 | Rename the future with a format string. 333 | 334 | @param format the format string for the Future's name. 335 | @return the receiver, for chaining. 336 | */ 337 | - (FBFuture *)nameFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2); 338 | 339 | /** 340 | A helper to log completion of the future. 341 | 342 | @param logger the logger to log to. 343 | @param format a description of the future. 344 | @return the receiver, for chaining 345 | */ 346 | - (FBFuture *)logCompletion:(id)logger withPurpose:(NSString *)format, ... NS_FORMAT_FUNCTION(2,3); 347 | 348 | #pragma mark Properties 349 | 350 | /** 351 | YES if receiver has terminated, NO otherwise. 352 | */ 353 | @property (atomic, assign, readonly) BOOL hasCompleted; 354 | 355 | /** 356 | The Error if one is present. 357 | */ 358 | @property (atomic, copy, nullable, readonly) NSError *error; 359 | 360 | /** 361 | The Result. 362 | */ 363 | @property (atomic, copy, nullable, readonly) T result; 364 | 365 | /** 366 | The State. 367 | */ 368 | @property (atomic, assign, readonly) FBFutureState state; 369 | 370 | /** 371 | The name of the Future. 372 | This can be used to set contextual information about the work that the Future represents. 373 | Any name is incorporated into the description. 374 | */ 375 | @property (atomic, copy, nullable, readonly) NSString *name; 376 | 377 | @end 378 | 379 | /** 380 | A Future that can be modified. 381 | */ 382 | @interface FBMutableFuture : FBFuture 383 | 384 | #pragma mark Initializers 385 | 386 | /** 387 | A Future that can be controlled externally. 388 | The Future is in a 'running' state until it is resolved with the `resolve` methods. 389 | 390 | @return a new Mutable Future. 391 | */ 392 | + (FBMutableFuture *)future; 393 | 394 | /** 395 | A Mutable Future with a Name. 396 | 397 | @param name the name of the Future 398 | @return a new Mutable Future. 399 | */ 400 | + (FBMutableFuture *)futureWithName:(nullable NSString *)name; 401 | 402 | /** 403 | A Mutable Future with a Formatted Name 404 | 405 | @param format the format string for the Future's name. 406 | @return a new Mutable Future. 407 | */ 408 | + (FBMutableFuture *)futureWithNameFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2); 409 | 410 | #pragma mark Mutation 411 | 412 | /** 413 | Make the wrapped future succeeded. 414 | 415 | @param result The result. 416 | @return the receiver, for chaining. 417 | */ 418 | - (instancetype)resolveWithResult:(T)result; 419 | 420 | /** 421 | Make the wrapped future fail with an error. 422 | 423 | @param error The error. 424 | @return the receiver, for chaining. 425 | */ 426 | - (instancetype)resolveWithError:(NSError *)error; 427 | 428 | /** 429 | Resolve the receiver upon the completion of another future. 430 | 431 | @param future the future to resolve from. 432 | @return the receiver, for chaining. 433 | */ 434 | - (instancetype)resolveFromFuture:(FBFuture *)future; 435 | 436 | @end 437 | 438 | /** 439 | Wraps a Future in such a way that teardown work can be deferred. 440 | This is useful when the Future wraps some kind of resource that requires cleanup. 441 | Upon completion of the future that the context wraps, a teardown action associated with the context is then performed. 442 | 443 | From this class: 444 | - A Future can be obtained that will completed before the teardown work does. 445 | - Additional chaining is possible, deferring teardown, or adding to a stack of teardowns. 446 | 447 | The API intentionally mirrors some of the methods in FBFuture, so that it can used in equivalent places. 448 | The nominal types of FBFuture and FBFutureContext so that it hard to confuse chaining on between them. 449 | 450 | Like cancellation on a Future, teardown is also permitted to be asynchronous. This is important where resources are allocated on top of each other. 451 | For example this can be useful to have set-up and tear-down actions performed in the order they are added to the teardown stack: 452 | 1) A socket is created. 453 | 2) A file read operation is made on the socket. 454 | 3) The file read operation is used, and then finishes. 455 | 4) The file read operation is stopped. 456 | 5) The socket is closed. 457 | 458 | In this case it's important that #4 has finished it's teardown work before #5 completes. 459 | This is achieved by a teardown action returning a future that completes when the work of #4 is completely done. 460 | Async teardown is completely optional, if the ordering is not significant, then the action can return an empty future to not defer any teardown work lower in the stack. 461 | */ 462 | @interface FBFutureContext : NSObject 463 | 464 | #pragma mark Initializers 465 | 466 | /** 467 | Constructs a context with no teardown. 468 | 469 | @param future the future to wrap. 470 | @return a FBFutureContext wrapping the Future. 471 | */ 472 | + (FBFutureContext *)futureContextWithFuture:(FBFuture *)future; 473 | 474 | /** 475 | Constructs a context with no teardown, from a result. 476 | 477 | @param result the result to wrap. 478 | @return a FBFutureContext wrapping the Future. 479 | */ 480 | + (FBFutureContext *)futureContextWithResult:(T)result; 481 | 482 | #pragma mark Public Methods 483 | 484 | /** 485 | Return a Future from the context. 486 | The receiver's teardown will occur *after* the Future returned by `pop` resolves. 487 | If you wish to keep the context alive after the `pop` then use `pend` instead. 488 | 489 | @param queue the queue to chain on. 490 | @param pop the function to re-map the result to a new future, only called on success. 491 | @return a Future derived from the fmap. The teardown of the context will occur *after* this future has resolved. 492 | */ 493 | - (FBFuture *)onQueue:(dispatch_queue_t)queue pop:(FBFuture * (^)(T result))pop; 494 | 495 | /** 496 | Continue to keep the context alive, but fmap a new future. 497 | The receiver's teardown will not occur after the `pend`'s Future has resolved. 498 | 499 | @param queue the queue to chain on. 500 | @param fmap the function to re-map the result to a new future, only called on success. 501 | @return a Context derived from the fmap. 502 | */ 503 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue pend:(FBFuture * (^)(T result))fmap; 504 | 505 | /** 506 | Pushes another context. 507 | This can be used to make a stack of contexts that unroll once the produced context pops. 508 | 509 | @param queue the queue to chain on. 510 | @param fmap the block to produce more context. 511 | @return a Context derived from the fmap with the current context stacked below. 512 | */ 513 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue push:(FBFutureContext * (^)(T result))fmap; 514 | 515 | /** 516 | Replaces the current context. 517 | This is equivalent to replacing the top of the context stack with a different context. 518 | @param queue the queue to chain on. 519 | @param replace the block to produce more context. 520 | @return a Context derived from the replace with the current context stacked below. 521 | */ 522 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue replace:(FBFutureContext * (^)(T result))replace; 523 | 524 | /** 525 | Continue to keep the context alive, but handleError: a new future. 526 | The receiver's teardown will not occur after the `handleError`'s Future has resolved. 527 | 528 | @param queue the queue to chain on. 529 | @param handler the function to re-map the error to a new future, only executed if caller resolves to a failure. 530 | @return a Context derived from the handleError. 531 | */ 532 | 533 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue handleError:(nonnull FBFuture * _Nonnull (^)(NSError * _Nonnull))handler; 534 | 535 | /** 536 | Adds a teardown to the context 537 | 538 | @param queue the queue to call the teardown on 539 | @param action the teardown action 540 | @return a context with the teardown applied. 541 | */ 542 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue contextualTeardown:( FBFuture * (^)(T, FBFutureState))action; 543 | 544 | /** 545 | Extracts the wrapped context, so that it can be torn-down at a later time. 546 | This is designed to allow a context manager to be combined with the teardown of other long-running operations. 547 | 548 | @param queue the queue to chain on. 549 | @param enter the block that receives two parameters. The first is the context value, the second is a future that will tear-down the context when it is resolved. 550 | @return a Future that wraps the value returned from fmap. 551 | */ 552 | - (FBFuture *)onQueue:(dispatch_queue_t)queue enter:(id (^)(T result, FBMutableFuture *teardown))enter; 553 | 554 | #pragma mark Properties 555 | 556 | /** 557 | The future keeping the context alive. 558 | The context will not be torn down until this future resolves. 559 | */ 560 | @property (nonatomic, strong, readonly) FBFuture *future; 561 | 562 | @end 563 | 564 | NS_ASSUME_NONNULL_END 565 | -------------------------------------------------------------------------------- /SimBooter/SimIndigoHIDServer/FBControlCore/Async/FBFuture.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "FBFuture.h" 9 | 10 | //#import "FBCollectionOperations.h" 11 | //#import "FBControlCore.h" 12 | 13 | @class FBFutureContext_Teardown; 14 | 15 | // A class that encapsulates a mutable array of FBFutureContext_Teardown for 16 | // a threadsafety. 17 | @interface FBFutureTeardowns : NSObject 18 | - (void)addObject:(FBFutureContext_Teardown *)object; 19 | - (FBFutureContext_Teardown *)pop; 20 | - (void)addObjectsFromArray:(NSArray *)array; 21 | - (NSArray *)asArray; 22 | @end 23 | 24 | @implementation FBFutureTeardowns { 25 | NSMutableArray *_data; 26 | dispatch_queue_t _queue; 27 | } 28 | 29 | - (instancetype)init { 30 | return [self initWithArray:@[]]; 31 | } 32 | 33 | - (instancetype)initWithArray:(NSArray *)teardowns { 34 | self = [super init]; 35 | if (self) { 36 | _data = [teardowns mutableCopy]; 37 | _queue = dispatch_queue_create("com.facebook.fbcontrolcore.FBFutureTeardowns", DISPATCH_QUEUE_SERIAL); 38 | } 39 | return self; 40 | } 41 | 42 | - (void)addObject:(FBFutureContext_Teardown *)object { 43 | dispatch_sync(_queue, ^{ 44 | [self->_data addObject:object]; 45 | }); 46 | } 47 | 48 | - (FBFutureContext_Teardown *)pop { 49 | __block FBFutureContext_Teardown *result; 50 | dispatch_sync(_queue, ^{ 51 | result = [self->_data lastObject]; 52 | [self->_data removeLastObject]; 53 | }); 54 | return result; 55 | 56 | } 57 | 58 | - (void)addObjectsFromArray:(NSArray *)array { 59 | dispatch_sync(_queue, ^{ 60 | [self->_data addObjectsFromArray:array]; 61 | }); 62 | } 63 | 64 | - (NSArray *)asArray { 65 | __block NSArray *result; 66 | dispatch_sync(_queue, ^{ 67 | result = [self->_data copy]; 68 | }); 69 | return result; 70 | } 71 | 72 | @end 73 | 74 | /** 75 | A String Mirror of the State. 76 | */ 77 | typedef NSString *FBFutureStateString NS_STRING_ENUM; 78 | FBFutureStateString const FBFutureStateStringRunning = @"running"; 79 | FBFutureStateString const FBFutureStateStringDone = @"done"; 80 | FBFutureStateString const FBFutureStateStringFailed = @"error"; 81 | FBFutureStateString const FBFutureStateStringCancelled = @"cancelled"; 82 | 83 | static FBFutureStateString FBFutureStateStringFromState(FBFutureState state) 84 | { 85 | switch (state) { 86 | case FBFutureStateRunning: 87 | return FBFutureStateStringRunning; 88 | case FBFutureStateDone: 89 | return FBFutureStateStringDone; 90 | case FBFutureStateFailed: 91 | return FBFutureStateStringFailed; 92 | case FBFutureStateCancelled: 93 | return FBFutureStateStringCancelled; 94 | default: 95 | return @""; 96 | } 97 | } 98 | 99 | dispatch_time_t FBCreateDispatchTimeFromDuration(NSTimeInterval inDuration) 100 | { 101 | return dispatch_time(DISPATCH_TIME_NOW, (int64_t)(inDuration * NSEC_PER_SEC)); 102 | } 103 | 104 | static void final_resolveUntil(FBMutableFuture *final, dispatch_queue_t queue, FBFuture *(^resolveUntil)(void)) { 105 | if (final.hasCompleted) { 106 | return; 107 | } 108 | FBFuture *future = resolveUntil(); 109 | [future onQueue:queue notifyOfCompletion:^(FBFuture *resolved) { 110 | switch (resolved.state) { 111 | case FBFutureStateCancelled: 112 | [final cancel]; 113 | return; 114 | case FBFutureStateDone: 115 | [final resolveWithResult:resolved.result]; 116 | return; 117 | case FBFutureStateFailed: 118 | final_resolveUntil(final, queue, resolveUntil); 119 | return; 120 | default: 121 | return; 122 | } 123 | }]; 124 | } 125 | 126 | @interface FBFuture_Handler : NSObject 127 | 128 | @property (nonatomic, strong, readonly) dispatch_queue_t queue; 129 | @property (nonatomic, strong, readonly) void (^handler)(FBFuture *); 130 | 131 | @end 132 | 133 | @implementation FBFuture_Handler 134 | 135 | - (instancetype)initWithQueue:(dispatch_queue_t)queue handler:(void (^)(FBFuture *))handler 136 | { 137 | self = [super init]; 138 | if (!self) { 139 | return nil; 140 | } 141 | 142 | _queue = queue; 143 | _handler = handler; 144 | 145 | return self; 146 | } 147 | 148 | @end 149 | 150 | @interface FBFuture_Cancellation : NSObject 151 | 152 | @property (nonatomic, strong, readonly) dispatch_queue_t queue; 153 | @property (nonatomic, strong, readonly) FBFuture *(^handler)(void); 154 | 155 | @end 156 | 157 | @implementation FBFuture_Cancellation 158 | 159 | - (instancetype)initWithQueue:(dispatch_queue_t)queue handler:(FBFuture *(^)(void))handler 160 | { 161 | self = [super init]; 162 | if (!self) { 163 | return nil; 164 | } 165 | 166 | _queue = queue; 167 | _handler = handler; 168 | 169 | return self; 170 | } 171 | 172 | @end 173 | 174 | @interface FBFutureContext_Teardown : NSObject 175 | 176 | @property (nonatomic, strong, readonly) FBFuture *future; 177 | @property (nonatomic, strong, readonly) dispatch_queue_t queue; 178 | @property (nonatomic, strong, readonly) FBFuture * (^action)(id, FBFutureState); 179 | 180 | @end 181 | 182 | @implementation FBFutureContext_Teardown 183 | 184 | - (instancetype)initWithFuture:(FBFuture *)future queue:(dispatch_queue_t)queue action:(FBFuture * (^)(id, FBFutureState))action 185 | { 186 | self = [super init]; 187 | if (!self) { 188 | return nil; 189 | } 190 | 191 | _future = future; 192 | _queue = queue; 193 | _action = action; 194 | 195 | return self; 196 | } 197 | 198 | - (FBFuture *)performTeardown:(FBFutureState)endState 199 | { 200 | NSAssert(self.future.state != FBFutureStateRunning, @"Performing teardown on an unresolved future is not-permitted."); 201 | FBFuture * (^action)(id, FBFutureState) = self.action; 202 | FBMutableFuture *teardownCompleted = FBMutableFuture.future; 203 | 204 | // By this point the future will actually be resolved. 205 | // The reason for this notifyOfCompletion, is that we can use it for the queue-bounce to the queue that the action is expected to be called on. 206 | [self.future onQueue:self.queue notifyOfCompletion:^(FBFuture *resolved) { 207 | if (resolved.result) { 208 | FBFuture *resolvedTeardownCompleted = action(resolved.result, endState); 209 | [teardownCompleted resolveFromFuture:resolvedTeardownCompleted]; 210 | } else { 211 | [teardownCompleted resolveWithResult:NSNull.null]; 212 | } 213 | }]; 214 | return teardownCompleted; 215 | } 216 | 217 | @end 218 | 219 | @interface FBFutureContext () 220 | 221 | @property (nonatomic, readonly) FBFutureTeardowns *teardowns; 222 | 223 | @end 224 | 225 | @implementation FBFutureContext 226 | 227 | #pragma mark Initializers 228 | 229 | + (FBFutureContext *)futureContextWithFuture:(FBFuture *)future; 230 | { 231 | return [[self alloc] initWithFuture:future teardowns:[FBFutureTeardowns new]]; 232 | } 233 | 234 | + (FBFutureContext *)futureContextWithResult:(id)result 235 | { 236 | return [self futureContextWithFuture:[FBFuture futureWithResult:result]]; 237 | } 238 | 239 | + (FBFutureContext *)futureContextWithError:(NSError *)error 240 | { 241 | return [self futureContextWithFuture:[FBFuture futureWithError:error]]; 242 | } 243 | 244 | + (FBFutureContext *> *)futureContextWithFutureContexts:(NSArray *)contexts 245 | { 246 | NSMutableArray *futures = NSMutableArray.array; 247 | FBFutureTeardowns *teardowns = [[FBFutureTeardowns alloc] init]; 248 | for (FBFutureContext *context in contexts) { 249 | [futures addObject:context.future]; 250 | [teardowns addObjectsFromArray:[context.teardowns asArray]]; 251 | } 252 | FBFuture *> *future = [FBFuture futureWithFutures:futures]; 253 | return [[FBFutureContext alloc] initWithFuture:future teardowns:teardowns]; 254 | } 255 | 256 | - (instancetype)initWithFuture:(FBFuture *)future teardowns:(FBFutureTeardowns *)teardowns 257 | { 258 | self = [super init]; 259 | if (!self) { 260 | return nil; 261 | } 262 | 263 | _future = future; 264 | _teardowns = teardowns; 265 | 266 | return self; 267 | } 268 | 269 | #pragma mark Public 270 | 271 | - (FBFuture *)onQueue:(dispatch_queue_t)queue pop:(FBFuture * (^)(id))pop 272 | { 273 | return [[self.future 274 | onQueue:queue fmap:pop] 275 | onQueue:queue notifyOfCompletion:^(FBFuture *resolved) { 276 | NSArray *teardowns = [self.teardowns asArray]; 277 | [FBFutureContext popTeardowns:teardowns.reverseObjectEnumerator state:resolved.state]; 278 | }]; 279 | } 280 | 281 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue pend:(FBFuture * (^)(id result))fmap 282 | { 283 | FBFuture *next = [self.future onQueue:queue fmap:fmap]; 284 | return [[FBFutureContext alloc] initWithFuture:next teardowns:self.teardowns]; 285 | } 286 | 287 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue push:(FBFutureContext * (^)(id))fmap 288 | { 289 | dispatch_queue_t nextContextQueue = dispatch_queue_create("com.facebook.fbcontrolcore.next_context", DISPATCH_QUEUE_SERIAL); 290 | __block FBFutureContext *nextContext = nil; 291 | FBFuture *future = [self.future onQueue:queue fmap:^(id result) { 292 | FBFutureContext *resolved = fmap(result); 293 | dispatch_sync(nextContextQueue, ^{ 294 | [nextContext.teardowns addObjectsFromArray:[resolved.teardowns asArray]]; 295 | }); 296 | return resolved.future; 297 | }]; 298 | dispatch_sync(nextContextQueue, ^{ 299 | nextContext = [[FBFutureContext alloc] initWithFuture:future teardowns:self.teardowns]; 300 | }); 301 | return nextContext; 302 | } 303 | 304 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue replace:(FBFutureContext * (^)(id))replace 305 | { 306 | dispatch_queue_t nextContextQueue = dispatch_queue_create("com.facebook.fbcontrolcore.next_context", DISPATCH_QUEUE_SERIAL); 307 | FBFutureContext_Teardown *top = [self.teardowns pop]; 308 | __block FBFutureContext *nextContext = nil; 309 | FBFuture *future = [[self.future 310 | onQueue:queue fmap:^(id result) { 311 | FBFutureContext *resolved = replace(result); 312 | dispatch_sync(nextContextQueue, ^{ 313 | [nextContext.teardowns addObjectsFromArray:[resolved.teardowns asArray]]; 314 | }); 315 | return resolved.future; 316 | }] 317 | onQueue:queue chain:^(FBFuture *resolved) { 318 | return [[top performTeardown:resolved.state] chainReplace:resolved]; 319 | }]; 320 | 321 | dispatch_sync(nextContextQueue, ^{ 322 | nextContext = [[FBFutureContext alloc] initWithFuture:future teardowns:self.teardowns]; 323 | }); 324 | return nextContext; 325 | } 326 | 327 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue handleError:(nonnull FBFuture * _Nonnull (^)(NSError * _Nonnull))handler 328 | { 329 | FBFuture *next = [self.future onQueue:queue handleError:handler]; 330 | return [[FBFutureContext alloc] initWithFuture:next teardowns:self.teardowns]; 331 | } 332 | 333 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue contextualTeardown:( FBFuture * (^)(id, FBFutureState) )action 334 | { 335 | FBFutureContext_Teardown *teardown = [[FBFutureContext_Teardown alloc] initWithFuture:self.future queue:queue action:action]; 336 | [self.teardowns addObject:teardown]; 337 | return self; 338 | } 339 | 340 | - (FBFuture *)onQueue:(dispatch_queue_t)queue enter:(id (^)(id result, FBMutableFuture *teardown))enter 341 | { 342 | FBMutableFuture *started = FBMutableFuture.future; 343 | 344 | [[self 345 | onQueue:queue pop:^(id contextValue){ 346 | FBMutableFuture *completed = FBMutableFuture.future; 347 | id mappedValue = enter(contextValue, completed); 348 | [started resolveWithResult:mappedValue]; 349 | return completed; 350 | }] 351 | onQueue:queue handleError:^(NSError *error) { 352 | [started resolveWithError:error]; 353 | return [FBFuture futureWithError:error]; 354 | }]; 355 | 356 | return started; 357 | } 358 | 359 | #pragma mark Private 360 | 361 | + (FBFuture *)popTeardowns:(NSEnumerator *)teardowns state:(FBFutureState)state 362 | { 363 | FBFutureContext_Teardown *teardown = teardowns.nextObject; 364 | if (!teardown) { 365 | return FBFuture.empty; 366 | } 367 | return [[teardown 368 | performTeardown:state] 369 | onQueue:teardown.queue chain:^(id _) { 370 | return [self popTeardowns:teardowns state:state]; 371 | }]; 372 | } 373 | 374 | @end 375 | 376 | @interface FBFuture () 377 | 378 | @property (atomic, copy, nullable, readwrite) NSString *name; 379 | @property (nonatomic, strong, readonly) NSMutableArray *handlers; 380 | @property (nonatomic, strong, nullable, readwrite) NSMutableArray *cancelResponders; 381 | @property (nonatomic, strong, nullable, readwrite) FBFuture *resolvedCancellation; 382 | 383 | @end 384 | 385 | @implementation FBFuture 386 | 387 | @synthesize error = _error, result = _result, state = _state; 388 | 389 | #pragma mark Initializers 390 | 391 | + (FBFuture *)futureWithResult:(id)result 392 | { 393 | FBMutableFuture *future = FBMutableFuture.future; 394 | return [future resolveWithResult:result]; 395 | } 396 | 397 | + (FBFuture *)futureWithError:(NSError *)error 398 | { 399 | FBMutableFuture *future = FBMutableFuture.future; 400 | return [future resolveWithError:error]; 401 | } 402 | 403 | + (FBFuture *)futureWithDelay:(NSTimeInterval)delay future:(FBFuture *)future 404 | { 405 | FBMutableFuture *delayed = FBMutableFuture.future; 406 | dispatch_after(FBCreateDispatchTimeFromDuration(delay), FBFuture.internalQueue, ^{ 407 | [delayed resolveFromFuture:future]; 408 | }); 409 | return [delayed onQueue:FBFuture.internalQueue respondToCancellation:^{ 410 | [future cancel]; 411 | return FBFuture.empty; 412 | }]; 413 | } 414 | 415 | + (instancetype)resolveValue:( id(^)(NSError **) )resolve 416 | { 417 | NSError *error = nil; 418 | id result = resolve(&error); 419 | if (result) { 420 | return [FBFuture futureWithResult:result]; 421 | } else { 422 | return [FBFuture futureWithError:error]; 423 | } 424 | } 425 | 426 | + (instancetype)onQueue:(dispatch_queue_t)queue resolveValue:(id(^)(NSError **))resolve; 427 | { 428 | FBMutableFuture *future = FBMutableFuture.future; 429 | dispatch_async(queue, ^{ 430 | NSError *error = nil; 431 | id result = resolve(&error); 432 | if (!result) { 433 | NSCAssert(error, @"Error must be set on nil return"); 434 | [future resolveWithError:error]; 435 | } else { 436 | [future resolveWithResult:result]; 437 | } 438 | }); 439 | return future; 440 | } 441 | 442 | + (instancetype)onQueue:(dispatch_queue_t)queue resolve:( FBFuture *(^)(void) )resolve 443 | { 444 | FBMutableFuture *future = FBMutableFuture.future; 445 | dispatch_async(queue, ^{ 446 | FBFuture *resolved = resolve(); 447 | [future resolveFromFuture:resolved]; 448 | }); 449 | return future; 450 | } 451 | 452 | + (FBFuture *)onQueue:(dispatch_queue_t)queue resolveWhen:(BOOL (^)(void))resolveWhen 453 | { 454 | return [self onQueue:queue resolveOrFailWhen:^FBFutureLoopState(NSError **errorOut) { 455 | if (resolveWhen()){ 456 | return FBFutureLoopFinished; 457 | } else { 458 | return FBFutureLoopContinue; 459 | } 460 | }]; 461 | } 462 | 463 | + (FBFuture *)onQueue:(dispatch_queue_t)queue resolveOrFailWhen:(FBFutureLoopState (^)(NSError ** errorOut))resolveOrFailWhen 464 | { 465 | FBMutableFuture *future = FBMutableFuture.future; 466 | 467 | dispatch_async(queue, ^{ 468 | const NSTimeInterval interval = 0.1; 469 | const dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); 470 | 471 | dispatch_source_set_timer(timer, FBCreateDispatchTimeFromDuration(interval), (uint64_t)(interval * NSEC_PER_SEC), (uint64_t)(interval * NSEC_PER_SEC / 10)); 472 | dispatch_source_set_event_handler(timer, ^{ 473 | 474 | 475 | if (future.state != FBFutureStateRunning) { 476 | dispatch_cancel(timer); 477 | } else { 478 | NSError *error = nil; 479 | switch(resolveOrFailWhen(&error)){ 480 | case FBFutureLoopContinue: 481 | //Continue running 482 | break; 483 | case FBFutureLoopFailed: 484 | dispatch_cancel(timer); 485 | NSCAssert(error != nil, @"Expected error to be set when returning FBFutureLoopFailed"); 486 | [future resolveWithError:error]; 487 | break; 488 | case FBFutureLoopFinished: 489 | dispatch_cancel(timer); 490 | NSCAssert(error == nil, @"Error must be set on nil when return FBFutureLoopFinished"); 491 | [future resolveWithResult:NSNull.null]; 492 | break; 493 | } 494 | } 495 | }); 496 | dispatch_resume(timer); 497 | }); 498 | 499 | return future; 500 | } 501 | 502 | + (FBFuture *)onQueue:(dispatch_queue_t)queue resolveUntil:(FBFuture *(^)(void))resolveUntil 503 | { 504 | FBMutableFuture *final = FBMutableFuture.future; 505 | dispatch_async(queue, ^{ 506 | final_resolveUntil(final, queue, resolveUntil); 507 | }); 508 | return final; 509 | } 510 | 511 | - (FBFuture *)onQueue:(dispatch_queue_t)queue 512 | { 513 | FBMutableFuture *future = FBMutableFuture.future; 514 | dispatch_async(queue, ^{ 515 | [future resolveFromFuture:self]; 516 | }); 517 | return future; 518 | } 519 | 520 | - (FBFuture *)onQueue:(dispatch_queue_t)queue timeout:(NSTimeInterval)timeout handler:(FBFuture * (^)(void))handler 521 | { 522 | return [FBFuture 523 | race:@[ 524 | self, 525 | [[FBFuture 526 | futureWithDelay:timeout future:FBFuture.empty] 527 | onQueue:queue fmap:^(id _) { 528 | return handler(); 529 | }], 530 | ] 531 | ]; 532 | } 533 | 534 | + (FBFuture *)futureWithFutures:(NSArray *)futures 535 | { 536 | if (futures.count == 0) { 537 | return [FBFuture futureWithResult:@[]]; 538 | } 539 | 540 | FBMutableFuture *compositeFuture = FBMutableFuture.future; 541 | NSMutableArray *results = [NSMutableArray arrayWithCapacity:futures.count]; 542 | for (NSUInteger index = 0; index < futures.count; index++) { 543 | [results addObject:NSNull.null]; 544 | } 545 | dispatch_queue_t queue = dispatch_queue_create("com.facebook.fbcontrolcore.future.composite", DISPATCH_QUEUE_SERIAL); 546 | __block NSUInteger remaining = futures.count; 547 | 548 | // `futureCompleted` must be called on `queue`. 549 | void (^futureCompleted)(FBFuture *, NSUInteger) = ^(FBFuture *future, NSUInteger index) { 550 | if (compositeFuture.hasCompleted) { 551 | return; 552 | } 553 | 554 | FBFutureState state = future.state; 555 | switch (state) { 556 | case FBFutureStateDone: 557 | results[index] = future.result; 558 | remaining--; 559 | if (remaining == 0) { 560 | [compositeFuture resolveWithResult:[results copy]]; 561 | } 562 | return; 563 | case FBFutureStateFailed: 564 | [compositeFuture resolveWithError:future.error]; 565 | return; 566 | case FBFutureStateCancelled: 567 | [compositeFuture cancel]; 568 | return; 569 | default: 570 | NSCAssert(NO, @"Unexpected state in callback %@", FBFutureStateStringFromState(state)); 571 | return; 572 | } 573 | }; 574 | 575 | for (NSUInteger index = 0; index < futures.count; index++) { 576 | FBFuture *future = futures[index]; 577 | if (future.hasCompleted) { 578 | // The reason that this is done in-line is to avoid work being 579 | // asynchronous when not necessary. For example a future-of-futures where 580 | // the input futures have resolved already should resolve immediately. 581 | // The dispatch_sync ensures that in this case, the composed future is 582 | // resolved before returning from the constructor. 583 | // It's OK to use dispatch_sync here: queue is local; there is no dispatch 584 | // calls within futureCompleted(). 585 | // @lint-ignore FBOBJCDISCOURAGEDFUNCTION 586 | dispatch_sync(queue, ^{ 587 | futureCompleted(future, index); 588 | }); 589 | } else { 590 | [future onQueue:queue notifyOfCompletion:^(FBFuture *innerFuture){ 591 | futureCompleted(innerFuture, index); 592 | }]; 593 | } 594 | } 595 | return compositeFuture; 596 | } 597 | 598 | + (FBFuture *)race:(NSArray *)futures 599 | { 600 | NSParameterAssert(futures.count > 0); 601 | 602 | FBMutableFuture *compositeFuture = FBMutableFuture.future; 603 | dispatch_queue_t queue = dispatch_queue_create("com.facebook.fbcontrolcore.future.race", DISPATCH_QUEUE_SERIAL); 604 | __block NSUInteger remainingCounter = futures.count; 605 | 606 | void (^cancelAllFutures)(void) = ^{ 607 | for (FBFuture *future in futures) { 608 | [future cancel]; 609 | } 610 | }; 611 | 612 | // `futureCompleted` must be called on `queue`. 613 | void (^futureCompleted)(FBFuture *future) = ^(FBFuture *future){ 614 | remainingCounter--; 615 | if (future.result) { 616 | [compositeFuture resolveWithResult:future.result]; 617 | cancelAllFutures(); 618 | return; 619 | } 620 | if (future.error) { 621 | [compositeFuture resolveWithError:future.error]; 622 | cancelAllFutures(); 623 | return; 624 | } 625 | if (remainingCounter == 0) { 626 | [compositeFuture cancel]; 627 | } 628 | }; 629 | 630 | for (FBFuture *future in futures) { 631 | if (future.hasCompleted) { 632 | // The reason that this is done in-line is to avoid work being 633 | // asynchronous when not necessary. For example a future-of-futures where 634 | // the input futures have resolved already should resolve immediately. 635 | // The dispatch_sync ensures that in this case, the composed future is 636 | // resolved before returning from the constructor. 637 | // It's OK to use dispatch_sync here: queue is local; there is no dispatch 638 | // calls within futureCompleted() 639 | // @lint-ignore FBOBJCDISCOURAGEDFUNCTION 640 | dispatch_sync(queue, ^{ 641 | futureCompleted(future); 642 | }); 643 | } else { 644 | [future onQueue:queue notifyOfCompletion:futureCompleted]; 645 | } 646 | } 647 | return compositeFuture; 648 | } 649 | 650 | + (FBFuture *)empty 651 | { 652 | FBMutableFuture *future = [FBMutableFuture futureWithName:@"Empty"]; 653 | return [future resolveWithResult:NSNull.null]; 654 | } 655 | 656 | - (instancetype)init 657 | { 658 | return [self initWithName:nil]; 659 | } 660 | 661 | - (instancetype)initWithName:(NSString *)name 662 | { 663 | self = [super init]; 664 | if (!self) { 665 | return nil; 666 | } 667 | 668 | _state = FBFutureStateRunning; 669 | _handlers = [NSMutableArray array]; 670 | _cancelResponders = [NSMutableArray array]; 671 | 672 | _name = name; 673 | 674 | return self; 675 | } 676 | 677 | #pragma mark NSObject 678 | 679 | - (NSString *)description 680 | { 681 | NSString *state = [NSString stringWithFormat:@"Future %@", FBFutureStateStringFromState(self.state)]; 682 | NSString *name = self.name; 683 | if (name) { 684 | return [NSString stringWithFormat:@"%@ %@", name, state]; 685 | } 686 | return state; 687 | } 688 | 689 | #pragma mark Cancellation 690 | 691 | - (FBFuture *)cancel 692 | { 693 | @synchronized (self) { 694 | if (self.resolvedCancellation) { 695 | return self.resolvedCancellation; 696 | } 697 | if (self.state != FBFutureStateRunning) { 698 | return FBFuture.empty; 699 | } 700 | } 701 | NSArray *cancelResponders = [self resolveAsCancelled]; 702 | @synchronized (self) { 703 | self.resolvedCancellation = [FBFuture resolveCancellationResponders:cancelResponders forOriginalName:self.name]; 704 | return self.resolvedCancellation; 705 | } 706 | } 707 | 708 | - (instancetype)onQueue:(dispatch_queue_t)queue respondToCancellation:(FBFuture *(^)(void))handler 709 | { 710 | NSParameterAssert(queue); 711 | NSParameterAssert(handler); 712 | 713 | @synchronized(self) { 714 | [self.cancelResponders addObject:[[FBFuture_Cancellation alloc] initWithQueue:queue handler:handler]]; 715 | return self; 716 | } 717 | } 718 | 719 | #pragma mark Completion Notification 720 | 721 | - (instancetype)onQueue:(dispatch_queue_t)queue notifyOfCompletion:(void (^)(FBFuture *))handler 722 | { 723 | NSParameterAssert(queue); 724 | NSParameterAssert(handler); 725 | 726 | @synchronized (self) { 727 | if (self.state == FBFutureStateRunning) { 728 | FBFuture_Handler *wrapper = [[FBFuture_Handler alloc] initWithQueue:queue handler:handler]; 729 | [self.handlers addObject:wrapper]; 730 | } else { 731 | dispatch_async(queue, ^{ 732 | handler(self); 733 | }); 734 | } 735 | } 736 | return self; 737 | } 738 | 739 | - (instancetype)onQueue:(dispatch_queue_t)queue doOnResolved:(void (^)(id))handler 740 | { 741 | return [self onQueue:queue map:^(id result) { 742 | handler(result); 743 | return result; 744 | }]; 745 | } 746 | 747 | #pragma mark Deriving new Futures 748 | 749 | - (FBFuture *)onQueue:(dispatch_queue_t)queue chain:(FBFuture *(^)(FBFuture *))chain 750 | { 751 | FBMutableFuture *chained = FBMutableFuture.future; 752 | [self onQueue:queue notifyOfCompletion:^(FBFuture *future) { 753 | FBFuture *next = chain(future); 754 | NSCAssert([next isKindOfClass:FBFuture.class], @"chained value is not a Future, got %@", next); 755 | [next onQueue:queue notifyOfCompletion:^(FBFuture *final) { 756 | FBFutureState state = final.state; 757 | switch (state) { 758 | case FBFutureStateFailed: 759 | [chained resolveWithError:final.error]; 760 | break; 761 | case FBFutureStateDone: 762 | [chained resolveWithResult:final.result]; 763 | break; 764 | case FBFutureStateCancelled: 765 | [chained cancel]; 766 | break; 767 | default: 768 | NSCAssert(NO, @"Invalid State %lu", (unsigned long)state); 769 | } 770 | }]; 771 | }]; 772 | // Chaining: 'self' References 'chained' 773 | // Cancellation: 'chained' references 'self' 774 | // Break the cycle, if weakSelf is nullified, this is fine as completion has been processed already. 775 | __weak typeof(self) weakSelf = self; 776 | return [chained onQueue:FBFuture.internalQueue respondToCancellation:^{ 777 | [weakSelf cancel]; 778 | return FBFuture.empty; 779 | }]; 780 | } 781 | 782 | - (FBFuture *)onQueue:(dispatch_queue_t)queue fmap:(FBFuture * (^)(id result))fmap 783 | { 784 | FBMutableFuture *chained = FBMutableFuture.future; 785 | [self onQueue:queue notifyOfCompletion:^(FBFuture *future) { 786 | if (future.error) { 787 | [chained resolveWithError:future.error]; 788 | return; 789 | } 790 | if (future.state == FBFutureStateCancelled) { 791 | [chained cancel]; 792 | return; 793 | } 794 | FBFuture *fmapped = fmap(future.result); 795 | NSCAssert([fmapped isKindOfClass:FBFuture.class], @"fmap'ped value is not a Future, got %@", fmapped); 796 | [fmapped onQueue:queue notifyOfCompletion:^(FBFuture *next) { 797 | if (next.error) { 798 | [chained resolveWithError:next.error]; 799 | return; 800 | } 801 | [chained resolveWithResult:next.result]; 802 | }]; 803 | }]; 804 | // Chaining: 'self' References 'chained' 805 | // Cancellation: 'chained' references 'self' 806 | // Break the cycle, if weakSelf is nullified, this is fine as completion has been processed already. 807 | __weak typeof(self) weakSelf = self; 808 | return [chained onQueue:FBFuture.internalQueue respondToCancellation:^{ 809 | [weakSelf cancel]; 810 | return FBFuture.empty; 811 | }]; 812 | } 813 | 814 | - (FBFuture *)onQueue:(dispatch_queue_t)queue map:(id (^)(id result))map 815 | { 816 | return [self onQueue:queue fmap:^FBFuture *(id result) { 817 | id next = map(result); 818 | return [FBFuture futureWithResult:next]; 819 | }]; 820 | } 821 | 822 | - (FBFuture *)onQueue:(dispatch_queue_t)queue handleError:(FBFuture * (^)(NSError *))handler 823 | { 824 | return [self onQueue:queue chain:^(FBFuture *future) { 825 | return future.error ? handler(future.error) : future; 826 | }]; 827 | } 828 | 829 | - (FBFuture *)mapReplace:(id)replacement 830 | { 831 | return [self onQueue:FBFuture.internalQueue map:^(id _) { 832 | return replacement; 833 | }]; 834 | } 835 | 836 | - (FBFuture *)chainReplace:(FBFuture *)replacement 837 | { 838 | return [self onQueue:FBFuture.internalQueue chain:^FBFuture *(FBFuture *_) { 839 | return replacement; 840 | }]; 841 | } 842 | 843 | - (FBFuture *)fallback:(id)replacement 844 | { 845 | return [self onQueue:FBFuture.internalQueue handleError:^(NSError *_) { 846 | return [FBFuture futureWithResult:replacement]; 847 | }]; 848 | } 849 | 850 | - (FBFuture *)delay:(NSTimeInterval)delay 851 | { 852 | return [FBFuture futureWithDelay:delay future:self]; 853 | } 854 | 855 | #pragma mark Creating Context 856 | 857 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue contextualTeardown:( FBFuture * (^)(id, FBFutureState))action 858 | { 859 | FBFutureContext_Teardown *teardown = [[FBFutureContext_Teardown alloc] initWithFuture:self queue:queue action:action]; 860 | return [[FBFutureContext alloc] initWithFuture:self teardowns:[[FBFutureTeardowns alloc] initWithArray:@[teardown]]]; 861 | } 862 | 863 | - (FBFutureContext *)onQueue:(dispatch_queue_t)queue pushTeardown:(FBFutureContext *(^)(id))fmap 864 | { 865 | FBFutureTeardowns *teardowns = [FBFutureTeardowns new]; 866 | FBFuture *future = [self onQueue:queue fmap:^(id value) { 867 | FBFutureContext *chained = fmap(value); 868 | for (FBFutureContext_Teardown *teardown in [chained.teardowns asArray]) { 869 | [teardowns addObject:[[FBFutureContext_Teardown alloc] initWithFuture:chained.future queue:teardown.queue action:teardown.action]]; 870 | } 871 | return chained.future; 872 | }]; 873 | return [[FBFutureContext alloc] initWithFuture:future teardowns:teardowns]; 874 | } 875 | 876 | #pragma mark Metadata 877 | 878 | - (FBFuture *)named:(NSString *)name 879 | { 880 | self.name = name; 881 | return self; 882 | } 883 | 884 | - (FBFuture *)nameFormat:(NSString *)format, ... 885 | { 886 | va_list args; 887 | va_start(args, format); 888 | NSString *name = [[NSString alloc] initWithFormat:format arguments:args]; 889 | va_end(args); 890 | 891 | return [self named:name]; 892 | } 893 | 894 | - (FBFuture *)logCompletion:(id)logger withPurpose:(NSString *)format, ... 895 | { 896 | va_list args; 897 | va_start(args, format); 898 | NSString *string = [[NSString alloc] initWithFormat:format arguments:args]; 899 | va_end(args); 900 | 901 | return [self onQueue:FBFuture.internalQueue notifyOfCompletion:^(FBFuture *resolved) { 902 | NSLog(@"Completed %@ with state '%@'", string, resolved); 903 | }]; 904 | } 905 | 906 | #pragma mark - Properties 907 | 908 | - (BOOL)hasCompleted 909 | { 910 | FBFutureState state = self.state; 911 | return state != FBFutureStateRunning; 912 | } 913 | 914 | - (NSError *)error 915 | { 916 | @synchronized (self) { 917 | return self->_error; 918 | } 919 | } 920 | 921 | - (id)result 922 | { 923 | @synchronized (self) { 924 | return self->_result; 925 | } 926 | } 927 | 928 | - (FBFutureState)state 929 | { 930 | @synchronized (self) { 931 | return _state; 932 | } 933 | } 934 | 935 | - (void)setError:(NSError *)error 936 | { 937 | _error = error; 938 | } 939 | 940 | - (void)setState:(FBFutureState)state 941 | { 942 | _state = state; 943 | } 944 | 945 | - (void)setResult:(id)result 946 | { 947 | _result = result; 948 | } 949 | 950 | #pragma mark FBMutableFuture Implementation 951 | 952 | - (instancetype)resolveWithResult:(id)result 953 | { 954 | @synchronized (self) { 955 | if (self.state == FBFutureStateRunning) { 956 | self.result = result; 957 | self.state = FBFutureStateDone; 958 | [self fireAllHandlers]; 959 | self.cancelResponders = nil; 960 | } 961 | } 962 | 963 | return self; 964 | } 965 | 966 | - (instancetype)resolveWithError:(NSError *)error 967 | { 968 | @synchronized (self) { 969 | if (self.state == FBFutureStateRunning) { 970 | self.error = error; 971 | self.state = FBFutureStateFailed; 972 | [self fireAllHandlers]; 973 | self.cancelResponders = nil; 974 | } 975 | } 976 | return self; 977 | } 978 | 979 | - (instancetype)resolveFromFuture:(FBFuture *)future 980 | { 981 | void (^resolve)(FBFuture *future) = ^(FBFuture *resolvedFuture){ 982 | FBFutureState state = resolvedFuture.state; 983 | switch (state) { 984 | case FBFutureStateFailed: 985 | [self resolveWithError:resolvedFuture.error]; 986 | return; 987 | case FBFutureStateDone: 988 | [self resolveWithResult:resolvedFuture.result]; 989 | return; 990 | case FBFutureStateCancelled: 991 | [self cancel]; 992 | return; 993 | default: 994 | NSCAssert(NO, @"Invalid State %lu", (unsigned long)state); 995 | } 996 | }; 997 | if (future.hasCompleted) { 998 | resolve(future); 999 | } else { 1000 | [future onQueue:FBFuture.internalQueue notifyOfCompletion:resolve]; 1001 | } 1002 | return self; 1003 | } 1004 | 1005 | #pragma mark Private 1006 | 1007 | - (NSArray *)resolveAsCancelled 1008 | { 1009 | @synchronized (self) { 1010 | if (self.state == FBFutureStateRunning) { 1011 | self.state = FBFutureStateCancelled; 1012 | [self fireAllHandlers]; 1013 | } 1014 | NSArray *cancelResponders = self.cancelResponders; 1015 | self.cancelResponders = nil; 1016 | return cancelResponders; 1017 | } 1018 | } 1019 | 1020 | - (void)fireAllHandlers 1021 | { 1022 | for (FBFuture_Handler *handler in self.handlers) { 1023 | dispatch_async(handler.queue, ^{ 1024 | handler.handler(self); 1025 | }); 1026 | } 1027 | [self.handlers removeAllObjects]; 1028 | } 1029 | 1030 | + (FBFuture *)resolveCancellationResponders:(NSArray *)cancelResponders forOriginalName:(NSString *)originalName 1031 | { 1032 | NSString *name = [NSString stringWithFormat:@"Cancellation of %@", originalName]; 1033 | if (cancelResponders.count == 0) { 1034 | return [FBFuture.empty named:name]; 1035 | } else if (cancelResponders.count == 1) { 1036 | FBFuture_Cancellation *cancelResponder = cancelResponders[0]; 1037 | return [[FBFuture onQueue:cancelResponder.queue resolve:cancelResponder.handler] named:name]; 1038 | } else { 1039 | NSMutableArray *> *futures = [NSMutableArray array]; 1040 | for (FBFuture_Cancellation *cancelResponder in cancelResponders) { 1041 | [futures addObject:[FBFuture onQueue:cancelResponder.queue resolve:cancelResponder.handler]]; 1042 | } 1043 | return [[[FBFuture futureWithFutures:futures] mapReplace:NSNull.null] named:name]; 1044 | } 1045 | } 1046 | 1047 | + (dispatch_queue_t)internalQueue 1048 | { 1049 | return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); 1050 | } 1051 | 1052 | #pragma mark KVO 1053 | 1054 | + (NSSet *)keyPathsForValuesAffectingHasCompleted 1055 | { 1056 | return [NSSet setWithObjects:NSStringFromSelector(@selector(state)), nil]; 1057 | } 1058 | 1059 | @end 1060 | 1061 | @implementation FBMutableFuture 1062 | 1063 | - (instancetype)resolveWithError:(NSError *)error 1064 | { 1065 | return [super resolveWithError:error]; 1066 | } 1067 | 1068 | - (instancetype)resolveWithResult:(id)result 1069 | { 1070 | return [super resolveWithResult:result]; 1071 | } 1072 | 1073 | - (instancetype)resolveFromFuture:(FBFuture *)future 1074 | { 1075 | return [super resolveFromFuture:future]; 1076 | } 1077 | 1078 | + (FBMutableFuture *)future 1079 | { 1080 | return [self futureWithName:nil]; 1081 | } 1082 | 1083 | + (FBMutableFuture *)futureWithName:(NSString *)name 1084 | { 1085 | return [[FBMutableFuture alloc] initWithName:name]; 1086 | } 1087 | 1088 | + (FBMutableFuture *)futureWithNameFormat:(NSString *)format, ... 1089 | { 1090 | va_list args; 1091 | va_start(args, format); 1092 | NSString *name = [[NSString alloc] initWithFormat:format arguments:args]; 1093 | va_end(args); 1094 | 1095 | return [self futureWithName:name]; 1096 | } 1097 | 1098 | @end 1099 | --------------------------------------------------------------------------------