diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index dae773a..8dac48e 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -579,7 +579,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { return } - if !installUrl.startAccessingSecurityScopedResource() { + if !FileManager.default.isReadableFile(atPath: installUrl.path) && !installUrl.startAccessingSecurityScopedResource() { errorInfo = "lc.appList.ipaAccessError".loc errorShow = true return diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index f91207e..56f7e02 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -3216,7 +3216,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Install another LiveContainer" + "value" : "Install Another LiveContainer" } }, "zh_CN" : { @@ -3267,7 +3267,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Reinstall another LiveContainer" + "value" : "Reinstall Another LiveContainer" } }, "zh_CN" : { diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 721a4bb..aaebe5b 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -793,15 +793,44 @@ extension LCUtils { onServerMessage?("Please make sure the VPN is connected if the server is not in your local network.") do { + + onServerMessage?("Contacting JitStreamer-EB server at \(JITStresmerEBAddress)...") + + let session = URLSession.shared + let decoder = JSONDecoder() + + let mountStatusUrlStr = "\(JITStresmerEBAddress)/mount" + guard let mountStatusUrl = URL(string: mountStatusUrlStr) else { return false } + let mountRequest = URLRequest(url: mountStatusUrl) + + // check mount status + onServerMessage?("Checking mount status...") + let (mountData, mountResponse) = try await session.asyncRequest(request: mountRequest) + guard let mountData else { + onServerMessage?("Failed to mount status from server!") + return false + } + let mountResponseObj = try decoder.decode(JITStreamerEBMountResponse.self, from: mountData) + guard mountResponseObj.ok else { + onServerMessage?(mountResponseObj.error ?? "Mounting failed with unknown error.") + return false + } + if mountResponseObj.mounting { + onServerMessage?("Your device is currently mounting the developer disk image. Leave your device on and connected. Once this finishes, you can run JitStreamer again.") + onServerMessage?("Check \(JITStresmerEBAddress)/mount_status for mounting status.") + return false + } + + // send launch_app request let launchJITUrlStr = "\(JITStresmerEBAddress)/launch_app/\(Bundle.main.bundleIdentifier ?? "")" guard let launchJITUrl = URL(string: launchJITUrlStr) else { return false } - let session = URLSession.shared + - onServerMessage?("Contacting JitStreamer-EB server at \(JITStresmerEBAddress)...") + onServerMessage?("Sending launch request...") let request1 = URLRequest(url: launchJITUrl) let (data, response) = try await session.asyncRequest(request: request1) - let decoder = JSONDecoder() + guard let data else { onServerMessage?("Failed to retrieve data from server!") return false @@ -813,11 +842,7 @@ extension LCUtils { return false } - if launchAppResponse.mounting { - onServerMessage?("Your device is currently mounting the developer disk image. Leave your device on and connected. Once this finishes, you can run JitStreamer again.") - } else { - onServerMessage?("Your app will launch soon! You are position \(launchAppResponse.position ?? -1) in the queue.") - } + onServerMessage?("Your app will launch soon! You are position \(launchAppResponse.position ?? -1) in the queue.") // start polling status let statusUrlStr = "\(JITStresmerEBAddress)/status" @@ -841,23 +866,10 @@ extension LCUtils { } if statusResponse.done { onServerMessage?("Server done.") - if launchAppResponse.mounting { - onServerMessage?("Run the app again to enable JIT.") - } - return true } - if statusResponse.in_progress { - if launchAppResponse.mounting { - onServerMessage?("JIT enabling in progress. (Attempt \(i + 1)/\(maxTries))") - } else { - onServerMessage?("Mounting in progress. (Attempt \(i + 1)/\(maxTries))") - } - - } else { - onServerMessage?("Your app will launch soon! You are position \(launchAppResponse.position ?? -1) in the queue. (Attempt \(i + 1)/\(maxTries))") - } + onServerMessage?("Your app will launch soon! You are position \(launchAppResponse.position ?? -1) in the queue. (Attempt \(i + 1)/\(maxTries))") } @@ -876,7 +888,6 @@ extension LCUtils { struct JITStreamerEBLaunchAppResponse : Codable { let ok: Bool let launching: Bool - let mounting: Bool let position: Int? let error: String? } @@ -884,7 +895,12 @@ struct JITStreamerEBLaunchAppResponse : Codable { struct JITStreamerEBStatusResponse : Codable { let ok: Bool let done: Bool - let in_progress: Bool let position: Int? let error: String? } + +struct JITStreamerEBMountResponse : Codable { + let ok: Bool + let mounting: Bool + let error: String? +} diff --git a/Makefile b/Makefile index 961f6a0..69c7e9a 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ export CONFIG_COMMIT = $(shell git log --oneline | sed '2,10000000d' | cut -b 1- # Build the app APPLICATION_NAME = LiveContainer -$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m LCSharedUtils.m NSUserDefaults.m SecItem.m fishhook/fishhook.c +$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m LCSharedUtils.m Tweaks/NSUserDefaults.m Tweaks/SecItem.m Tweaks/NSBundle+FixCydiaSubstrate.m Tweaks/Dyld.m fishhook/fishhook.c $(APPLICATION_NAME)_CODESIGN_FLAGS = -Sentitlements.xml $(APPLICATION_NAME)_CFLAGS = -fobjc-arc $(APPLICATION_NAME)_LDFLAGS = -e _LiveContainerMain -rpath @loader_path/Frameworks diff --git a/Resources/Info.plist b/Resources/Info.plist index e1e5d87..85d4155 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -42,7 +42,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.2.55 + 3.2.56 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -61,7 +61,7 @@ CFBundleVersion - 3.2.55 + 3.2.56 LSApplicationCategoryType public.app-category.games LSApplicationQueriesSchemes diff --git a/TweakLoader/Makefile b/TweakLoader/Makefile index 00537f4..6d20968 100644 --- a/TweakLoader/Makefile +++ b/TweakLoader/Makefile @@ -6,7 +6,7 @@ include $(THEOS)/makefiles/common.mk LIBRARY_NAME = TweakLoader -TweakLoader_FILES = TweakLoader.m NSBundle+FixCydiaSubstrate.m NSFileManager+GuestHooks.m UIKit+GuestHooks.m utils.m DocumentPicker.m FBSSerialQueue.m ../Localization.m +TweakLoader_FILES = TweakLoader.m NSFileManager+GuestHooks.m UIKit+GuestHooks.m utils.m DocumentPicker.m FBSSerialQueue.m ../Localization.m TweakLoader_CFLAGS = -objc-arc TweakLoader_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks TweakLoader_PRIVATE_FRAMEWORKS = CoreServices FrontBoard RunningBoardServices diff --git a/TweakLoader/NSBundle+FixCydiaSubstrate.m b/TweakLoader/NSBundle+FixCydiaSubstrate.m deleted file mode 100644 index c7bd554..0000000 --- a/TweakLoader/NSBundle+FixCydiaSubstrate.m +++ /dev/null @@ -1,29 +0,0 @@ -#import -#import "utils.h" - -__attribute__((constructor)) -static void NSBundleHookInit(void) { - swizzle(NSBundle.class, @selector(bundlePath), @selector(hook_bundlePath)); - swizzle(NSBundle.class, @selector(executablePath), @selector(hook_executablePath)); - -} - -@implementation NSBundle(FixCydiaSubstrate) - -- (NSString *)hook_bundlePath { - NSString *path = self.hook_bundlePath; - if ([path hasPrefix:@"/var"]) { - return [@"/private" stringByAppendingPathComponent:path]; - } - return path; -} - -- (NSString *)hook_executablePath { - NSString *path = self.hook_executablePath; - if ([path hasPrefix:@"/var"]) { - return [@"/private" stringByAppendingPathComponent:path]; - } - return path; -} - -@end diff --git a/Tweaks/Dyld.m b/Tweaks/Dyld.m new file mode 100644 index 0000000..83ac007 --- /dev/null +++ b/Tweaks/Dyld.m @@ -0,0 +1,77 @@ +// +// Dyld.m +// LiveContainer +// +// Created by s s on 2025/2/7. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#import "../fishhook/fishhook.h" + +uint32_t appMainImageIndex = 0; +void* appExecutableHandle = 0; + +void* (*orig_dlsym)(void * __handle, const char * __symbol); +uint32_t (*orig_dyld_image_count)(void); +const struct mach_header* (*orig_dyld_get_image_header)(uint32_t image_index); +intptr_t (*orig_dyld_get_image_vmaddr_slide)(uint32_t image_index); +const char* (*orig_dyld_get_image_name)(uint32_t image_index); + +static inline int translateImageIndex(int origin) { + if(origin == 1) { + return appMainImageIndex; + } + if(origin >= appMainImageIndex) { + return origin + 1; + } + return origin; +} + + +void* hook_dlsym(void * __handle, const char * __symbol) { + if(__handle == (void*)RTLD_MAIN_ONLY) { + if(strcmp(__symbol, MH_EXECUTE_SYM) == 0) { + return (void*)orig_dyld_get_image_header(appMainImageIndex); + } + __handle = appExecutableHandle; + } + + __attribute__((musttail)) return orig_dlsym(__handle, __symbol); +} + +uint32_t hook_dyld_image_count(void) { + return orig_dyld_image_count() - 1; +} + +const struct mach_header* hook_dyld_get_image_header(uint32_t image_index) { + __attribute__((musttail)) return orig_dyld_get_image_header(translateImageIndex(image_index)); +} + +intptr_t hook_dyld_get_image_vmaddr_slide(uint32_t image_index) { + __attribute__((musttail)) return orig_dyld_get_image_vmaddr_slide(translateImageIndex(image_index)); +} + +const char* hook_dyld_get_image_name(uint32_t image_index) { + __attribute__((musttail)) return orig_dyld_get_image_name(translateImageIndex(image_index)); +} + + + +void DyldHooksInit(void) { + // hook dlsym to solve RTLD_MAIN_ONLY, hook other functions to hide LiveContainer itself + rebind_symbols((struct rebinding[5]){ + {"dlsym", (void *)hook_dlsym, (void **)&orig_dlsym}, + {"_dyld_image_count", (void *)hook_dyld_image_count, (void **)&orig_dyld_image_count}, + {"_dyld_get_image_header", (void *)hook_dyld_get_image_header, (void **)&orig_dyld_get_image_header}, + {"_dyld_get_image_vmaddr_slide", (void *)hook_dyld_get_image_vmaddr_slide, (void **)&orig_dyld_get_image_vmaddr_slide}, + {"_dyld_get_image_name", (void *)hook_dyld_get_image_name, (void **)&orig_dyld_get_image_name}, + },5); +} diff --git a/Tweaks/NSBundle+FixCydiaSubstrate.m b/Tweaks/NSBundle+FixCydiaSubstrate.m new file mode 100644 index 0000000..0bdf353 --- /dev/null +++ b/Tweaks/NSBundle+FixCydiaSubstrate.m @@ -0,0 +1,22 @@ +#import +#import "Tweaks.h" +#import + +@implementation NSString(LiveContainer) +- (NSString *)lc_realpath { + // stringByResolvingSymlinksInPath does not fully resolve symlink, and some apps will cradh without /private prefix + char result[PATH_MAX]; + realpath(self.fileSystemRepresentation, result); + return [NSString stringWithUTF8String:result]; +} +@end +@implementation NSBundle(LiveContainer) +// Built-in initWith* will strip out the /private prefix, which could crash certain apps +// This initializer replicates +[NSBundle mainBundle] to solve this issue (FIXME: may not work) +- (instancetype)initWithPathForMainBundle:(NSString *)path { + self = [self init]; + CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:path.lc_realpath]; + object_setIvar(self, class_getInstanceVariable(self.class, "_cfBundle"), CFBridgingRelease(CFBundleCreate(NULL, url))); + return self; +} +@end diff --git a/NSUserDefaults.m b/Tweaks/NSUserDefaults.m similarity index 100% rename from NSUserDefaults.m rename to Tweaks/NSUserDefaults.m diff --git a/SecItem.m b/Tweaks/SecItem.m similarity index 99% rename from SecItem.m rename to Tweaks/SecItem.m index 08a6662..9cef96d 100644 --- a/SecItem.m +++ b/Tweaks/SecItem.m @@ -8,7 +8,7 @@ #import #import "utils.h" #import -#import "fishhook/fishhook.h" +#import "../fishhook/fishhook.h" extern void* (*msHookFunction)(void *symbol, void *hook, void **old); OSStatus (*orig_SecItemAdd)(CFDictionaryRef attributes, CFTypeRef *result); diff --git a/Tweaks/Tweaks.h b/Tweaks/Tweaks.h new file mode 100644 index 0000000..d4b3f30 --- /dev/null +++ b/Tweaks/Tweaks.h @@ -0,0 +1,21 @@ +// +// Tweaks.h +// LiveContainer +// +// Created by s s on 2025/2/7. +// + +void swizzle(Class class, SEL originalAction, SEL swizzledAction); + + +void NUDGuestHooksInit(void); +void SecItemGuestHooksInit(void); +void DyldHooksInit(void); + +@interface NSBundle(LiveContainer) +- (instancetype)initWithPathForMainBundle:(NSString *)path; +@end + + +extern uint32_t appMainImageIndex; +extern void* appExecutableHandle; diff --git a/ZSign/macho.cpp b/ZSign/macho.cpp index 062e832..694b12c 100644 --- a/ZSign/macho.cpp +++ b/ZSign/macho.cpp @@ -362,6 +362,6 @@ bool is_64bit_macho(const char *filepath) { fread(&magic, sizeof(uint32_t), 1, file); fclose(file); - // 64-bit Mach-O magic number is 0xfeedfacf - return magic == 0xfeedfacf; + // check 64-bit Mach-O magic number + return magic == MH_MAGIC_64 || magic == FAT_CIGAM; } diff --git a/control b/control index bd543a2..1337a62 100644 --- a/control +++ b/control @@ -1,6 +1,6 @@ Package: com.kdt.livecontainer Name: livecontainer -Version: 3.2.55 +Version: 3.2.56 Architecture: iphoneos-arm Description: Run iOS app without actually installing it! Maintainer: khanhduytran0 diff --git a/main.m b/main.m index f174d9d..0aac82a 100644 --- a/main.m +++ b/main.m @@ -15,6 +15,7 @@ #include #include "TPRO.h" #import "fishhook/fishhook.h" +#import "Tweaks/Tweaks.h" #include static int (*appMain)(int, char**); @@ -26,9 +27,6 @@ NSBundle* lcMainBundle; NSDictionary* guestAppInfo; -void NUDGuestHooksInit(); -void SecItemGuestHooksInit(); - @implementation NSUserDefaults(LiveContainer) + (instancetype)lcUserDefaults { return lcUserDefaults; @@ -203,19 +201,6 @@ static void overwriteExecPath(NSString *bundlePath) { return (void *)header + entryoff; } -uint32_t appMainImageIndex = 0; -void* appExecutableHandle = 0; -void* (*orig_dlsym)(void * __handle, const char * __symbol); -void* new_dlsym(void * __handle, const char * __symbol) { - if(__handle == (void*)RTLD_MAIN_ONLY) { - if(strcmp(__symbol, MH_EXECUTE_SYM) == 0) { - return (void*)_dyld_get_image_header(appMainImageIndex); - } - __handle = appExecutableHandle; - } - - __attribute__((musttail)) return orig_dlsym(__handle, __symbol); -} static NSString* invokeAppMain(NSString *selectedApp, NSString *selectedContainer, int argc, char *argv[]) { NSString *appError = nil; @@ -260,7 +245,7 @@ static void overwriteExecPath(NSString *bundlePath) { } } - NSBundle *appBundle = [[NSBundle alloc] initWithPath:bundlePath]; + NSBundle *appBundle = [[NSBundle alloc] initWithPathForMainBundle:bundlePath]; if(!appBundle) { return @"App not found"; @@ -426,8 +411,7 @@ static void overwriteExecPath(NSString *bundlePath) { uint32_t appIndex = _dyld_image_count(); appMainImageIndex = appIndex; - // hook dlsym to solve RTLD_MAIN_ONLY - rebind_symbols((struct rebinding[1]){{"dlsym", (void *)new_dlsym, (void **)&orig_dlsym}},1); + DyldHooksInit(); void *appHandle = dlopen(*path, RTLD_LAZY|RTLD_GLOBAL|RTLD_FIRST); appExecutableHandle = appHandle; @@ -455,8 +439,8 @@ static void overwriteExecPath(NSString *bundlePath) { } NSLog(@"[LCBootstrap] loaded bundle"); - // Find main() - appMain = getAppEntryPoint(appHandle, appIndex); + // Find main(), pass 1 as we hooked _dyld_get_image_header + appMain = getAppEntryPoint(appHandle, 1); if (!appMain) { appError = @"Could not find the main entry point"; NSLog(@"[LCBootstrap] %@", appError);