iOS 调用私有函数安装app 卸载 app

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

  

<dict><key>application-identifier</key><string>com.q2q.testIPAInstall222</string><key>com.apple.private.mobileinstall.allowedSPI</key><array><string>Install</string><string>Browse</string><string>Uninstall</string><string>InstallForLaunchServices</string><string>UninstallForLaunchServices</string></array><key>com.apple.springboard.debugapplications</key><true/><key>get-task-allow</key><true/><key>task_for_pid-allow</key><true/></dict>

  直接使用ldid签名可执行文件后,重新打包成ipa就可以了。

  如果觉得复杂,也可以在Xcode中设置

使用普通的账号就可以了

https://github.com/kryhear/IPAInstaller/tree/master/testIPAInstall.xcodeproj 工程中的代码没有设置签名,导致调用是不成功的

5、ipainstaller源码

  

#include <dlfcn.h>
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#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; i<len; i++)
        ret = [NSString stringWithFormat:@"%@%C", ret, [letters characterAtIndex:arc4random() % [letters length]]];
    return ret;
}

static BOOL removeAllContentsUnderPath(NSString *path) {
    NSFileManager *fileMgr = [NSFileManager defaultManager];
    BOOL isDirectory;
    if ([fileMgr fileExistsAtPath:path isDirectory:&isDirectory]) {
        if (isDirectory) {
            NSArray *dirContents = [fileMgr contentsOfDirectoryAtPath:path error:nil];
            BOOL allRemoved = YES;
            for (int unsigned j=0; j<[dirContents count]; j++) {
                if (![fileMgr removeItemAtPath:[path stringByAppendingPathComponent:[dirContents objectAtIndex:j]] error:nil])
                    allRemoved = NO;
            }
            if (!allRemoved)
                return NO;
            if (![fileMgr removeItemAtPath:path error:nil])
                return NO;
        }
    }
    return YES;
}

static void setPermissionsForPath(NSString *path) {
    NSFileManager *fileMgr = [NSFileManager defaultManager];

    //Set root folder's attributes
    NSDictionary *directoryAttributes = [fileMgr attributesOfItemAtPath:path 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:path error:nil];

    for (NSString *subPath in [fileMgr contentsOfDirectoryAtPath:path error:nil]) {
        NSDictionary *attributes = [fileMgr attributesOfItemAtPath:[path stringByAppendingPathComponent:subPath] error:nil];
        if ([[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeRegular]) {
            NSMutableDictionary *defaultAttributes = [NSMutableDictionary dictionaryWithDictionary:directoryAttributes];

            [defaultAttributes setObject:[NSNumber numberWithInt:501] forKey:NSFileOwnerAccountID];
            [defaultAttributes setObject:@"mobile" forKey:NSFileOwnerAccountName];
            [defaultAttributes setObject:[NSNumber numberWithInt:501] forKey:NSFileGroupOwnerAccountID];
            [defaultAttributes setObject:@"mobile" forKey:NSFileGroupOwnerAccountName];
            [defaultAttributes setObject:[NSNumber numberWithShort:0644] forKey:NSFilePosixPermissions];

            [fileMgr setAttributes:defaultAttributes ofItemAtPath:[path stringByAppendingPathComponent:subPath] error:nil];
        } else if ([[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory])
            setPermissionsForPath([path stringByAppendingPathComponent:subPath]);
        else {
            //Ignore symblic links
        }
    }
}

static void setExecutables(NSString *dirPath) {
    NSFileManager *fileMgr = [NSFileManager defaultManager];
    BOOL isDir;
    if (![fileMgr fileExistsAtPath:dirPath isDirectory:&isDir])
        return;
    if (!isDir)
        return;
    
    NSString *infoPlistPath = [dirPath stringByAppendingPathComponent:@"Info.plist"];
    if ([fileMgr fileExistsAtPath:infoPlistPath]) {
        NSDictionary *infoDict = [NSDictionary dictionaryWithContentsOfFile:infoPlistPath];
        NSString *exeName = [infoDict objectForKey:@"CFBundleExecutable"];
        NSString *exePath = [dirPath stringByAppendingPathComponent:exeName];
        if ([fileMgr fileExistsAtPath:exePath]) {
            NSDictionary *attributes = [fileMgr attributesOfItemAtPath:exePath error:nil];
            if ([[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeRegular]) {
                NSMutableDictionary *executableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
                [executableAttributes setObject:[NSNumber numberWithShort:0755] forKey:NSFilePosixPermissions];
                [fileMgr setAttributes:executableAttributes ofItemAtPath:exePath error:nil];
            }
        }
    }
    
    for (NSString *subPath in [fileMgr contentsOfDirectoryAtPath:dirPath error:nil]) {
        NSString *subDirPath = [dirPath stringByAppendingPathComponent:subPath];
        NSDictionary *attributes = [fileMgr attributesOfItemAtPath:subDirPath error:nil];
        if ([[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory])
            setExecutables(subDirPath);
    }
}

static int versionCompare(NSString *ver1, NSString *ver2) {
    //-1: ver1<ver2; 0: ver1=ver2; 1: ver1>ver2
    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<count; i++) {
            int num1 = [[components1 objectAtIndex:i] intValue];
            int num2 = [[components2 objectAtIndex:i] intValue];

            if (num1 < num2)
                return -1;
            else if (num1 > 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]...
       %@ -{bB} [APP_ID] [-o OUTPUT_PATH]
       %@ -i [APP_ID]...
       %@ -l
       %@ -u [APP_ID]...


Options:
    -a  Show tool about information.
    -b  Back up application with given identifier to IPA.
    -B  Back up application with given identifier and its documents and settings to IPA.
    -c  Perform a clean install.
        If the application has already been installed, the existing documents and other resources will be cleared.
        This implements -n automatically.
    -d  Delete IPA file(s) after installation.
    -f  Force installation, do not check capabilities and system version.
        Installed application may not work properly.
    -h  Display this usage information.
    -i  Display information of installed application(s).
    -l  List identifiers of all installed App Store applications.
    -n  Do not restore saved documents and other resources.
    -o  Output IPA to specified path, or the IPA will be saved under /var/mobile/Documents/.
    -q  Quiet mode, suppress all normal outputs.
    -Q  Quieter mode, suppress all outputs including errors.
    -r  Remove iTunesMetadata.plist after installation.
    -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 %@
Install IPAs via command line or back up/browse/uninstall installed applications.
Version: %@
Author: Merlin Mao

ZipArchive from Matt Connolly
FSSystemHasCapability from Ryan Petrich

Copyright 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
", [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.
");
                [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
", [identifier cStringUsingEncoding:NSUTF8StringEncoding]);
                    if ([appVersion length] > 0)
                        printf("Version: %s
", [appVersion cStringUsingEncoding:NSUTF8StringEncoding]);
                    if ([appShortVersion length] > 0)
                        printf("Short Version: %s
", [appShortVersion cStringUsingEncoding:NSUTF8StringEncoding]);
                    if ([appName length] > 0)
                        printf("Name: %s
", [appName cStringUsingEncoding:NSUTF8StringEncoding]);
                    if ([appDisplayName length] > 0)
                        printf("Display Name: %s
", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding]);
                    if ([appDirPath length] > 0)
                        printf("Bundle: %s
", [appDirPath cStringUsingEncoding:NSUTF8StringEncoding]);
                    if ([appPath length] > 0)
                        printf("Application: %s
", [appPath cStringUsingEncoding:NSUTF8StringEncoding]);
                    if ([dataPath length] > 0)
                        printf("Data: %s
", [dataPath cStringUsingEncoding:NSUTF8StringEncoding]);
                } else {
                    if (quietInstall < 2)
                        printf("Application "%s" is not installed.
", [identifier cStringUsingEncoding:NSUTF8StringEncoding]);
                }
                if (i < [identifiers count] - 1)
                    printf("
");
            }
            return 0;
        }

        if (isUninstall) {
            if ([identifiers count] < 1) {
                printf("You must specify at least one application identifier.
");
                [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".
", [[identifiers objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
                        if (uninstallApplication([identifiers objectAtIndex:i])) {
                            if (quietInstall == 0)
                                printf("Successfully removed application "%s".
", [[identifiers objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
                        } else {
                            if (quietInstall < 2)
                                printf("Failed to remove application "%s".
", [[identifiers objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
                        }
                    } else {
                        if (quietInstall < 2)
                            printf("Application "%s" is not installed.
", [[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.
");
                    [pool release];
                    return 0;
                }
                savePath = [arguments objectAtIndex:4];
            } else if ([arguments count] != 3) {
                printf("Invalid parameters.
");
                [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.
");
                    [pool release];
                    return 0;
                }
                savePath = [arguments objectAtIndex:4];
            } else if ([arguments count] != 3) {
                printf("Invalid parameters.
");
                [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.
");
                    [pool release];
                    return 0;
                }
                savePath = [arguments objectAtIndex:4];
            } else if ([arguments count] != 3) {
                printf("Invalid parameters.
");
                [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.
");
                    [pool release];
                    return 0;
                }
                savePath = [arguments objectAtIndex:4];
            } else if ([arguments count] != 3) {
                printf("Invalid parameters.
");
                [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.
");
                        [pool release];
                        return 0;
                    }
                    savePath = [arguments objectAtIndex:5];
                } else if ([arguments count] != 4) {
                    printf("Invalid parameters.
");
                    [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.
");
                        [pool release];
                        return 0;
                    }
                    savePath = [arguments objectAtIndex:4];
                } else if ([arguments count] != 3) {
                    printf("Invalid parameters.
");
                    [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.
");
                        [pool release];
                        return 0;
                    }
                    savePath = [arguments objectAtIndex:5];
                } else if ([arguments count] != 4) {
                    printf("Invalid parameters.
");
                    [pool release];
                    return 0;
                } else
                    identifier = [arguments objectAtIndex:3];
            }
        }

        if (isBackup || isBackupFull) {
            if ([identifier length] < 1) {
                printf("You must specify an application identifier.
");
                [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.
", [savePath cStringUsingEncoding:NSUTF8StringEncoding]);
                [pool release];
                return IPA_FAILED;
            }

            NSDictionary *installedAppInfo = getInstalledAppInfo(identifier);

            if (!installedAppInfo) {
                if (quietInstall < 2)
                    printf("Application "%s" is not installed.
", [identifier cStringUsingEncoding:NSUTF8StringEncoding]);
                [pool release];
                return IPA_FAILED;
            } else
                printf("Backing up application with identifier "%s"...
", [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.
", [appDirPath cStringUsingEncoding:NSUTF8StringEncoding]);
                [pool release];
                return IPA_FAILED;
            }
            if (!isDirectory) {
                if (quietInstall < 2)
                    printf("%s is not a directory.
", [appDirPath cStringUsingEncoding:NSUTF8StringEncoding]);
                [pool release];
                return IPA_FAILED;
            }
            if (![fileMgr fileExistsAtPath:appPath isDirectory:&isDirectory]) {
                if (quietInstall < 2)
                    printf("Cannot find %s.
", [appPath cStringUsingEncoding:NSUTF8StringEncoding]);
                [pool release];
                return IPA_FAILED;
            }
            if (!isDirectory) {
                if (quietInstall < 2)
                    printf("%s is not a directory.
", [appPath cStringUsingEncoding:NSUTF8StringEncoding]);
                [pool release];
                return IPA_FAILED;
            }
            if (isBackupFull) {
                if (![fileMgr fileExistsAtPath:dataPath isDirectory:&isDirectory]) {
                    if (quietInstall < 2)
                        printf("Cannot find %s.
", [dataPath cStringUsingEncoding:NSUTF8StringEncoding]);
                    [pool release];
                    return IPA_FAILED;
                }
                if (!isDirectory) {
                    if (quietInstall < 2)
                        printf("%s is not a directory.
", [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.
", [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.
");
                [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.
");

                if (!removeAllContentsUnderPath(workPath)) {
                    if (quietInstall < 2)
                        printf("Failed to clean caches.
");
                }

                [pool release];
                return IPA_FAILED;
            }

            if (![ipaArchive addDirectoryToZip:appPath toPathInZip:[NSString stringWithFormat:@"Payload/%@/", [appPath lastPathComponent]]]) {
                if (quietInstall < 2)
                    printf("Failed to create ipa file.
");
                [ipaArchive release];

                if (!removeAllContentsUnderPath(workPath)) {
                    if (quietInstall < 2)
                        printf("Failed to clean caches.
");
                }

                [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...
");

                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.
", [saveDir cStringUsingEncoding:NSUTF8StringEncoding]);

                        if (!removeAllContentsUnderPath(workPath)) {
                            if (quietInstall < 2)
                                printf("Failed to clean caches.
");
                        }

                        [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.
", [saveDir cStringUsingEncoding:NSUTF8StringEncoding]);

                        if (!removeAllContentsUnderPath(workPath)) {
                            if (quietInstall < 2)
                                printf("Failed to clean caches.
");
                        }

                        [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.
");

                    if (!removeAllContentsUnderPath(workPath)) {
                        if (quietInstall < 2)
                            printf("Failed to clean caches.
");
                    }

                    [pool release];
                    return IPA_FAILED;
                }

                if (!removeAllContentsUnderPath(workPath)) {
                    if (quietInstall < 2)
                        printf("Failed to clean caches.
");
                }

                if (quietInstall == 0)
                    printf("The application has been backed up as %s.
", [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.
");

                        if (!removeAllContentsUnderPath(workPath)) {
                            if (quietInstall < 2)
                                printf("Failed to clean caches.
");
                        }

                        [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.
");

                    if (!removeAllContentsUnderPath(workPath)) {
                        if (quietInstall < 2)
                            printf("Failed to clean caches.
");
                    }

                    [pool release];
                    return IPA_FAILED;
                }

                if (!removeAllContentsUnderPath(workPath)) {
                    if (quietInstall < 2)
                        printf("Failed to clean caches.
");
                }

                if (quietInstall == 0)
                    printf("The application has been backed up as %s.
", [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.
");
                [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.
");
                        [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.
");
                        [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.
");
                        [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.
");
                        [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.
");
                        [pool release];
                        return IPA_FAILED;
                    }
                } else {
                    printf("Invalid parameter '%s'.
", [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.
");
            [pool release];
            return IPA_FAILED;
        } else {
            NSArray * identifiers = getInstalledApplications();

            for (unsigned int i=0; i<[identifiers count]; i++)
                printf("%s
", [(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.
");
        [pool release];
        return IPA_FAILED;
    }

    if (showHelp) {
        printf("%s
", [helpString cStringUsingEncoding:NSUTF8StringEncoding]);
        [pool release];
        return 0;
    }

    if (showAbout) {
        printf("%s
", [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.
", [[filesNotFound objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
    }

    if ([ipaFiles count] < 1) {
        if (quietInstall < 2)
            printf("Please specify any IPA file(s) to install.
");
        [pool release];
        return IPA_FAILED;
    }

    if (cleanInstall)
        notRestore = YES;
    if (quietInstall == 0 && cleanInstall)
        printf("Clean installation enabled.
");
    if (quietInstall == 0 && forceInstall)
        printf("Force installation enabled.
");
    if (quietInstall == 0 && notRestore)
        printf("Will not restore any saved documents and other resources.
");
    if (quietInstall == 0 && removeMetadata)
        printf("iTunesMetadata.plist will be removed after installation.
");
    if (quietInstall == 0 && deleteFile) {
        if ([ipaFiles count] == 1)
            printf("%s will be deleted after installation.
", [[[ipaFiles objectAtIndex:0] lastPathComponent] cStringUsingEncoding:NSUTF8StringEncoding]);
        else
            printf("IPA files will be deleted after installation.
");
    }

    if (quietInstall == 0 && (cleanInstall || forceInstall || notRestore || removeMetadata || deleteFile))
        printf("
");

    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.
", [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.
");
        [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.
");
            [pool release];
            return IPA_FAILED;
        }

        NSString *ipa = [ipaFiles objectAtIndex:i];
        if (quietInstall == 0)
            printf("Analyzing %s...
", [[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) ? "
" : "

");

            if (!removeAllContentsUnderPath(workPath)) {
                if (quietInstall < 2)
                    printf("Failed to clean caches.
");
            }

            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) ? "
" : "

");

            if (!removeAllContentsUnderPath(workPath)) {
                if (quietInstall < 2)
                    printf("Failed to clean caches.
");
            }

            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) ? "
" : "

");

            if (!removeAllContentsUnderPath(workPath)) {
                if (quietInstall < 2)
                    printf("Failed to clean caches.
");
            }

            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.
");
                    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.
");
                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) ? "
" : "

");
                    } 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) ? "
" : "

");

                        if (!removeAllContentsUnderPath(workPath)) {
                            if (quietInstall < 2)
                                printf("Failed to clean caches.
");
                        }

                        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) ? "
" : "

");
                    } 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) ? "
" : "

");
                        if (!removeAllContentsUnderPath(workPath)) {
                            if (quietInstall < 2)
                                printf("Failed to clean caches.
");
                        }

                        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 ? "
" : "

");

                [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 ? "
" : "

");

                if (!removeAllContentsUnderPath(workPath)) {
                    if (quietInstall < 2)
                        printf("Failed to clean caches.
");
                }

                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 ? "
" : "

");

                [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 ? "
" : "

");

                if (!removeAllContentsUnderPath(workPath)) {
                    if (quietInstall < 2)
                        printf("Failed to clean caches.
");
                }

                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.
", [capability cStringUsingEncoding:NSUTF8StringEncoding]);

                            shouldUpdateInfoPlist = YES;
                        } else {
                            if (quietInstall < 2)
                                printf("Your device does not support %s capability.
", [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.
");
                        }

                        if (i != [ipaFiles count] - 1) //Not the last output
                            printf("
");

                        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.
", [capabilityKey cStringUsingEncoding:NSUTF8StringEncoding]);
                                else //Device support but IPA requires to be false
                                    printf("Your device conflicts with %s capability.
", [capabilityKey cStringUsingEncoding:NSUTF8StringEncoding]);
                            }

                            shouldUpdateInfoPlist = YES;
                        } else {
                            if (quietInstall < 2) {
                                if (capabilityValue) //Device does not support
                                    printf("Your device does not support %s capability.
", [capabilityKey cStringUsingEncoding:NSUTF8StringEncoding]);
                                else //Device support but IPA requires to be false
                                    printf("Your device conflicts with %s capability.
", [capabilityKey cStringUsingEncoding:NSUTF8StringEncoding]);
                            }
                        }
                    }
                }
                if (!isCapable) {
                    if (forceInstall)
                        [infoDict setObject:newCapabilities forKey:@"UIRequiredDeviceCapabilities"];
                    else {
                        if (!removeAllContentsUnderPath(workPath)) {
                            if (quietInstall < 2)
                                printf("Failed to clean caches.
");
                        }

                        if (i != [ipaFiles count] - 1) //Not the last output
                            printf("
");

                        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) ? "
" : "

");
            continue;
        }

        //Copy file to install
        if ([fileMgr fileExistsAtPath:installPath]) {
            if (![fileMgr removeItemAtPath:installPath error:nil]) {
                if (quietInstall < 2)
                    printf("Failed to delete %s.
", [installPath cStringUsingEncoding:NSUTF8StringEncoding]);

                if (![fileMgr removeItemAtPath:workPath error:nil]) {
                    if (quietInstall < 2)
                        printf("Failed to clean caches.
");
                }

                [pool release];
                return IPA_FAILED;
            }
        }

        if (![fileMgr copyItemAtPath:ipa toPath:installPath error:nil]) {
            if (quietInstall < 2)
                printf("Failed to create temporaty files.
");

            if (![fileMgr removeItemAtPath:workPath error:nil] && quietInstall < 2)
                printf("Failed to clean caches.
");

            [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) ? "
" : "

");

                //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.
");
                }

                continue;
            }
        }

        if (quietInstall == 0)
            printf("%snstalling %s (v%s)...
", 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.
", 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...
", [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.
", [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.
");
                                    }
                                }
                            }
                            [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...
", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding]);
                            if (![fileMgr removeItemAtPath:[installedAppLocation stringByAppendingPathComponent:@"iTunesMetadata.plist"] error:nil]) {
                                if (quietInstall < 2)
                                    printf("Failed to remove %s.
", [[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).
", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding]);
                }
            } else {
                if (quietInstall < 2)
                    printf("Failed to install %s (v%s).
", [appDisplayName cStringUsingEncoding:NSUTF8StringEncoding], [(appShortVersion ? appShortVersion : appVersion) cStringUsingEncoding:NSUTF8StringEncoding]);
            }
        } else {
            if (quietInstall < 2)
                printf("Failed to install %s (v%s).
", [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) ? "
" : "

");

            [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.
", [ipa cStringUsingEncoding:NSUTF8StringEncoding]);
            }
        }

        if (quietInstall == 0 && i < [ipaFiles count]-1)
            printf("
");
    }

    if (!removeAllContentsUnderPath(workPath)) {
        if (quietInstall < 2)
            printf("Failed to clean caches.
");
    }

    [pool release];

    return successfulInstalls;
}

https://github.com/autopear/ipainstaller

中的theos工程经过make之后,需要手动ldid签名,之后打包deb才能正常工作

签名格式:

 ldid -Ssign.plist XXXXX 

  

原文地址:https://www.cnblogs.com/doudouyoutang/p/5777741.html