// main.mm boilerplate #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "AppDelegate.h" #import "PPSSPPUIApplication.h" #import "ViewController.h" #include "Common/MemoryUtil.h" #include "Common/System/NativeApp.h" #include "Common/System/System.h" #include "Common/System/Request.h" #include "Common/StringUtils.h" #include "Common/Profiler/Profiler.h" #include "Core/Config.h" #include "Common/Log.h" #include "UI/DarwinFileSystemServices.h" struct cs_blob_index { uint32_t type; uint32_t offset; }; struct cs_superblob { uint32_t magic; uint32_t length; uint32_t count; struct cs_blob_index index[]; }; struct cs_entitlements { uint32_t magic; uint32_t length; char entitlements[]; }; static int (*csops)(pid_t pid, unsigned int ops, void * useraddr, size_t usersize); static boolean_t (*exc_server)(mach_msg_header_t *, mach_msg_header_t *); static int (*ptrace)(int request, pid_t pid, caddr_t addr, int data); #define CS_OPS_STATUS 0 /* return status */ #define CS_KILL 0x00000200 /* kill process if it becomes invalid */ #define CS_DEBUGGED 0x10000000 /* process is currently or has previously been debugged and allowed to run with invalid pages */ #define PT_TRACE_ME 0 /* child declares it's being traced */ #define PT_SIGEXC 12 /* signals as exceptions for current_proc */ #define ptrace(a, b, c, d) syscall(SYS_ptrace, a, b, c, d) static void *exception_handler(void *argument) { mach_port_t port = *(mach_port_t *)argument; mach_msg_server(exc_server, 2048, port, 0); return NULL; } static NSDictionary *parse_entitlements(const void *entitlements, size_t length) { char *copy = (char *)malloc(length); memcpy(copy, entitlements, length); // strip out psychic paper entitlement hiding if (@available(iOS 13.5, *)) { } else { static const char *needle = ""; char *found = strnstr(copy, needle, length); if (found) { memset(found, ' ', strlen(needle)); } } NSData *data = [NSData dataWithBytes:copy length:length]; free(copy); return [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:nil error:nil]; } static NSDictionary *app_entitlements(void) { // Inspired by codesign.c in Darwin sources for Security.framework // Find our mach-o header Dl_info dl_info; if (dladdr((const void *)app_entitlements, &dl_info) == 0) return nil; if (dl_info.dli_fbase == NULL) return nil; char *base = (char *)dl_info.dli_fbase; struct mach_header_64 *header = (struct mach_header_64 *)dl_info.dli_fbase; if (header->magic != MH_MAGIC_64) return nil; // Simulator executables have fake entitlements in the code signature. The real entitlements can be found in an __entitlements section. size_t entitlements_size; uint8_t *entitlements_data = getsectiondata(header, "__TEXT", "__entitlements", &entitlements_size); if (entitlements_data != NULL) { NSData *data = [NSData dataWithBytesNoCopy:entitlements_data length:entitlements_size freeWhenDone:NO]; return [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:nil error:nil]; } // Find the LC_CODE_SIGNATURE struct load_command *lc = (struct load_command *) (base + sizeof(*header)); struct linkedit_data_command *cs_lc = NULL; for (uint32_t i = 0; i < header->ncmds; i++) { if (lc->cmd == LC_CODE_SIGNATURE) { cs_lc = (struct linkedit_data_command *) lc; break; } lc = (struct load_command *) ((char *) lc + lc->cmdsize); } if (cs_lc == NULL) return nil; // Read the code signature off disk, as it's apparently not loaded into memory NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:NSBundle.mainBundle.executableURL error:nil]; if (fileHandle == nil) return nil; [fileHandle seekToFileOffset:cs_lc->dataoff]; NSData *csData = [fileHandle readDataOfLength:cs_lc->datasize]; [fileHandle closeFile]; const struct cs_superblob *cs = (const struct cs_superblob *)csData.bytes; if (ntohl(cs->magic) != 0xfade0cc0) return nil; // Find the entitlements in the code signature for (uint32_t i = 0; i < ntohl(cs->count); i++) { struct cs_entitlements *ents = (struct cs_entitlements *) ((char *) cs + ntohl(cs->index[i].offset)); if (ntohl(ents->magic) == 0xfade7171) { return parse_entitlements(ents->entitlements, ntohl(ents->length) - offsetof(struct cs_entitlements, entitlements)); } } return nil; } static NSDictionary *cached_app_entitlements(void) { static NSDictionary *entitlements = nil; if (!entitlements) { entitlements = app_entitlements(); } return entitlements; } bool jb_has_container(void) { NSDictionary *entitlements = cached_app_entitlements(); return ![entitlements[@"com.apple.private.security.no-sandbox"] boolValue]; } static char *childArgv[] = {NULL, "debugme", NULL}; bool jb_spawn_ptrace_child(int argc, char **argv) { int ret; pid_t pid; if (argc > 1 && strcmp(argv[1], childArgv[1]) == 0) { ret = ptrace(PT_TRACE_ME, 0, NULL, 0); NSLog(@"child: ptrace(PT_TRACE_ME) %d", ret); exit(ret); } if (jb_has_container()) { return false; } childArgv[0] = argv[0]; if ((ret = posix_spawnp(&pid, argv[0], NULL, NULL, (char *const *)childArgv, NULL)) != 0) { return false; } return true; } bool jb_has_jit_entitlement(void) { NSDictionary *entitlements = cached_app_entitlements(); return [entitlements[@"dynamic-codesigning"] boolValue]; } bool jb_has_cs_disabled(void) { int flags; return !csops(getpid(), CS_OPS_STATUS, &flags, sizeof(flags)) && (flags & ~CS_KILL) == flags; } #define _COMM_PAGE_START_ADDRESS (0x0000000FFFFFC000ULL) /* In TTBR0 */ #define _COMM_PAGE_APRR_SUPPORT (_COMM_PAGE_START_ADDRESS+0x10C) static bool is_device_A12_or_newer(void) { // devices without APRR are definitely < A12 char aprr_support = *(volatile char *)_COMM_PAGE_APRR_SUPPORT; if (aprr_support == 0) { return false; } // we still have A11 devices that support APRR struct utsname systemInfo; if (uname(&systemInfo) != 0) { return false; } // iPhone 8, 8 Plus, and iPhone X if (strncmp("iPhone10,", systemInfo.machine, 9) == 0) { return false; } else { return true; } } bool jb_has_cs_execseg_allow_unsigned(void) { NSDictionary *entitlements = cached_app_entitlements(); if (@available(iOS 14.2, *)) { if (@available(iOS 14.4, *)) { return false; // iOS 14.4 broke it again } // technically we need to check the Code Directory and make sure // CS_EXECSEG_ALLOW_UNSIGNED is set but we assume that it is properly // signed, which should reflect the get-task-allow entitlement return is_device_A12_or_newer() && [entitlements[@"get-task-allow"] boolValue]; } else { return false; } } static bool jb_has_debugger_attached(void) { int flags; return !csops(getpid(), CS_OPS_STATUS, &flags, sizeof(flags)) && flags & CS_DEBUGGED; } bool jb_enable_ptrace_hack(void) { bool debugged = jb_has_debugger_attached(); // Thanks to this comment: https://news.ycombinator.com/item?id=18431524 // We use this hack to allow mmap with PROT_EXEC (which usually requires the // dynamic-codesigning entitlement) by tricking the process into thinking // that Xcode is debugging it. We abuse the fact that JIT is needed to // debug the process. if (ptrace(PT_TRACE_ME, 0, NULL, 0) < 0) { return false; } // ptracing ourselves confuses the kernel and will cause bad things to // happen to the system (hangs…) if an exception or signal occurs. Setup // some "safety nets" so we can cause the process to exit in a somewhat sane // state. We only need to do this if the debugger isn't attached. (It'll do // this itself, and if we do it we'll interfere with its normal operation // anyways.) if (!debugged) { // First, ensure that signals are delivered as Mach software exceptions… ptrace(PT_SIGEXC, 0, NULL, 0); // …then ensure that this exception goes through our exception handler. // I think it's OK to just watch for EXC_SOFTWARE because the other // exceptions (e.g. EXC_BAD_ACCESS, EXC_BAD_INSTRUCTION, and friends) // will end up being delivered as signals anyways, and we can get them // once they're resent as a software exception. mach_port_t port = MACH_PORT_NULL; mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); task_set_exception_ports(mach_task_self(), EXC_MASK_SOFTWARE, port, EXCEPTION_DEFAULT, THREAD_STATE_NONE); pthread_t thread; pthread_create(&thread, NULL, exception_handler, (void *)&port); } return true; } float g_safeInsetLeft = 0.0; float g_safeInsetRight = 0.0; float g_safeInsetTop = 0.0; float g_safeInsetBottom = 0.0; // We no longer need to judge if jit is usable or not by according to the ios version. static bool g_jitAvailable = true; //static int g_iosVersionMinor; static int g_iosVersionMajor; static std::string version; std::string System_GetProperty(SystemProperty prop) { switch (prop) { case SYSPROP_NAME: return StringFromFormat("iOS %s", version.c_str()); case SYSPROP_LANGREGION: return [[[NSLocale currentLocale] objectForKey:NSLocaleIdentifier] UTF8String]; case SYSPROP_BUILD_VERSION: return PPSSPP_GIT_VERSION; default: return ""; } } std::vector System_GetPropertyStringVec(SystemProperty prop) { switch (prop) { case SYSPROP_TEMP_DIRS: default: return std::vector(); } } int64_t System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: return 44100; case SYSPROP_DEVICE_TYPE: return DEVICE_TYPE_MOBILE; case SYSPROP_SYSTEMVERSION: return g_iosVersionMajor; default: return -1; } } float System_GetPropertyFloat(SystemProperty prop) { switch (prop) { case SYSPROP_DISPLAY_REFRESH_RATE: return 60.f; case SYSPROP_DISPLAY_SAFE_INSET_LEFT: return g_safeInsetLeft; case SYSPROP_DISPLAY_SAFE_INSET_RIGHT: return g_safeInsetRight; case SYSPROP_DISPLAY_SAFE_INSET_TOP: return g_safeInsetTop; case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM: return g_safeInsetBottom; default: return -1; } } bool System_GetPropertyBool(SystemProperty prop) { switch (prop) { case SYSPROP_HAS_OPEN_DIRECTORY: return false; case SYSPROP_HAS_BACK_BUTTON: return false; case SYSPROP_HAS_ACCELEROMETER: return true; case SYSPROP_APP_GOLD: #ifdef GOLD return true; #else return false; #endif case SYSPROP_CAN_JIT: return g_jitAvailable; #ifndef HTTPS_NOT_AVAILABLE case SYSPROP_SUPPORTS_HTTPS: return true; #endif default: return false; } } void System_Notify(SystemNotification notification) { switch (notification) { } } bool System_MakeRequest(SystemRequestType type, int requestId, const std::string ¶m1, const std::string ¶m2, int64_t param3, int64_t param4) { switch (type) { case SystemRequestType::EXIT_APP: exit(0); // The below seems right, but causes hangs. See #12140. // dispatch_async(dispatch_get_main_queue(), ^{ // [sharedViewController shutdown]; // exit(0); // }); break; case SystemRequestType::BROWSE_FOR_FILE: { DarwinDirectoryPanelCallback callback = [requestId] (bool success, Path path) { if (success) { g_requestManager.PostSystemSuccess(requestId, path.c_str()); } else { g_requestManager.PostSystemFailure(requestId); } }; DarwinFileSystemServices services; services.presentDirectoryPanel(callback, /* allowFiles = */ true, /* allowDirectories = */ false); return true; } case SystemRequestType::BROWSE_FOR_FOLDER: { DarwinDirectoryPanelCallback callback = [requestId] (bool success, Path path) { if (success) { g_requestManager.PostSystemSuccess(requestId, path.c_str()); } else { g_requestManager.PostSystemFailure(requestId); } }; DarwinFileSystemServices services; services.presentDirectoryPanel(callback, /* allowFiles = */ false, /* allowDirectories = */ true); return true; } case SystemRequestType::CAMERA_COMMAND: if (!strncmp(param1.c_str(), "startVideo", 10)) { int width = 0, height = 0; sscanf(param1.c_str(), "startVideo_%dx%d", &width, &height); setCameraSize(width, height); startVideo(); } else if (!strcmp(param1.c_str(), "stopVideo")) { stopVideo(); } return true; case SystemRequestType::GPS_COMMAND: if (param1 == "open") { startLocation(); } else if (param1 == "close") { stopLocation(); } return true; case SystemRequestType::SHARE_TEXT: { NSString *text = [NSString stringWithUTF8String:param1.c_str()]; [sharedViewController shareText:text]; return true; } default: return false; } } void System_Toast(std::string_view text) {} void System_AskForPermission(SystemPermission permission) {} PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; } FOUNDATION_EXTERN void AudioServicesPlaySystemSoundWithVibration(unsigned long, objc_object*, NSDictionary*); BOOL SupportsTaptic() { // we're on an iOS version that cannot instantiate UISelectionFeedbackGenerator, so no. if(!NSClassFromString(@"UISelectionFeedbackGenerator")) { return NO; } // http://www.mikitamanko.com/blog/2017/01/29/haptic-feedback-with-uifeedbackgenerator/ // use private API against UIDevice to determine the haptic stepping // 2 - iPhone 7 or above, full taptic feedback // 1 - iPhone 6S, limited taptic feedback // 0 - iPhone 6 or below, no taptic feedback NSNumber* val = (NSNumber*)[[UIDevice currentDevice] valueForKey:@"feedbackSupportLevel"]; return [val intValue] >= 2; } void System_Vibrate(int mode) { if (SupportsTaptic()) { PPSSPPUIApplication* app = (PPSSPPUIApplication*)[UIApplication sharedApplication]; if(app.feedbackGenerator == nil) { app.feedbackGenerator = [[UISelectionFeedbackGenerator alloc] init]; [app.feedbackGenerator prepare]; } [app.feedbackGenerator selectionChanged]; } else { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; NSArray *pattern = @[@YES, @30, @NO, @2]; dictionary[@"VibePattern"] = pattern; dictionary[@"Intensity"] = @2; AudioServicesPlaySystemSoundWithVibration(kSystemSoundID_Vibrate, nil, dictionary); } } int main(int argc, char *argv[]) { // Hacky hacks to try to enable JIT by pretending to be a debugger. csops = reinterpret_cast(dlsym(dlopen(nullptr, RTLD_LAZY), "csops")); exc_server = reinterpret_cast(dlsym(dlopen(NULL, RTLD_LAZY), "exc_server")); ptrace = reinterpret_cast(dlsym(dlopen(NULL, RTLD_LAZY), "ptrace")); // see https://github.com/hrydgard/ppsspp/issues/11905 // Tried checking for JIT support here with AllocateExecutableMemory and ProtectMemoryPages, // but it just succeeds, and then fails when you try to execute from it. // So, we'll just resort to a version check. version = [[[UIDevice currentDevice] systemVersion] UTF8String]; if (2 != sscanf(version.c_str(), "%d", &g_iosVersionMajor)) { // Just set it to 14.0 if the parsing fails for whatever reason. g_iosVersionMajor = 14; } if (jb_spawn_ptrace_child(argc, argv)) { INFO_LOG(SYSTEM, "JIT: ptrace() child spawn trick\n"); } else if (jb_has_jit_entitlement()) { INFO_LOG(SYSTEM, "JIT: found entitlement\n"); } else if (jb_has_cs_disabled()) { INFO_LOG(SYSTEM, "JIT: CS_KILL disabled\n"); } else if (jb_has_cs_execseg_allow_unsigned()) { INFO_LOG(SYSTEM, "JIT: CS_EXECSEG_ALLOW_UNSIGNED set\n"); } else if (jb_enable_ptrace_hack()) { INFO_LOG(SYSTEM, "JIT: ptrace() hack supported\n"); } else { INFO_LOG(SYSTEM, "JIT: ptrace() hack failed\n"); g_jitAvailable = false; } /* if (g_iosVersionMajor > 14 || (g_iosVersionMajor == 14 && g_iosVersionMinor >= 4)) { g_jitAvailable = false; } else { g_jitAvailable = true; } */ PROFILE_INIT(); // Ignore sigpipe. if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { perror("Unable to ignore SIGPIPE"); } @autoreleasepool { return UIApplicationMain(argc, argv, NSStringFromClass([PPSSPPUIApplication class]), NSStringFromClass([AppDelegate class])); } }