1、环境
1、OS X EI Caption 10.11.1 & Xcode 7
2、Xcode安装Command Line Tools
3、iPhone 安装AppSync
2、MobileInstallation.framework 私有API
/*!
* @brief Mobile Installation 的回调定义
*/
typedef void (*MobileInstallationCallback)(CFDictionaryRef information);
/*!
* @brief Mobile Installation 安装App (8.0)
* @param bundlePath IPA文件路径
* @param parameters unknown
* @param unknown1 unknown
* @param unknown2 unknown
*/
extern int MobileInstallationInstallForLaunchServices(CFStringRef bundlePath, CFDictionaryRef parameters, void *unknown1, void *unknown2) NS_AVAILABLE_IOS(8_0);
/*!
* @brief Mobile Installation 卸载App (8.0)
* @param bundleIdentifier App的Bundle ID
* @param parameters unknown
* @param callback Mobile Installation 的回调
* @param unknown unknown
*/
extern int MobileInstallationUninstallForLaunchServices(CFStringRef bundleIdentifier, CFDictionaryRef parameters, MobileInstallationCallback callback, void *unknown) NS_AVAILABLE_IOS(8_0);
以上是函数符号
3、关键代码
void *lib = dlopen([frameworkPath UTF8String], RTLD_LAZY); if (lib) { MobileInstallationInstall pMobileInstallationInstall = (MobileInstallationInstall)dlsym(lib, "MobileInstallationInstall"); if (pMobileInstallationInstall) { NSString* temp = [NSTemporaryDirectory() stringByAppendingPathComponent:[@"Temp_" stringByAppendingString:ipaPath.lastPathComponent]]; if (![[NSFileManager defaultManager] copyItemAtPath:ipaPath toPath:temp error:nil]) { [self showAlertMessage:@"检查要安装的IPA路径是否正确!" Title:@"复制IPA文件失败"]; [SVProgressHUD dismiss]; return NO; } int ret = pMobileInstallationInstall(temp, [NSDictionary dictionaryWithObject:@"User" forKey:@"ApplicationType"], 0, temp); [[NSFileManager defaultManager] removeItemAtPath:temp error:nil]; if (ret == 0) { [self showAlertMessage:@"请退出桌面确定是否有个HelloIPA的程序!" Title:@"安装成功"]; [SVProgressHUD dismiss]; return YES; } else { [self showAlertMessage:@"若为真机,确定该设备已经jailbreak!" Title:@"安装失败"]; [SVProgressHUD dismiss]; return NO; } } } else { [self showAlertMessage:@"检查MobileInstallation.framework路径是否正确!" Title:@"无法连接到MobileInstallation"]; [SVProgressHUD dismiss]; return NO; } return NO;
以上代码可以在App中使用
4、使用ldid签名
上面的函数如果没有经过签名,会返回-1
application-identifier <string>com.q2q.testIPAInstall222string>com.apple.private.mobileinstall.allowedSPI <string>Installstring><string>Browsestring><string>Uninstallstring><string>InstallForLaunchServicesstring><string>UninstallForLaunchServicesstring> com.apple.springboard.debugapplications <true/>get-task-allow <true/>task_for_pid-allow <true/>
直接使用ldid签名可执行文件后,重新打包成ipa就可以了。
如果觉得复杂,也可以在Xcode中设置
使用普通的账号就可以了
https://github.com/kryhear/IPAInstaller/tree/master/testIPAInstall.xcodeproj 工程中的代码没有设置签名,导致调用是不成功的
5、ipainstaller源码
#include
#import
#import
#import "ZipArchive/ZipArchive.h"
#import "UIDevice-Capabilities/UIDevice-Capabilities.h"
#define EXECUTABLE_VERSION @"3.4.1"
#define KEY_INSTALL_TYPE @"User"
#define KEY_SDKPATH "/System/Library/PrivateFrameworks/MobileInstallation.framework/MobileInstallation"
#define IPA_FAILED -1
typedef int (*MobileInstallationInstall)(NSString *path, NSDictionary *dict, void *na, NSString *backpath);
typedef int (*MobileInstallationUninstall)(NSString *bundleID, NSDictionary *dict, void *na);
@interface LSApplicationWorkspace : NSObject
+ (LSApplicationWorkspace *)defaultWorkspace;
- (BOOL)installApplication:(NSURL *)path withOptions:(NSDictionary *)options;
- (BOOL)uninstallApplication:(NSString *)identifier withOptions:(NSDictionary *)options;
- (BOOL)applicationIsInstalled:(NSString *)appIdentifier;
- (NSArray *)allInstalledApplications;
- (NSArray *)allApplications;
- (NSArray *)applicationsOfType:(unsigned int)appType; // 0 for user, 1 for system
@end
@interface LSApplicationProxy : NSObject
+ (LSApplicationProxy *)applicationProxyForIdentifier:(id)appIdentifier;
@property(readonly) NSString * applicationIdentifier;
@property(readonly) NSString * bundleVersion;
@property(readonly) NSString * bundleExecutable;
@property(readonly) NSArray * deviceFamily;
@property(readonly) NSURL * bundleContainerURL;
@property(readonly) NSString * bundleIdentifier;
@property(readonly) NSURL * bundleURL;
@property(readonly) NSURL * containerURL;
@property(readonly) NSURL * dataContainerURL;
@property(readonly) NSString * localizedShortName;
@property(readonly) NSString * localizedName;
@property(readonly) NSString * shortVersionString;
@end
static NSString *SystemVersion = nil;
static int DeviceModel = 0;
static BOOL isUninstall = NO;
static BOOL isGetInfo = NO;
static BOOL isListing = NO;
static BOOL isBackup = NO;
static BOOL isBackupFull = NO;
static BOOL cleanInstall = NO;
static int quietInstall = 0; //0 is show all outputs, 1 is to show only errors, 2 is to show nothing
static BOOL forceInstall = NO;
static BOOL removeMetadata = NO;
static BOOL deleteFile = NO;
static BOOL notRestore = NO;
static NSString * randomStringInLength(int len) {
NSString *ret = @"";
NSString *letters = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (int i=0; iver2
BOOL isEmpty1 = (ver1 == nil || [ver1 length] == 0);
BOOL isEmpty2 = (ver2 == nil || [ver2 length] == 0);
if (isEmpty1 && isEmpty2)
return 0;
else if (isEmpty1 && !isEmpty2)
return -1;
else if (!isEmpty1 && isEmpty2)
return 1;
else {
NSArray *components1 = [ver1 componentsSeparatedByString:@"."];
NSArray *components2 = [ver2 componentsSeparatedByString:@"."];
int count = [components1 count] > [components2 count] ? [components2 count] : [components1 count];
for (int i=0; i num2)
return 1;
else {
if ([[components1 objectAtIndex:i] isEqualToString:[components2 objectAtIndex:i]])
continue;
else
return [[components1 objectAtIndex:i] compare:[components2 objectAtIndex:i]] == NSOrderedDescending ? 1 : -1;
}
}
if ([components1 count] != [components2 count])
return [components1 count] > [components2 count] ? 1 : -1;
else
return 0;
}
}
static NSArray *getInstalledApplications() {
if (kCFCoreFoundationVersionNumber < 1140.10) {
NSDictionary *mobileInstallationPlist = [NSDictionary dictionaryWithContentsOfFile:@"/private/var/mobile/Library/Caches/com.apple.mobile.installation.plist"];
NSDictionary *installedAppDict = (NSDictionary*)[mobileInstallationPlist objectForKey:@"User"];
NSArray * identifiers = [[installedAppDict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
return identifiers;
} else {
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
if (LSApplicationWorkspace_class) {
LSApplicationWorkspace *workspace = [LSApplicationWorkspace_class performSelector:@selector(defaultWorkspace)];
if (workspace) {
NSArray *allApps = [workspace applicationsOfType:0];
NSMutableArray *identifiers = [NSMutableArray arrayWithCapacity:[allApps count]];
for (LSApplicationProxy *appBundle in allApps)
[identifiers addObject:appBundle.bundleIdentifier];
return [identifiers sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}
}
}
return nil;
}
static NSString *formatDictValue(NSObject *object) {
return object ? (NSString *)object : @"";
}
static NSString *getBestString(NSString *main, NSString *minor) {
return (minor && [minor length] > 0) ? minor : (main ? main : @"");
}
static NSDictionary *getInstalledAppInfo(NSString *appIdentifier) {
if (kCFCoreFoundationVersionNumber < 1140.10) {
NSDictionary *mobileInstallationPlist = [NSDictionary dictionaryWithContentsOfFile:@"/private/var/mobile/Library/Caches/com.apple.mobile.installation.plist"];
NSDictionary *installedAppDict = (NSDictionary*)[mobileInstallationPlist objectForKey:@"User"];
NSDictionary *appInfo = [installedAppDict objectForKey:appIdentifier];
if (appInfo) {
NSMutableDictionary *info = [NSMutableDictionary dictionaryWithCapacity:8];
[info setObject:formatDictValue([appInfo objectForKey:@"CFBundleIdentifier"]) forKey:@"APP_ID"];
[info setObject:formatDictValue([appInfo objectForKey:@"Container"]) forKey:@"BUNDLE_PATH"];
[info setObject:formatDictValue([appInfo objectForKey:@"Path"]) forKey:@"APP_PATH"];
[info setObject:formatDictValue([appInfo objectForKey:@"Container"]) forKey:@"DATA_PATH"];
[info setObject:formatDictValue([appInfo objectForKey:@"CFBundleVersion"]) forKey:@"VERSION"];
[info setObject:formatDictValue([appInfo objectForKey:@"CFBundleShortVersionString"]) forKey:@"SHORT_VERSION"];
[info setObject:formatDictValue([appInfo objectForKey:@"CFBundleName"]) forKey:@"NAME"];
[info setObject:formatDictValue([appInfo objectForKey:@"CFBundleDisplayName"]) forKey:@"DISPLAY_NAME"];
return info;
}
} else {
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
if (LSApplicationWorkspace_class) {
LSApplicationWorkspace *workspace = [LSApplicationWorkspace_class performSelector:@selector(defaultWorkspace)];
if (workspace && [workspace applicationIsInstalled:appIdentifier]) {
Class LSApplicationProxy_class = objc_getClass("LSApplicationProxy");
if (LSApplicationProxy_class) {
LSApplicationProxy *app = [LSApplicationProxy_class applicationProxyForIdentifier:appIdentifier];
if (app) {
NSMutableDictionary *info = [NSMutableDictionary dictionaryWithCapacity:9];
[info setObject:formatDictValue(app.bundleIdentifier) forKey:@"APP_ID"];
[info setObject:formatDictValue([app.bundleContainerURL path]) forKey:@"BUNDLE_PATH"];
[info setObject:formatDictValue([app.bundleURL path]) forKey:@"APP_PATH"];
[info setObject:formatDictValue([app.dataContainerURL path]) forKey:@"DATA_PATH"];
[info setObject:formatDictValue(app.bundleVersion) forKey:@"VERSION"];
[info setObject:formatDictValue(app.shortVersionString) forKey:@"SHORT_VERSION"];
[info setObject:formatDictValue(app.localizedName) forKey:@"NAME"];
[info setObject:formatDictValue(app.localizedShortName) forKey:@"DISPLAY_NAME"];
return info;
}
}
}
}
}
return nil;
}
static int installApp(NSString *ipaPath, NSString *ipaId) {
int ret = -1;
if (kCFCoreFoundationVersionNumber < 1140.10) {
void *lib = dlopen(KEY_SDKPATH, RTLD_LAZY);
if (lib) {
MobileInstallationInstall install = (MobileInstallationInstall)dlsym(lib, "MobileInstallationInstall");
if (install)
ret = install(ipaPath, [NSDictionary dictionaryWithObject:KEY_INSTALL_TYPE forKey:@"ApplicationType"], 0, ipaPath);
dlclose(lib);
}
} else {
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
if (LSApplicationWorkspace_class) {
LSApplicationWorkspace *workspace = [LSApplicationWorkspace_class performSelector:@selector(defaultWorkspace)];
if (workspace && [workspace installApplication:[NSURL fileURLWithPath:ipaPath] withOptions:[NSDictionary dictionaryWithObject:ipaId forKey:@"CFBundleIdentifier"]])
ret = 0;
}
}
return ret;
}
static BOOL uninstallApplication(NSString *appIdentifier) {
if (kCFCoreFoundationVersionNumber < 1140.10) {
void *lib = dlopen(KEY_SDKPATH, RTLD_LAZY);
if (lib) {
MobileInstallationUninstall uninstall = (MobileInstallationUninstall)dlsym(lib, "MobileInstallationUninstall");
if (uninstall)
return 0 == uninstall(appIdentifier, nil, nil);
dlclose(lib);
}
} else {
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
if (LSApplicationWorkspace_class) {
LSApplicationWorkspace *workspace = [LSApplicationWorkspace_class performSelector:@selector(defaultWorkspace)];
if (workspace && [workspace uninstallApplication:appIdentifier withOptions:nil])
return YES;
}
}
return NO;
}
int main (int argc, char **argv, char **envp) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
freopen("/dev/null", "w", stderr); //Suppress output from NSLog
//Get system info
SystemVersion = [UIDevice currentDevice].systemVersion;
NSString *deviceString = [UIDevice currentDevice].model;
if ([deviceString isEqualToString:@"iPhone"] || [deviceString isEqualToString:@"iPod touch"])
DeviceModel = 1;
else if ([deviceString isEqualToString:@"iPad"])
DeviceModel = 2;
else
DeviceModel = 3; //Apple TV maybe?
//Process parameters
NSArray *arguments = [[NSProcessInfo processInfo] arguments];
if ([arguments count] < 1) {
[pool release];
return IPA_FAILED;
}
NSString *executableName = [[arguments objectAtIndex:0] lastPathComponent];
NSString *helpString = [NSString stringWithFormat:@"Usage: %@ [OPTION]... [FILE]...\n %@ -{bB} [APP_ID] [-o OUTPUT_PATH]\n %@ -i [APP_ID]...\n %@ -l\n %@ -u [APP_ID]...\n\n\nOptions:\n -a Show tool about information.\n -b Back up application with given identifier to IPA.\n -B Back up application with given identifier and its documents and settings to IPA.\n -c Perform a clean install.\n If the application has already been installed, the existing documents and other resources will be cleared.\n This implements -n automatically.\n -d Delete IPA file(s) after installation.\n -f Force installation, do not check capabilities and system version.\n Installed application may not work properly.\n -h Display this usage information.\n -i Display information of installed application(s).\n -l List identifiers of all installed App Store applications.\n -n Do not restore saved documents and other resources.\n -o Output IPA to specified path, or the IPA will be saved under /var/mobile/Documents/.\n -q Quiet mode, suppress all normal outputs.\n -Q Quieter mode, suppress all outputs including errors.\n -r Remove iTunesMetadata.plist after installation.\n -u Uninstall application with given identifier(s).", executableName, executableName, executableName, executableName, executableName];
NSDate *today = [NSDate date];
NSDateFormatter *currentFormatter = [[NSDateFormatter alloc] init];
[currentFormatter setDateFormat:@"yyyy"];
NSString *aboutString = [NSString stringWithFormat:@"About %@\nInstall IPAs via command line or back up/browse/uninstall installed applications.\nVersion: %@\nAuthor: Merlin Mao\n\nZipArchive from Matt Connolly\nFSSystemHasCapability from Ryan Petrich\n\nCopyright \u00A9 2012%@ Merlin Mao. All rights reserved.", executableName, EXECUTABLE_VERSION, [[currentFormatter stringFromDate:today] isEqualToString:@"2012"] ? @"" : [@"-" stringByAppendingString:[currentFormatter stringFromDate:today]]];
[currentFormatter release];
if ([arguments count] == 1) {
printf("%s\n", [helpString cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return 0;
}
NSFileManager *fileMgr = [NSFileManager defaultManager];
if ([arguments count] >= 3) {
NSMutableArray *identifiers = [NSMutableArray array];
NSString *op1 = [arguments objectAtIndex:1];
if ([op1 isEqualToString:@"-uq"] || [op1 isEqualToString:@"-qu"]) {
isUninstall = YES;
quietInstall = 1;
for (unsigned int i=2; i<[arguments count]; i++)
[identifiers addObject:[arguments objectAtIndex:i]];
}
if ([op1 isEqualToString:@"-uQ"] || [op1 isEqualToString:@"-Qu"]) {
isUninstall = YES;
quietInstall = 2;
for (unsigned int i=2; i<[arguments count]; i++)
[identifiers addObject:[arguments objectAtIndex:i]];
}
NSString *op2 = [arguments objectAtIndex:2];
if ([op1 isEqualToString:@"-u"]) {
isUninstall = YES;
if ([op2 isEqualToString:@"-q"]) {
quietInstall = 1;
for (unsigned int i=3; i<[arguments count]; i++)
[identifiers addObject:[arguments objectAtIndex:i]];
}
else if ([op2 isEqualToString:@"-Q"]) {
quietInstall = 2;
for (unsigned int i=3; i<[arguments count]; i++)
[identifiers addObject:[arguments objectAtIndex:i]];
} else {
for (unsigned int i=2; i<[arguments count]; i++)
[identifiers addObject:[arguments objectAtIndex:i]];
}
}
if ([op1 isEqualToString:@"-i"]) {
isGetInfo = YES;
for (unsigned int i=2; i<[arguments count]; i++)
[identifiers addObject:[arguments objectAtIndex:i]];
}
if ([op2 isEqualToString:@"-u"]) {
if ([op1 isEqualToString:@"-q"]) {
isUninstall = YES;
quietInstall = 1;
for (unsigned int i=3; i<[arguments count]; i++)
[identifiers addObject:[arguments objectAtIndex:i]];
}
if ([op1 isEqualToString:@"-Q"]) {
quietInstall = 2;
for (unsigned int i=3; i<[arguments count]; i++)
[identifiers addObject:[arguments objectAtIndex:i]];
}
}
if (isGetInfo) {
if ([identifiers count] < 1) {
printf("You must specify at least one application identifier.\n");
[pool release];
return IPA_FAILED;
}
NSArray *installedApps = getInstalledApplications();
for (unsigned int i=0; i<[identifiers count]; i++) {
NSString *identifier = [identifiers objectAtIndex:i];
if ([installedApps containsObject:identifier]) {
NSDictionary *installedAppInfo = getInstalledAppInfo(identifier);
NSString *appDirPath = [installedAppInfo objectForKey:@"BUNDLE_PATH"];
NSString *appPath = [installedAppInfo objectForKey:@"APP_PATH"];
NSString *dataPath = [installedAppInfo objectForKey:@"DATA_PATH"];
NSString *appName = [installedAppInfo objectForKey:@"NAME"];
NSString *appDisplayName = [installedAppInfo objectForKey:@"DISPLAY_NAME"];
NSString *appVersion = [installedAppInfo objectForKey:@"VERSION"];
NSString *appShortVersion = [installedAppInfo objectForKey:@"SHORT_VERSION"];
printf("Identifier: %s\n", [identifier cStringUsingEncoding:NSUTF8StringEncoding]);
if ([appVersion length] > 0)
printf("Version: %s\n", [appVersion cStringUsingEncoding:NSUTF8StringEncoding]);
if ([appShortVersion length] > 0)
printf("Short Version: %s\n", [appShortVersion cStringUsingEncoding:NSUTF8StringEncoding]);
if ([appName length] > 0)
printf("Name: %s\n", [appName cStringUsingEncoding:NSUTF8StringEncoding]);
if ([appDisplayName length] > 0)
printf("Display Name: %s\n", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding]);
if ([appDirPath length] > 0)
printf("Bundle: %s\n", [appDirPath cStringUsingEncoding:NSUTF8StringEncoding]);
if ([appPath length] > 0)
printf("Application: %s\n", [appPath cStringUsingEncoding:NSUTF8StringEncoding]);
if ([dataPath length] > 0)
printf("Data: %s\n", [dataPath cStringUsingEncoding:NSUTF8StringEncoding]);
} else {
if (quietInstall < 2)
printf("Application \"%s\" is not installed.\n", [identifier cStringUsingEncoding:NSUTF8StringEncoding]);
}
if (i < [identifiers count] - 1)
printf("\n");
}
return 0;
}
if (isUninstall) {
if ([identifiers count] < 1) {
printf("You must specify at least one application identifier.\n");
[pool release];
return IPA_FAILED;
} else {
NSArray *installedApps = getInstalledApplications();
for (unsigned int i=0; i<[identifiers count]; i++) {
if ([installedApps containsObject:[identifiers objectAtIndex:i]]) {
printf("Removing application \"%s\".\n", [[identifiers objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
if (uninstallApplication([identifiers objectAtIndex:i])) {
if (quietInstall == 0)
printf("Successfully removed application \"%s\".\n", [[identifiers objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
} else {
if (quietInstall < 2)
printf("Failed to remove application \"%s\".\n", [[identifiers objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
}
} else {
if (quietInstall < 2)
printf("Application \"%s\" is not installed.\n", [[identifiers objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
[pool release];
return 0;
}
}
NSString *identifier = nil, *savePath = nil;
if ([op1 isEqualToString:@"-bq"] || [op1 isEqualToString:@"-qb"]) {
isBackup = YES;
quietInstall = 1;
if ([arguments count] == 5) {
identifier = [arguments objectAtIndex:2];
NSString *opOutput = [arguments objectAtIndex:3];
if (![opOutput isEqualToString:@"-o"]) {
printf("Invalid parameters.\n");
[pool release];
return 0;
}
savePath = [arguments objectAtIndex:4];
} else if ([arguments count] != 3) {
printf("Invalid parameters.\n");
[pool release];
return 0;
} else
identifier = [arguments objectAtIndex:2];
}
if ([op1 isEqualToString:@"-bQ"] || [op1 isEqualToString:@"-Qb"]) {
isBackup = YES;
quietInstall = 2;
if ([arguments count] == 5) {
identifier = [arguments objectAtIndex:2];
NSString *opOutput = [arguments objectAtIndex:3];
if (![opOutput isEqualToString:@"-o"]) {
printf("Invalid parameters.\n");
[pool release];
return 0;
}
savePath = [arguments objectAtIndex:4];
} else if ([arguments count] != 3) {
printf("Invalid parameters.\n");
[pool release];
return 0;
} else
identifier = [arguments objectAtIndex:2];
}
if ([op1 isEqualToString:@"-Bq"] || [op1 isEqualToString:@"-qB"]) {
isBackupFull = YES;
quietInstall = 1;
if ([arguments count] == 5) {
identifier = [arguments objectAtIndex:2];
NSString *opOutput = [arguments objectAtIndex:3];
if (![opOutput isEqualToString:@"-o"]) {
printf("Invalid parameters.\n");
[pool release];
return 0;
}
savePath = [arguments objectAtIndex:4];
} else if ([arguments count] != 3) {
printf("Invalid parameters.\n");
[pool release];
return 0;
} else
identifier = [arguments objectAtIndex:2];
}
if ([op1 isEqualToString:@"-BQ"] || [op1 isEqualToString:@"-QB"]) {
isBackupFull = YES;
quietInstall = 2;
if ([arguments count] == 5) {
identifier = [arguments objectAtIndex:2];
NSString *opOutput = [arguments objectAtIndex:3];
if (![opOutput isEqualToString:@"-o"]) {
printf("Invalid parameters.\n");
[pool release];
return 0;
}
savePath = [arguments objectAtIndex:4];
} else if ([arguments count] != 3) {
printf("Invalid parameters.\n");
[pool release];
return 0;
} else
identifier = [arguments objectAtIndex:2];
}
if ([op1 isEqualToString:@"-b"] || [op1 isEqualToString:@"-B"]) {
if ([op1 isEqualToString:@"-b"])
isBackup = YES;
else
isBackupFull = YES;
if ([op2 isEqualToString:@"-q"] || [op2 isEqualToString:@"-Q"]) {
quietInstall = [op2 isEqualToString:@"-q"] ? 1 : 2;
if ([arguments count] == 6) {
identifier = [arguments objectAtIndex:3];
NSString *opOutput = [arguments objectAtIndex:4];
if (![opOutput isEqualToString:@"-o"]) {
printf("Invalid parameters.\n");
[pool release];
return 0;
}
savePath = [arguments objectAtIndex:5];
} else if ([arguments count] != 4) {
printf("Invalid parameters.\n");
[pool release];
return 0;
} else
identifier = [arguments objectAtIndex:3];
} else {
if ([arguments count] == 5) {
identifier = [arguments objectAtIndex:2];
NSString *opOutput = [arguments objectAtIndex:3];
if (![opOutput isEqualToString:@"-o"]) {
printf("Invalid parameters.\n");
[pool release];
return 0;
}
savePath = [arguments objectAtIndex:4];
} else if ([arguments count] != 3) {
printf("Invalid parameters.\n");
[pool release];
return 0;
} else
identifier = [arguments objectAtIndex:2];
}
}
if ([op2 isEqualToString:@"-b"] || [op2 isEqualToString:@"-B"]) {
if ([op1 isEqualToString:@"-q"] || [op1 isEqualToString:@"-Q"]) {
if ([op2 isEqualToString:@"-b"])
isBackup = YES;
else
isBackupFull = YES;
quietInstall = [op1 isEqualToString:@"-q"] ? 1 : 2;
if ([arguments count] == 6) {
identifier = [arguments objectAtIndex:3];
NSString *opOutput = [arguments objectAtIndex:4];
if (![opOutput isEqualToString:@"-o"]) {
printf("Invalid parameters.\n");
[pool release];
return 0;
}
savePath = [arguments objectAtIndex:5];
} else if ([arguments count] != 4) {
printf("Invalid parameters.\n");
[pool release];
return 0;
} else
identifier = [arguments objectAtIndex:3];
}
}
if (isBackup || isBackupFull) {
if ([identifier length] < 1) {
printf("You must specify an application identifier.\n");
[pool release];
return 0;
}
if (savePath) {
if (![savePath hasPrefix:@"/"])
savePath = [[fileMgr currentDirectoryPath] stringByAppendingPathComponent:savePath];
savePath = [savePath stringByStandardizingPath];;
}
if ([fileMgr fileExistsAtPath:savePath]) {
printf("%s already exists.\n", [savePath cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return IPA_FAILED;
}
NSDictionary *installedAppInfo = getInstalledAppInfo(identifier);
if (!installedAppInfo) {
if (quietInstall < 2)
printf("Application \"%s\" is not installed.\n", [identifier cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return IPA_FAILED;
} else
printf("Backing up application with identifier \"%s\"...\n", [identifier cStringUsingEncoding:NSUTF8StringEncoding]);
NSString *appDirPath = [installedAppInfo objectForKey:@"BUNDLE_PATH"];
NSString *appPath = [installedAppInfo objectForKey:@"APP_PATH"];
NSString *dataPath = [installedAppInfo objectForKey:@"DATA_PATH"];
NSString *appName = [installedAppInfo objectForKey:@"NAME"];
NSString *appDisplayName = [installedAppInfo objectForKey:@"DISPLAY_NAME"];
NSString *appVersion = [installedAppInfo objectForKey:@"VERSION"];
NSString *appShortVersion = [installedAppInfo objectForKey:@"SHORT_VERSION"];
if (!appDisplayName || [appDisplayName length] < 1)
appDisplayName = appName;
if (!appShortVersion || [appShortVersion length] < 1)
appShortVersion = appVersion;
BOOL isDirectory;
if (![fileMgr fileExistsAtPath:appDirPath isDirectory:&isDirectory]) {
if (quietInstall < 2)
printf("Cannot find %s.\n", [appDirPath cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return IPA_FAILED;
}
if (!isDirectory) {
if (quietInstall < 2)
printf("%s is not a directory.\n", [appDirPath cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return IPA_FAILED;
}
if (![fileMgr fileExistsAtPath:appPath isDirectory:&isDirectory]) {
if (quietInstall < 2)
printf("Cannot find %s.\n", [appPath cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return IPA_FAILED;
}
if (!isDirectory) {
if (quietInstall < 2)
printf("%s is not a directory.\n", [appPath cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return IPA_FAILED;
}
if (isBackupFull) {
if (![fileMgr fileExistsAtPath:dataPath isDirectory:&isDirectory]) {
if (quietInstall < 2)
printf("Cannot find %s.\n", [dataPath cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return IPA_FAILED;
}
if (!isDirectory) {
if (quietInstall < 2)
printf("%s is not a directory.\n", [dataPath cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return IPA_FAILED;
}
}
//Clean before
NSArray *filesInTemp = [fileMgr contentsOfDirectoryAtPath:NSTemporaryDirectory() error:nil];
for (NSString *file in filesInTemp) {
file = [NSTemporaryDirectory() stringByAppendingPathComponent:[file lastPathComponent]];
if ([[file lastPathComponent] hasPrefix:@"com.autopear.ipainstaller."] && ![fileMgr removeItemAtPath:file error:nil]) {
if (quietInstall < 2)
printf("Failed to delete %s.\n", [file cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
//Create temp path
NSString *workPath = nil;
while (YES) {
workPath = [NSString stringWithFormat:@"com.autopear.ipainstaller.%@", randomStringInLength(6)];
workPath = [NSTemporaryDirectory() stringByAppendingPathComponent:workPath];
if (![fileMgr fileExistsAtPath:workPath])
break;
}
if(![fileMgr createDirectoryAtPath:workPath withIntermediateDirectories:YES attributes:nil error:NULL] ) {
if (quietInstall < 2)
printf("Failed to create workspace.\n");
[pool release];
return IPA_FAILED;
}
ZipArchive *ipaArchive = [[ZipArchive alloc] init];
// APPEND_STATUS_ADDINZIP = 2
if (![ipaArchive openZipFile2:[workPath stringByAppendingPathComponent:@"temp.zip"] withZipModel:APPEND_STATUS_ADDINZIP]) {
[ipaArchive release];
if (quietInstall < 2)
printf("Failed to create IPA file.\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
[pool release];
return IPA_FAILED;
}
if (![ipaArchive addDirectoryToZip:appPath toPathInZip:[NSString stringWithFormat:@"Payload/%@/", [appPath lastPathComponent]]]) {
if (quietInstall < 2)
printf("Failed to create ipa file.\n");
[ipaArchive release];
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
[pool release];
return IPA_FAILED;
}
if ([fileMgr fileExistsAtPath:[appDirPath stringByAppendingPathComponent:@"iTunesArtwork"]])
[ipaArchive addFileToZip:[appDirPath stringByAppendingPathComponent:@"iTunesArtwork"] newname:@"iTunesArtwork"];
if ([fileMgr fileExistsAtPath:[appDirPath stringByAppendingPathComponent:@"iTunesMetadata.plist"]])
[ipaArchive addFileToZip:[appDirPath stringByAppendingPathComponent:@"iTunesMetadata.plist"] newname:@"iTunesMetadata.plist"];
if (isBackupFull) {
if (quietInstall == 0)
printf("Backing up application data...\n");
NSArray *dataContents = [fileMgr contentsOfDirectoryAtPath:dataPath error:nil];
for (NSString *file in dataContents) {
if ([file hasSuffix:@".app"] ||
[file isEqualToString:@".com.apple.mobile_container_manager.metadata.plist"] ||
[file isEqualToString:@".com.apple.mobileinstallation.placeholder"] ||
[file isEqualToString:@".GlobalPreferences.plist"] ||
[file isEqualToString:@"com.apple.PeoplePicker.plist"] ||
[file isEqualToString:@"iTunesArtwork"] ||
[file isEqualToString:@"iTunesMetadata.plist"])
continue;
if ([file isEqualToString:@"Library"]){
BOOL globalMoved = NO;
if ([fileMgr moveItemAtPath:[dataPath stringByAppendingPathComponent:@"Library/Preferences/.GlobalPreferences.plist"] toPath:[dataPath stringByAppendingPathComponent:@".GlobalPreferences.plist"] error:nil])
globalMoved = YES;
BOOL pickerMoved = NO;
if ([fileMgr moveItemAtPath:[dataPath stringByAppendingPathComponent:@"Library/Preferences/com.apple.PeoplePicker.plist"] toPath:[dataPath stringByAppendingPathComponent:@"com.apple.PeoplePicker.plist"] error:nil])
pickerMoved = YES;
[ipaArchive addDirectoryToZip:[dataPath stringByAppendingPathComponent:@"Library"] toPathInZip:@"Container/Library/"];
if (globalMoved)
[fileMgr moveItemAtPath:[dataPath stringByAppendingPathComponent:@".GlobalPreferences.plist"] toPath:[dataPath stringByAppendingPathComponent:@"Library/Preferences/.GlobalPreferences.plist"] error:nil];
if (pickerMoved)
[fileMgr moveItemAtPath:[dataPath stringByAppendingPathComponent:@"com.apple.PeoplePicker.plist"] toPath:[dataPath stringByAppendingPathComponent:@"Library/Preferences/com.apple.PeoplePicker.plist"] error:nil];
} else {
NSString *sourcePath = [dataPath stringByAppendingPathComponent:file];
BOOL isDir;
if ([fileMgr fileExistsAtPath:sourcePath isDirectory:&isDir] && isDir)
[ipaArchive addDirectoryToZip:sourcePath toPathInZip:[NSString stringWithFormat:@"Container/%@/", file]];
else
[ipaArchive addFileToZip:sourcePath newname:[NSString stringWithFormat:@"Container/%@/", file]];
}
}
}
[ipaArchive release];
if (savePath) {
NSString *saveDir = [savePath stringByDeletingLastPathComponent];
BOOL isDirectory;
if ([fileMgr fileExistsAtPath:saveDir isDirectory:&isDirectory]) {
if (!isDirectory) {
if (quietInstall < 2)
printf("%s is not a directory.\n", [saveDir cStringUsingEncoding:NSUTF8StringEncoding]);
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
[pool release];
return IPA_FAILED;
}
} else {
if(![fileMgr createDirectoryAtPath:saveDir withIntermediateDirectories:YES attributes:nil error:NULL] ) {
if (quietInstall < 2)
printf("Failed to create directory %s.\n", [saveDir cStringUsingEncoding:NSUTF8StringEncoding]);
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
[pool release];
return IPA_FAILED;
}
//Set root folder's attributes
NSDictionary *directoryAttributes = [fileMgr attributesOfItemAtPath:saveDir error:nil];
NSMutableDictionary *defaultDirectoryAttributes = [NSMutableDictionary dictionaryWithCapacity:[directoryAttributes count]];
[defaultDirectoryAttributes setDictionary:directoryAttributes];
[defaultDirectoryAttributes setObject:[NSNumber numberWithInt:501] forKey:NSFileOwnerAccountID];
[defaultDirectoryAttributes setObject:@"mobile" forKey:NSFileOwnerAccountName];
[defaultDirectoryAttributes setObject:[NSNumber numberWithInt:501] forKey:NSFileGroupOwnerAccountID];
[defaultDirectoryAttributes setObject:@"mobile" forKey:NSFileGroupOwnerAccountName];
[defaultDirectoryAttributes setObject:[NSNumber numberWithShort:0755] forKey:NSFilePosixPermissions];
[fileMgr setAttributes:defaultDirectoryAttributes ofItemAtPath:saveDir error:nil];
}
//Move
if (![fileMgr moveItemAtPath:[workPath stringByAppendingPathComponent:@"temp.zip"] toPath:savePath error:nil]) {
if (quietInstall < 2)
printf("Failed to create IPA file.\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
[pool release];
return IPA_FAILED;
}
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
if (quietInstall == 0)
printf("The application has been backed up as %s.\n", [savePath cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return 0;
} else {
NSString *nameBase;
if (isBackup)
nameBase = [NSString stringWithFormat:@"%@ (%@) v%@", getBestString(appName, appDisplayName), identifier, getBestString(appVersion, appShortVersion)];
else
nameBase = [NSString stringWithFormat:@"%@ (%@) v%@ (Full)", getBestString(appName, appDisplayName), identifier, getBestString(appVersion, appShortVersion)];
NSString *saveDir = @"/private/var/mobile/Documents";
if (![fileMgr fileExistsAtPath:saveDir]) {
if(![fileMgr createDirectoryAtPath:saveDir withIntermediateDirectories:YES attributes:nil error:NULL] ) {
if (quietInstall < 2)
printf("Failed to create /var/mobile/Documents.\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
[pool release];
return IPA_FAILED;
}
//Set root folder's attributes
NSDictionary *directoryAttributes = [fileMgr attributesOfItemAtPath:saveDir error:nil];
NSMutableDictionary *defaultDirectoryAttributes = [NSMutableDictionary dictionaryWithCapacity:[directoryAttributes count]];
[defaultDirectoryAttributes setDictionary:directoryAttributes];
[defaultDirectoryAttributes setObject:[NSNumber numberWithInt:501] forKey:NSFileOwnerAccountID];
[defaultDirectoryAttributes setObject:@"mobile" forKey:NSFileOwnerAccountName];
[defaultDirectoryAttributes setObject:[NSNumber numberWithInt:501] forKey:NSFileGroupOwnerAccountID];
[defaultDirectoryAttributes setObject:@"mobile" forKey:NSFileGroupOwnerAccountName];
[defaultDirectoryAttributes setObject:[NSNumber numberWithShort:0755] forKey:NSFilePosixPermissions];
[fileMgr setAttributes:defaultDirectoryAttributes ofItemAtPath:saveDir error:nil];
}
//Move
NSString *ipaPath = [[NSString stringWithFormat:@"%@/%@.ipa", saveDir, nameBase] stringByStandardizingPath];
if ([fileMgr fileExistsAtPath:ipaPath]) {
for (int i=1; ; i++) {
ipaPath = [NSString stringWithFormat:@"%@/%@ %d.ipa", saveDir, nameBase, i];
if (![fileMgr fileExistsAtPath:ipaPath])
break;
}
}
if (![fileMgr moveItemAtPath:[workPath stringByAppendingPathComponent:@"temp.zip"] toPath:ipaPath error:nil]) {
if (quietInstall < 2)
printf("Failed to create IPA file.\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
[pool release];
return IPA_FAILED;
}
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
if (quietInstall == 0)
printf("The application has been backed up as %s.\n", [ipaPath cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return 0;
}
}
}
NSMutableArray *ipaFiles = [NSMutableArray arrayWithCapacity:0];
NSMutableArray *filesNotFound = [NSMutableArray arrayWithCapacity:0];
BOOL noParameters = NO;
BOOL showHelp = NO;
BOOL showAbout = NO;
for (unsigned int i=1; i<[arguments count]; i++) {
NSString *arg = [arguments objectAtIndex:i];
if ([arg hasPrefix:@"-" ]) {
if ([arg length] < 2 || noParameters) {
printf("Invalid parameters.\n");
[pool release];
return IPA_FAILED;
}
for (unsigned int j=1; j<[arg length]; j++) {
NSString *p = [arg substringWithRange:NSMakeRange(j, 1)];
if ([p isEqualToString:@"u"])
isUninstall = YES;
else if ([p isEqualToString:@"l"])
isListing = YES;
else if ([p isEqualToString:@"b"]) {
if (isBackupFull) {
printf("Parameter b and B cannot be specified at the same time.\n");
[pool release];
return IPA_FAILED;
}
isBackup = YES;
} else if ([p isEqualToString:@"B"]) {
if (isBackup) {
printf("Parameter -b and -B cannot be specified at the same time.\n");
[pool release];
return IPA_FAILED;
}
isBackupFull = YES;
} else if ([p isEqualToString:@"a"])
showAbout = YES;
else if ([p isEqualToString:@"c"])
cleanInstall = YES;
else if ([p isEqualToString:@"d"])
deleteFile = YES;
else if ([p isEqualToString:@"i"] || [p isEqualToString:@"I"])
isGetInfo = YES;
else if ([p isEqualToString:@"f"])
forceInstall = YES;
else if ([p isEqualToString:@"h"])
showHelp = YES;
else if ([p isEqualToString:@"n"])
notRestore = YES;
else if ([p isEqualToString:@"q"]) {
if (quietInstall != 0) {
printf("Parameter -q and -Q cannot be specified at the same time.\n");
[pool release];
return IPA_FAILED;
}
quietInstall = 1;
} else if ([p isEqualToString:@"Q"]) {
if (quietInstall != 0) {
printf("Parameter -q and -Q cannot be specified at the same time.\n");
[pool release];
return IPA_FAILED;
}
quietInstall = 2;
} else if ([p isEqualToString:@"r"])
removeMetadata = YES;
else if ([p isEqualToString:@"o"]) {
if (!isBackup && !isBackupFull) {
printf("You must specify -b or -B before -o.\n");
[pool release];
return IPA_FAILED;
}
} else {
printf("Invalid parameter '%s'.\n", [p cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return IPA_FAILED;
}
}
} else {
if (!isBackup && !isBackupFull) {
noParameters = YES;
NSURL *url = [NSURL fileURLWithPath:arg isDirectory:NO];
BOOL isDirectory;
if (url && [fileMgr fileExistsAtPath:[[url absoluteURL] path] isDirectory:&isDirectory]) {
if (isDirectory)
[filesNotFound addObject:arg];
else
[ipaFiles addObject:[[url absoluteURL] path]]; //File exists
} else
[filesNotFound addObject:arg];
}
}
}
if (isListing) {
getInstalledApplications();
if ([arguments count] != 2) {
printf("Invalid parameters.\n");
[pool release];
return IPA_FAILED;
} else {
NSArray * identifiers = getInstalledApplications();
for (unsigned int i=0; i<[identifiers count]; i++)
printf("%s\n", [(NSString *)[identifiers objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return 0;
}
}
if ((showAbout && showHelp) ||
((showAbout || showHelp) &&
(cleanInstall ||
deleteFile ||
forceInstall ||
notRestore ||
quietInstall != 0 ||
removeMetadata ||
([ipaFiles count] + [filesNotFound count] > 0)))) {
printf("Invalid parameters.\n");
[pool release];
return IPA_FAILED;
}
if (showHelp) {
printf("%s\n", [helpString cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return 0;
}
if (showAbout) {
printf("%s\n", [aboutString cStringUsingEncoding:NSUTF8StringEncoding]);
[pool release];
return 0;
}
for (unsigned int i=0; i<[filesNotFound count]; i++) {
if (quietInstall < 2)
printf("File not found at path: %s.\n", [[filesNotFound objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
}
if ([ipaFiles count] < 1) {
if (quietInstall < 2)
printf("Please specify any IPA file(s) to install.\n");
[pool release];
return IPA_FAILED;
}
if (cleanInstall)
notRestore = YES;
if (quietInstall == 0 && cleanInstall)
printf("Clean installation enabled.\n");
if (quietInstall == 0 && forceInstall)
printf("Force installation enabled.\n");
if (quietInstall == 0 && notRestore)
printf("Will not restore any saved documents and other resources.\n");
if (quietInstall == 0 && removeMetadata)
printf("iTunesMetadata.plist will be removed after installation.\n");
if (quietInstall == 0 && deleteFile) {
if ([ipaFiles count] == 1)
printf("%s will be deleted after installation.\n", [[[ipaFiles objectAtIndex:0] lastPathComponent] cStringUsingEncoding:NSUTF8StringEncoding]);
else
printf("IPA files will be deleted after installation.\n");
}
if (quietInstall == 0 && (cleanInstall || forceInstall || notRestore || removeMetadata || deleteFile))
printf("\n");
NSArray *filesInTemp = [fileMgr contentsOfDirectoryAtPath:NSTemporaryDirectory() error:nil];
for (NSString *file in filesInTemp) {
file = [NSTemporaryDirectory() stringByAppendingPathComponent:[file lastPathComponent]];
if ([[file lastPathComponent] hasPrefix:@"com.autopear.ipainstaller."] && ![fileMgr removeItemAtPath:file error:nil]) {
if (quietInstall < 2)
printf("Failed to delete %s.\n", [file cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
NSString *workPath = nil;
while (YES) {
workPath = [NSString stringWithFormat:@"com.autopear.ipainstaller.%@", randomStringInLength(6)];
workPath = [NSTemporaryDirectory() stringByAppendingPathComponent:workPath];
if (![fileMgr fileExistsAtPath:workPath])
break;
}
if(![fileMgr createDirectoryAtPath:workPath withIntermediateDirectories:YES attributes:nil error:NULL]) {
if (quietInstall < 2)
printf("Failed to create workspace.\n");
[pool release];
return IPA_FAILED;
}
NSString *installPath = [workPath stringByAppendingPathComponent:@"tmp.install.ipa"];
int successfulInstalls = 0;
for (unsigned i=0; i<[ipaFiles count]; i++) {
//Before installation, make a clean workspace
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to create workspace.\n");
[pool release];
return IPA_FAILED;
}
NSString *ipa = [ipaFiles objectAtIndex:i];
if (quietInstall == 0)
printf("Analyzing %s...\n", [[ipa lastPathComponent] cStringUsingEncoding:NSUTF8StringEncoding]);
BOOL isValidIPA = YES;
BOOL hasContainer = NO;
NSString *pathInfoPlist = nil;
NSString *infoPath = nil;
while (YES) {
pathInfoPlist = [workPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.Info.plist", randomStringInLength(6)]];
if (![fileMgr fileExistsAtPath:pathInfoPlist])
break;
}
ZipArchive *ipaArchive = [[ZipArchive alloc] init];
if ([ipaArchive unzipOpenFile:[ipaFiles objectAtIndex:i]]) {
NSMutableArray *array = [ipaArchive getZipFileContents];
NSMutableArray *infoStrings = [NSMutableArray arrayWithCapacity:0];
NSString *appPathName = nil;
int cnt = 0;
for (unsigned int j=0; j<[array count]; j++) {
NSString *name = [array objectAtIndex:j];
NSArray *components = [name pathComponents];
if ([components count] > 1 && [[components objectAtIndex:0] isEqualToString:@"Container"])
hasContainer = YES;
else {
//Extract Info.plist
if ([components count] == 3 &&
[[components objectAtIndex:0] isEqualToString:@"Payload"] &&
[[components objectAtIndex:1] hasSuffix:@".app"] &&
[[components objectAtIndex:2] isEqualToString:@"Info.plist"]) {
appPathName = [@"Payload" stringByAppendingPathComponent:[components objectAtIndex:1]];
infoPath = name;
cnt++;
}
//Extract InfoPlist.strings if available
if ([components count] == 4 &&
[[components objectAtIndex:0] isEqualToString:@"Payload"] &&
[[components objectAtIndex:1] hasSuffix:@".app"] &&
[[components objectAtIndex:2] hasSuffix:@".lproj"] &&
[[components objectAtIndex:3] isEqualToString:@"InfoPlist.strings"]) {
[infoStrings addObject:[components objectAtIndex:2]];
}
}
}
if (cnt != 1)
isValidIPA = NO;
if (isValidIPA) {
//Unzip Info.plist
[ipaArchive unzipFileWithName:infoPath toPath:pathInfoPlist overwrite:YES];
//Unzip all InfoPlist.strings
for (unsigned int j=0; j<[infoStrings count]; j++) {
NSString *lprojPath = [[workPath stringByAppendingPathComponent:@"localizations"] stringByAppendingPathComponent:[infoStrings objectAtIndex:j]];
if ([fileMgr createDirectoryAtPath:lprojPath withIntermediateDirectories:YES attributes:nil error:NULL]) {
//Unzip to this directory
[ipaArchive unzipFileWithName:[[appPathName stringByAppendingPathComponent:[infoStrings objectAtIndex:j]] stringByAppendingPathComponent:@"InfoPlist.strings"] toPath:[lprojPath stringByAppendingPathComponent:@"InfoPlist.strings"] overwrite:YES];
}
}
}
[ipaArchive unzipCloseFile];
} else
isValidIPA = NO;
[ipaArchive release];
if (!isValidIPA) {
if (quietInstall < 2)
printf("%s is not a valid IPA.%s", [[ipaFiles objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
continue;
}
NSString *appIdentifier = nil;
NSString *appDisplayName = nil;
NSString *appVersion = nil;
NSString *appShortVersion = nil;
NSString *minSysVersion = nil;
NSMutableArray *supportedDeives = nil;
id requiredCapabilities = nil;
NSMutableDictionary *infoDict = [NSMutableDictionary dictionaryWithContentsOfFile:pathInfoPlist];
if (infoDict) {
appIdentifier = [infoDict objectForKey:@"CFBundleIdentifier"];
appVersion = [infoDict objectForKey:@"CFBundleVersion"];
appShortVersion = [infoDict objectForKey:@"CFBundleShortVersionString"];
minSysVersion = [infoDict objectForKey:@"MinimumOSVersion"];
supportedDeives = [infoDict objectForKey:@"UIDeviceFamily"];
requiredCapabilities = [infoDict objectForKey:@"UIRequiredDeviceCapabilities"];
appDisplayName = [infoDict objectForKey:@"CFBundleDisplayName"] ? [infoDict objectForKey:@"CFBundleDisplayName"] : [infoDict objectForKey:@"CFBundleName"];
//Obtain localized display name
BOOL isDirectory;
if ([fileMgr fileExistsAtPath:[workPath stringByAppendingPathComponent:@"localizations"] isDirectory:&isDirectory]) {
if (isDirectory) {
NSBundle *localizedBundle = [NSBundle bundleWithPath:[workPath stringByAppendingPathComponent:@"localizations"]];
if ([localizedBundle localizedStringForKey:@"CFBundleDisplayName" value:nil table:@"InfoPlist"])
appDisplayName = [localizedBundle localizedStringForKey:@"CFBundleDisplayName" value:appDisplayName table:@"InfoPlist"];
else
appDisplayName = [localizedBundle localizedStringForKey:@"CFBundleName" value:appDisplayName table:@"InfoPlist"];
//Delete the directory
[fileMgr removeItemAtPath:[workPath stringByAppendingPathComponent:@"localizations"] error:nil];
}
}
} else {
if (quietInstall < 2)
printf("%s is not a valid IPA.%s", [[ipaFiles objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
continue;
}
if (!appIdentifier || !appDisplayName || !appVersion) {
if (quietInstall < 2)
printf("%s is not a valid IPA.%s", [[ipaFiles objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
continue;
}
//Make a copy of extracted Info.plist
NSString *pathOriginalInfoPlist = [NSString stringWithFormat:@"%@.original", pathInfoPlist];
if ([fileMgr fileExistsAtPath:pathOriginalInfoPlist]) {
if (![fileMgr removeItemAtPath:pathOriginalInfoPlist error:nil]) {
if (![fileMgr copyItemAtPath:pathInfoPlist toPath:pathOriginalInfoPlist error:nil]) {
//Force installation has to be disabled.
if (forceInstall && quietInstall < 2)
printf("Force installation has to be disabled.\n");
forceInstall = NO;
}
}
} else {
if (![fileMgr copyItemAtPath:pathInfoPlist toPath:pathOriginalInfoPlist error:nil]) {
//Force installation has to be disabled.
if (forceInstall && quietInstall < 2)
printf("Force installation has to be disabled.\n");
forceInstall = NO;
}
}
//Check installed stats
NSDictionary *installedAppDict = getInstalledAppInfo(appIdentifier);
BOOL appAlreadyInstalled = NO;
if (installedAppDict) {
appAlreadyInstalled = YES;
NSString *installedVerion = [installedAppDict objectForKey:@"VERSION"];
NSString *installedShortVersion = [installedAppDict objectForKey:@"SHORT_VERSION"];
if (installedShortVersion != nil && appShortVersion != nil) {
if (versionCompare(installedShortVersion, appShortVersion) == 1) {
//Skip to avoid overriding a new version
if (forceInstall) {
if (quietInstall == 0)
printf("%s (v%s) is already installed. Will force to downgrade.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [installedShortVersion cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
} else {
if (quietInstall < 2)
printf("%s (v%s) is already installed. You may use -f parameter to force downgrade.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [installedShortVersion cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
continue;
}
}
} else {
if (versionCompare(installedVerion, appVersion) == 1) {
//Skip to avoid overriding a new version
if (forceInstall) {
if (quietInstall == 0)
printf("%s (v%s) is already installed. Will force to downgrade.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [installedVerion cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
} else {
if (quietInstall < 2)
printf("%s (v%s) is already installed. You may use -f parameter to force downgrade.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [installedVerion cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
continue;
}
}
}
}
BOOL shouldUpdateInfoPlist = NO;
//Check device family
BOOL supportiPhone = NO;
BOOL supportiPad = NO;
BOOL supportAppleTV = NO;
if (!supportedDeives || [supportedDeives count] == 0) {
supportiPhone = YES;
supportiPad = YES;
supportAppleTV = YES;
} else {
for (unsigned int j=0; j<[supportedDeives count]; j++) {
int d =[[supportedDeives objectAtIndex:j] intValue];
if (d == 1) {
supportiPhone = YES;
supportiPad = YES;
}
if (d == 2)
supportiPad = YES;
if (d == 3)
supportAppleTV = YES;
}
}
NSString *supportedDeivesString = nil;
if (!supportiPhone && supportiPad && !supportAppleTV)
supportedDeivesString = @"iPad";
else if (!supportiPhone && !supportiPad && supportAppleTV)
supportedDeivesString = @"Apple TV";
else if (supportiPhone && supportiPad && !supportAppleTV)
supportedDeivesString = @"iPhone, iPod touch or iPad";
else if (supportiPhone && !supportiPad && supportAppleTV)
supportedDeivesString = @"iPhone, iPod touch or Apple TV";
else if (!supportiPhone && supportiPad && supportAppleTV)
supportedDeivesString = @"iPad or Apple TV";
else if (supportiPhone && !supportiPad && !supportAppleTV)
supportedDeivesString = @"iPhone or iPod touch"; //Should not reach here, normally support iPhone should support iPad too
else
supportedDeivesString = @"iPhone, iPod touch, iPad or Apple TV"; //Should not reach here
if ((DeviceModel == 1 && !supportiPhone) || //Not support iPhone / iPod touch
(DeviceModel == 2 && !supportiPad) || //Not support iPad
(DeviceModel == 3 && !supportAppleTV)) { //Not support Apple TV
//Device not supported
if (forceInstall) {
if (quietInstall == 0)
printf("%s (v%s) requires %s while your device is %s.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding], [supportedDeivesString cStringUsingEncoding:NSUTF8StringEncoding], [deviceString cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) || forceInstall ? "\n" : "\n\n");
[supportedDeives addObject:[NSNumber numberWithInt:DeviceModel]];
[infoDict setObject:[supportedDeives sortedArrayUsingSelector:@selector(compare:)] forKey:@"UIDeviceFamily"];
shouldUpdateInfoPlist = YES;
} else {
if (quietInstall < 2)
printf("%s (v%s) requires %s while your device is %s.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding], [supportedDeivesString cStringUsingEncoding:NSUTF8StringEncoding], [deviceString cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) || forceInstall ? "\n" : "\n\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
continue;
}
}
//Check minimun system requirement
if (minSysVersion && versionCompare(minSysVersion, SystemVersion) == 1) {
//System version is less than the min required version
if (forceInstall) {
if (quietInstall == 0)
printf("%s (v%s) requires iOS %s while your system is %s.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding], [minSysVersion cStringUsingEncoding:NSUTF8StringEncoding], [SystemVersion cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) || forceInstall ? "\n" : "\n\n");
[infoDict setObject:SystemVersion forKey:@"MinimumOSVersion"];
shouldUpdateInfoPlist = YES;
} else {
if (quietInstall < 2)
printf("%s (v%s) requires iOS %s while your system is %s.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding], [minSysVersion cStringUsingEncoding:NSUTF8StringEncoding], [SystemVersion cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) || forceInstall ? "\n" : "\n\n");
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
continue;
}
}
//Chekc capabilities
if (requiredCapabilities) {
BOOL isCapable = YES;
//requiredCapabilities is NSArray, contains only strings
if ([requiredCapabilities isKindOfClass:[NSArray class]]) {
NSMutableArray *newCapabilities = [NSMutableArray arrayWithCapacity:0];
for (unsigned int j=0; j<[(NSArray *)requiredCapabilities count]; j++) {
NSString *capability = [(NSArray *)requiredCapabilities objectAtIndex:j];
if ([[UIDevice currentDevice] supportsCapability:capability])
[newCapabilities addObject:capability];
else {
isCapable = NO;
if (forceInstall) {
if (quietInstall == 0)
printf("Your device does not support %s capability.\n", [capability cStringUsingEncoding:NSUTF8StringEncoding]);
shouldUpdateInfoPlist = YES;
} else {
if (quietInstall < 2)
printf("Your device does not support %s capability.\n", [capability cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
}
if (!isCapable) {
if (forceInstall)
[infoDict setObject:[newCapabilities sortedArrayUsingSelector:@selector(compare:)] forKey:@"UIRequiredDeviceCapabilities"];
else {
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
if (i != [ipaFiles count] - 1) //Not the last output
printf("\n");
continue;
}
}
} else if ([requiredCapabilities isKindOfClass:[NSDictionary class]]) {
//requiredCapabilities is NSDictionary, contains only key-object pairs
NSMutableDictionary *newCapabilities = [NSMutableDictionary dictionaryWithCapacity:0];
for (NSString *capabilityKey in [(NSDictionary *)requiredCapabilities allKeys]) {
BOOL capabilityValue = [[(NSDictionary *)requiredCapabilities objectForKey:capabilityKey] boolValue];
//Only boolean value
if (capabilityValue == [[UIDevice currentDevice] supportsCapability:capabilityKey])
[newCapabilities setObject:[NSNumber numberWithBool:!capabilityValue] forKey:capabilityKey];
else {
isCapable = NO;
if (forceInstall) {
if (quietInstall == 0) {
if (capabilityValue) //Device does not support
printf("Your device does not support %s capability.\n", [capabilityKey cStringUsingEncoding:NSUTF8StringEncoding]);
else //Device support but IPA requires to be false
printf("Your device conflicts with %s capability.\n", [capabilityKey cStringUsingEncoding:NSUTF8StringEncoding]);
}
shouldUpdateInfoPlist = YES;
} else {
if (quietInstall < 2) {
if (capabilityValue) //Device does not support
printf("Your device does not support %s capability.\n", [capabilityKey cStringUsingEncoding:NSUTF8StringEncoding]);
else //Device support but IPA requires to be false
printf("Your device conflicts with %s capability.\n", [capabilityKey cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
}
}
if (!isCapable) {
if (forceInstall)
[infoDict setObject:newCapabilities forKey:@"UIRequiredDeviceCapabilities"];
else {
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
if (i != [ipaFiles count] - 1) //Not the last output
printf("\n");
continue;
}
}
}
}
if (shouldUpdateInfoPlist && ![infoDict writeToFile:pathInfoPlist atomically:YES]) {
if (quietInstall < 2)
printf("Failed to use force installation mode, %s (v%s) will not be installed.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
continue;
}
//Copy file to install
if ([fileMgr fileExistsAtPath:installPath]) {
if (![fileMgr removeItemAtPath:installPath error:nil]) {
if (quietInstall < 2)
printf("Failed to delete %s.\n", [installPath cStringUsingEncoding:NSUTF8StringEncoding]);
if (![fileMgr removeItemAtPath:workPath error:nil]) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
[pool release];
return IPA_FAILED;
}
}
if (![fileMgr copyItemAtPath:ipa toPath:installPath error:nil]) {
if (quietInstall < 2)
printf("Failed to create temporaty files.\n");
if (![fileMgr removeItemAtPath:workPath error:nil] && quietInstall < 2)
printf("Failed to clean caches.\n");
[pool release];
return IPA_FAILED;
}
//Modify ipa to force install
if (shouldUpdateInfoPlist) {
BOOL shouldContinue = NO;
ZipArchive *tmpArchive = [[ZipArchive alloc] init];
// APPEND_STATUS_ADDINZIP = 2
if ([tmpArchive openZipFile2:installPath withZipModel:APPEND_STATUS_ADDINZIP] && ![tmpArchive addFileToZip:pathInfoPlist newname:infoPath]) {
if (quietInstall < 2)
printf("Failed to use force installation mode, %s (v%s) will not be installed.%s", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
//Delete copied file
[fileMgr removeItemAtPath:installPath error:nil];
shouldContinue = YES;
}
[tmpArchive release];
//Remove extracted Info.plist
[fileMgr removeItemAtPath:pathInfoPlist error:nil];
if (shouldContinue) {
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
continue;
}
}
if (quietInstall == 0)
printf("%snstalling %s (v%s)...\n", shouldUpdateInfoPlist ? "Force i" : "I", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding]);
//Set permission before installation
setPermissionsForPath(workPath);
int ret = installApp(installPath, appIdentifier);
if (ret == 0) {
//Get installation path
NSDictionary *installedAppDict = getInstalledAppInfo(appIdentifier);
if (installedAppDict) {
NSString *installedVerion = [installedAppDict objectForKey:@"VERSION"];
NSString *installedShortVersion = [installedAppDict objectForKey:@"SHORT_VERSION"];
NSString *installedAppLocation = [installedAppDict objectForKey:@"BUNDLE_PATH"];
NSString *installedDataLocation = [installedAppDict objectForKey:@"DATA_PATH"];
NSString *appDirPath = [installedAppDict objectForKey:@"APP_PATH"];
BOOL appInstalled = YES;
if (appInstalled && versionCompare(installedVerion, appVersion) != 0)
appInstalled = NO;
if (appInstalled && versionCompare(installedShortVersion, appShortVersion) != 0)
appInstalled = NO;
if (appInstalled) {
//Recover the original Info.plist in force installation
if (shouldUpdateInfoPlist) {
NSString *pathInstalledInfoPlist = [NSString stringWithFormat:@"%@/%@/Info.plist", installedAppLocation, [[infoPath pathComponents] objectAtIndex:1]];
BOOL isDirectory;
if ([fileMgr fileExistsAtPath:pathInstalledInfoPlist isDirectory:&isDirectory]) {
if (!isDirectory) {
if ([fileMgr removeItemAtPath:pathInstalledInfoPlist error:nil]) {
if ([fileMgr moveItemAtPath:pathOriginalInfoPlist toPath:pathInstalledInfoPlist error:nil]) {
if ([fileMgr fileExistsAtPath:pathOriginalInfoPlist])
[fileMgr removeItemAtPath:pathOriginalInfoPlist error:nil];
}
}
}
}
}
successfulInstalls++;
if (quietInstall == 0)
printf("%snstalled %s (v%s) successfully%s.\n", shouldUpdateInfoPlist ? "Force i" : "I", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding], shouldUpdateInfoPlist ? ", but it may not work properly" : "");
BOOL tempEnableClean = NO;
if (!cleanInstall && hasContainer && !notRestore) {
tempEnableClean = YES;
cleanInstall = YES;
}
//Clear documents, etc.
if (appAlreadyInstalled && cleanInstall) {
if (quietInstall == 0)
printf("Cleaning old contents of %s...\n", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding]);
BOOL allContentsCleaned = YES;
NSArray *dataContents = [fileMgr contentsOfDirectoryAtPath:installedDataLocation error:nil];
for (NSString *file in dataContents) {
if ([file hasSuffix:@".app"] ||
[file isEqualToString:@".com.apple.mobile_container_manager.metadata.plist"] ||
[file isEqualToString:@".com.apple.mobileinstallation.placeholder"] ||
[file isEqualToString:@"iTunesArtwork"] ||
[file isEqualToString:@"iTunesMetadata.plist"])
continue;
if ([file isEqualToString:@"Library"]){
NSString *dirLibrary = [installedDataLocation stringByAppendingPathComponent:@"Library"];
NSString *dirPreferences = [dirLibrary stringByAppendingPathComponent:@"Preferences"];
NSString *dirCaches = [dirLibrary stringByAppendingPathComponent:@"Caches"];
NSArray *dirContents = [fileMgr contentsOfDirectoryAtPath:dirLibrary error:nil];
for (int unsigned j=0; j<[dirContents count]; j++) {
NSString *fileName = [dirContents objectAtIndex:j];
if ([fileName isEqualToString:@"Preferences"]) {
NSArray *preferencesContents = [fileMgr contentsOfDirectoryAtPath:dirPreferences error:nil];
for (unsigned int k=0; k<[preferencesContents count]; k++) {
NSString *preferenceFile = [preferencesContents objectAtIndex:k];
if (![preferenceFile isEqualToString:@".GlobalPreferences.plist"] && ![preferenceFile isEqualToString:@"com.apple.PeoplePicker.plist"]) {
if (![fileMgr removeItemAtPath:[dirPreferences stringByAppendingPathComponent:preferenceFile] error:nil])
allContentsCleaned = NO;
}
}
} else if ([fileName isEqualToString:@"Caches"]) {
NSArray *cachesContents = [fileMgr contentsOfDirectoryAtPath:dirCaches error:nil];
for (unsigned int k=0; k<[cachesContents count]; k++) {
if (![fileMgr removeItemAtPath:[dirCaches stringByAppendingPathComponent:[cachesContents objectAtIndex:k]] error:nil])
allContentsCleaned = NO;
}
} else {
if (![fileMgr removeItemAtPath:[dirLibrary stringByAppendingPathComponent:fileName] error:nil])
allContentsCleaned = NO;
}
}
} else {
NSString *sourcePath = [installedDataLocation stringByAppendingPathComponent:file];
BOOL isDir;
if ([fileMgr fileExistsAtPath:sourcePath isDirectory:&isDir] && isDir) {
NSArray *dirContents = [fileMgr contentsOfDirectoryAtPath:sourcePath error:nil];
for (int unsigned j=0; j<[dirContents count]; j++) {
if (![fileMgr removeItemAtPath:[sourcePath stringByAppendingPathComponent:[dirContents objectAtIndex:j]] error:nil])
allContentsCleaned = NO;
}
} else {
if (![fileMgr removeItemAtPath:sourcePath error:nil])
allContentsCleaned = NO;
}
}
}
if (!allContentsCleaned) {
if (quietInstall < 2)
printf("Failed to clean old contents of %s.\n", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
if (tempEnableClean)
cleanInstall = NO;
//Recover documents
if (!cleanInstall && hasContainer && !notRestore) {
//The tmp ipa file is already deleted.
ipaArchive = [[ZipArchive alloc] init];
if ([ipaArchive unzipOpenFile:[ipaFiles objectAtIndex:i]]) {
if ([ipaArchive unzipFileWithName:@"Container" toPath:[workPath stringByAppendingPathComponent:@"Container"] overwrite:YES]) {
NSString *containerPath = [workPath stringByAppendingPathComponent:@"Container"];
NSArray *containerContents = [fileMgr contentsOfDirectoryAtPath:containerPath error:nil];
if ([containerContents count] > 0) {
BOOL allSuccessfull = YES;
for (unsigned int j=0; j<[containerContents count]; j++) {
NSString *dirName = [containerContents objectAtIndex:j];
if ([dirName isEqualToString:@"Library"]) {
NSString *containerLibraryPath = [containerPath stringByAppendingPathComponent:dirName];
NSArray *containerLibraryContents = [fileMgr contentsOfDirectoryAtPath:containerLibraryPath error:nil];
for (unsigned int k=0; k<[containerLibraryContents count]; k++) {
NSString *dirLibraryName = [containerLibraryContents objectAtIndex:k];
if ([dirLibraryName isEqualToString:@"Caches"]) {
NSString *dirCachePath = [containerLibraryPath stringByAppendingPathComponent:dirLibraryName];
NSArray *containerCachesContents = [fileMgr contentsOfDirectoryAtPath:dirCachePath error:nil];
for (unsigned int m=0; m<[containerCachesContents count]; m++) {
if (![fileMgr moveItemAtPath:[dirCachePath stringByAppendingPathComponent:[containerCachesContents objectAtIndex:m]] toPath:[[[installedDataLocation stringByAppendingPathComponent:dirName] stringByAppendingPathComponent:dirLibraryName] stringByAppendingPathComponent:[containerCachesContents objectAtIndex:m]] error:nil])
allSuccessfull = NO;
}
} else if ([dirLibraryName isEqualToString:@"Preferences"]) {
NSString *dirPreferencesPath = [containerLibraryPath stringByAppendingPathComponent:dirLibraryName];
NSArray *containerPreferencesContents = [fileMgr contentsOfDirectoryAtPath:dirPreferencesPath error:nil];
for (unsigned int m=0; m<[containerPreferencesContents count]; m++) {
NSString *preferencesFileName = [containerPreferencesContents objectAtIndex:m];
if (![preferencesFileName isEqualToString:@".GlobalPreferences.plist"] && ![preferencesFileName isEqualToString:@"com.apple.PeoplePicker.plist"]) {
if (![fileMgr moveItemAtPath:[dirPreferencesPath stringByAppendingPathComponent:preferencesFileName] toPath:[[[installedDataLocation stringByAppendingPathComponent:dirName] stringByAppendingPathComponent:dirLibraryName] stringByAppendingPathComponent:preferencesFileName] error:nil])
allSuccessfull = NO;
}
}
} else {
if (![fileMgr moveItemAtPath:[containerLibraryPath stringByAppendingPathComponent:dirLibraryName] toPath:[[installedDataLocation stringByAppendingPathComponent:dirName] stringByAppendingPathComponent:dirLibraryName] error:nil])
allSuccessfull = NO;
}
}
} else {
NSString *containerSourcePath = [containerPath stringByAppendingPathComponent:dirName];
NSString *destPath = [installedDataLocation stringByAppendingPathComponent:dirName];
if ([fileMgr fileExistsAtPath:destPath]) {
if ([fileMgr removeItemAtPath:destPath error:nil]) {
if (![fileMgr moveItemAtPath:containerSourcePath toPath:destPath error:nil])
allSuccessfull = NO;
} else
allSuccessfull = NO;
} else {
if (![fileMgr moveItemAtPath:containerSourcePath toPath:destPath error:nil])
allSuccessfull = NO;
}
}
}
if (!allSuccessfull) {
if (quietInstall < 2)
printf("Cannot restore all saved documents and other resources.\n");
}
}
}
[ipaArchive unzipCloseFile];
}
[ipaArchive release];
}
//Remove metadata
BOOL isDirectory;
if (removeMetadata && [fileMgr fileExistsAtPath:[installedAppLocation stringByAppendingPathComponent:@"iTunesMetadata.plist"] isDirectory:&isDirectory]) {
if (!isDirectory) {
if (quietInstall == 0)
printf("Removing iTunesMetadata.plist for %s...\n", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding]);
if (![fileMgr removeItemAtPath:[installedAppLocation stringByAppendingPathComponent:@"iTunesMetadata.plist"] error:nil]) {
if (quietInstall < 2)
printf("Failed to remove %s.\n", [[installedAppLocation stringByAppendingPathComponent:@"iTunesMetadata.plist"] cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
}
//Set overall permission
if (kCFCoreFoundationVersionNumber < 793.00)
setPermissionsForPath(installedAppLocation);
else
setPermissionsForPath(installedDataLocation); //Restore data directory's user/group for writing
setExecutables(appDirPath);
} else {
if (quietInstall < 2)
printf("Failed to install %s (v%s).\n", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding]);
}
} else {
if (quietInstall < 2)
printf("Failed to install %s (v%s).\n", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding]);
}
} else {
if (quietInstall < 2)
printf("Failed to install %s (v%s).\n", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding]);
}
//Delete tmp ipa file
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to delete %s.%s", [installPath cStringUsingEncoding:NSUTF8StringEncoding], (i == [ipaFiles count] - 1) ? "\n" : "\n\n");
[pool release];
return IPA_FAILED;
}
//Delete original ipa
if (deleteFile && [fileMgr fileExistsAtPath:ipa]) {
if (![fileMgr removeItemAtPath:ipa error:nil]) {
if (quietInstall < 2)
printf("Failed to delete %s.\n", [ipa cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
if (quietInstall == 0 && i < [ipaFiles count]-1)
printf("\n");
}
if (!removeAllContentsUnderPath(workPath)) {
if (quietInstall < 2)
printf("Failed to clean caches.\n");
}
[pool release];
return successfulInstalls;
}
https://github.com/autopear/ipainstaller
中的theos工程经过make之后,需要手动ldid签名,之后打包deb才能正常工作
签名格式:
ldid -Ssign.plist XXXXX