//
//  jailbreak.m
//  Meridian
//
//  Created by Ben Sparkes on 16/02/2018.
//  Copyright © 2018 Ben Sparkes. All rights reserved.
//

#include "v0rtex.h"
#include "kernel.h"
#include "helpers.h"
#include "root-rw.h"
#include "amfi.h"
#include "offsetfinder.h"
#include "jailbreak.h"
#include "ViewController.h"
#include "patchfinder64.h"
#include "patchfinders/offsetdump.h"
#include "nvpatch.h"
#include <mach/mach_types.h>
#include <sys/stat.h>
#import <Foundation/Foundation.h>

NSFileManager *fileMgr;

offsets_t offsets;

BOOL great_success = FALSE;

int makeShitHappen(ViewController *view) {
    int ret;
    
    fileMgr = [NSFileManager defaultManager];

    // run v0rtex
    [view writeText:@"running v0rtex..."];
    suspend_all_threads();
    ret = runV0rtex();
    resume_all_threads();
    if (ret != 0) {
        [view writeText:@"failed!"];
        if (ret == -420) {
            [view writeTextPlain:@"failed to load offsets!"];
        }
        return 1;
    }
    [view writeTextPlain:@"succeeded! praize siguza!"];
    
    // set up stuff
    init_patchfinder(NULL);
    ret = init_amfi();
    
    if (ret != 0) {
        [view writeTextPlain:@"failed to initialize amfi class!"];
        return 1;
    }
    
    // patch containermanager
    [view writeText:@"patching containermanager..."];
    ret = patchContainermanagerd();
    if (ret != 0) {
        [view writeText:@"failed!"];
        return 1;
    }
    [view writeText:@"done!"];
    
    // remount root fs
    [view writeText:@"remounting rootfs as r/w..."];
    ret = remountRootFs();
    if (ret != 0) {
        [view writeText:@"failed!"];
        return 1;
    }
    [view writeText:@"done!"];
    
    /*      Begin the filesystem fuckery      */
    
    [view writeText:@"some filesytem fuckery..."];
    
    // Remove /meridian in the case of PB's
    if (file_exists("/meridian") == 0 &&
        file_exists("/meridian/.bootstrap") != 0) {
        [fileMgr removeItemAtPath:@"/meridian" error:nil];
    }
    
    if (file_exists("/meridian") != 0) {
        ret = mkdir("/meridian", 0755);
        if (ret != 0) {
            [view writeText:@"failed!"];
            [view writeTextPlain:@"creating /meridian failed with error %d: %s", errno, strerror(errno)];
            return 1;
        }
    }
    
    if (file_exists("/meridian/logs") != 0) {
        ret = mkdir("/meridian/logs", 0755);
        if (ret != 0) {
            [view writeText:@"failed!"];
            [view writeTextPlain:@"creating /meridian/logs failed with error %d: %s", errno, strerror(errno)];
            return 1;
        }
    }
    
    if (file_exists("/meridian/tar") == 0) {
        ret = unlink("/meridian/tar");
        if (ret != 0) {
            [view writeText:@"failed!"];
            [view writeTextPlain:@"removing /meridian/tar failed with error %d: %s", errno, strerror(errno)];
            return 1;
        }
    }
    
    if (file_exists("/meridian/tar.tar") == 0) {
        ret = unlink("/meridian/tar.tar");
        if (ret != 0) {
            [view writeText:@"failed!"];
            [view writeTextPlain:@"deleting /meridian/tar.tar failed with error %d: %s", errno, strerror(errno)];
            return 1;
        }
    }
    
    ret = extract_bundle("tar.tar", "/meridian");
    if (ret != 0) {
        [view writeText:@"failed!"];
        [view writeTextPlain:@"failed to extract tar.tar bundle! ret: %d, errno: %d: %s", ret, errno, strerror(errno)];
        return 1;
    }
    
    if (file_exists("/meridian/tar") != 0) {
        [view writeText:@"failed!"];
        [view writeTextPlain:@"/meridian/tar was not found :("];
        return 1;
    }
    
    ret = chmod("/meridian/tar", 0755);
    if (ret != 0) {
        [view writeText:@"failed!"];
        [view writeTextPlain:@"chmod(755)'ing /meridian/tar failed with error %d: %s", errno, strerror(errno)];
        return 1;
    }
    
    ret = inject_trust("/meridian/tar");
    if (ret != 0) {
        [view writeText:@"failed!"];
        [view writeTextPlain:@"injecting trust to /meridian/tar failed with retcode %d", ret];
        return 1;
    }
    
    [view writeText:@"done!"];
    
    // extract meridian-bootstrap
    [view writeText:@"extracting meridian files..."];
    ret = extractMeridianData();
    if (ret != 0) {
        [view writeText:@"failed!"];
        [view writeTextPlain:[NSString stringWithFormat:@"error code: %d", ret]];
        return 1;
    }
    [view writeText:@"done!"];
    
    // dump offsets to file for later use (/meridian/offsets.plist)
    dumpOffsetsToFile(&offsets, kernel_base, kslide);
    
    // patch amfid
    [view writeText:@"patching amfid..."];
    ret = defecateAmfi();
    if (ret != 0) {
        [view writeText:@"failed!"];
        if (ret > 0) {
            [view writeTextPlain:[NSString stringWithFormat:@"failed to patch - %d tries", ret]];
        }
        return 1;
    }
    [view writeText:@"done!"];
    
    // touch .cydia_no_stash
    touch_file("/.cydia_no_stash");
    
    // symlink /Library/MobileSubstrate/DynamicLibraries -> /usr/lib/tweaks
    setUpSymLinks();
    
    // remove Substrate's SafeMode (MobileSafety) if it's installed
    // removing from dpkg will be handled by Cydia conflicts later
    if (file_exists("/usr/lib/tweaks/MobileSafety.dylib") == 0) {
        unlink("/usr/lib/tweaks/MobileSafety.dylib");
    }
    if (file_exists("/usr/lib/tweaks/MobileSafety.plist") == 0) {
        unlink("/usr/lib/tweaks/MobileSafety.plist");
    }
    
    // extract bootstrap (if not already extracted)
    if (file_exists("/meridian/.bootstrap") != 0) {
        [view writeText:@"extracting bootstrap..."];
        int exitCode = 0;
        ret = extractBootstrap(&exitCode);
        
        if (ret != 0) {
            [view writeText:@"failed!"];
            
            switch (ret) {
                case 1:
                    [view writeTextPlain:@"failed to extract system-base.tar"];
                    break;
                case 2:
                    [view writeTextPlain:@"failed to extract installer-base.tar"];
                    break;
                case 3:
                    [view writeTextPlain:@"failed to extract dpkgdb-base.tar"];
                    break;
                case 4:
                    [view writeTextPlain:@"failed to extract cydia-base.tar"];
                    break;
                case 5:
                    [view writeTextPlain:@"failed to extract optional-base.tar"];
                    break;
                case 6:
                    [view writeTextPlain:@"failed to run uicache!"];
                    break;
            }
            [view writeTextPlain:@"exit code: %d", exitCode];
            
            return 1;
        }
        
        [view writeText:@"done!"];
    }
    
    // add the midnight repo 
    if (file_exists("/etc/apt/sources.list.d/meridian.list") != 0) {
        FILE *fd = fopen("/etc/apt/sources.list.d/meridian.list", "w+");
        const char *text = "deb http://repo.midnight.team ./";
        fwrite(text, strlen(text) + 1, 1, fd);
        fclose(fd);
    }
    
    // launch dropbear
    [view writeText:@"launching dropbear..."];
    ret = launchDropbear();
    if (ret != 0) {
        [view writeText:@"failed!"];
        [view writeTextPlain:@"exit code: %d", ret];
        return 1;
    }
    [view writeText:@"done!"];
    
    // link substitute stuff
    setUpSubstitute();
    
    // start jailbreakd
    [view writeText:@"starting jailbreakd..."];
    ret = startJailbreakd();
    if (ret != 0) {
        [view writeText:@"failed"];
        if (ret > 1) {
            [view writeTextPlain:@"failed to launch - %d tries", ret];
        }
        return 1;
    }
    [view writeText:@"done!"];
    
    // patch com.apple.System.boot-nonce
    [view writeText:@"patching boot-nonce..."];
    ret = nvpatch("com.apple.System.boot-nonce");
    if (ret != 0) {
        [view writeText:@"failed!"];
        return 1;
    }
    [view writeText:@"done!"];
    
    // load launchdaemons
    [view writeText:@"loading launchdaemons..."];
    ret = loadLaunchDaemons();
    if (ret != 0) {
        [view writeText:@"failed!"];
        return 1;
    }
    [view writeText:@"done!"];
    
    great_success = TRUE;
    
    return 0;
}

kern_return_t callback(task_t kern_task, kptr_t kbase, void *cb_data) {
    tfp0 = kern_task;
    kernel_base = kbase;
    kslide = kernel_base - 0xFFFFFFF007004000;
    
    return KERN_SUCCESS;
}

int runV0rtex() {
    offsets_t *offs = get_offsets();
    
    if (offs == NULL) {
        return -420;
    }
    
    offsets = *offs;
    
    int ret = v0rtex(&offsets, &callback, NULL);
    
    uint64_t kernel_task_addr = rk64(offs->kernel_task + kslide);
    kernprocaddr = rk64(kernel_task_addr + offs->task_bsd_info);
    kern_ucred = rk64(kernprocaddr + offs->proc_ucred);
    
    if (ret == 0) {
        NSLog(@"tfp0: 0x%x", tfp0);
        NSLog(@"kernel_base: 0x%llx", kernel_base);
        NSLog(@"kslide: 0x%llx", kslide);
        NSLog(@"kern_ucred: 0x%llx", kern_ucred);
        NSLog(@"kernprocaddr: 0x%llx", kernprocaddr);
    }
    
    return ret;
}

int patchContainermanagerd() {
    uint64_t cmgr = find_proc_by_name("containermanager");
    if (cmgr == 0) {
        NSLog(@"unable to find containermanager!");
        return 1;
    }
    
    wk64(cmgr + 0x100, kern_ucred);
    return 0;
}

int remountRootFs() {
    NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
    int pre130 = osVersion.minorVersion < 2 ? 1 : 0;
    
    int rv = mount_root(kslide, offsets.root_vnode, pre130);
    if (rv != 0) {
        return 1;
    }
    
    return 0;
}

int extractMeridianData() {
    return extract_bundle_tar("meridian-bootstrap.tar");
}

void setUpSymLinks() {
    struct stat file;
    stat("/Library/MobileSubstrate/DynamicLibraries", &file);
    
    if (file_exists("/Library/MobileSubstrate/DynamicLibraries") == 0 &&
        file_exists("/usr/lib/tweaks") == 0 &&
        S_ISLNK(file.st_mode)) {
        return;
    }
    
    // By the end of this check, /usr/lib/tweaks should exist containing any
    // tweaks (if applicable), and /Lib/MobSub/DynLib should NOT exist
    if (file_exists("/Library/MobileSubstrate/DynamicLibraries") == 0 &&
        file_exists("/usr/lib/tweaks") != 0) {
        // Move existing tweaks folder to /usr/lib/tweaks
        [fileMgr moveItemAtPath:@"/Library/MobileSubstrate/DynamicLibraries" toPath:@"/usr/lib/tweaks" error:nil];
    } else if (file_exists("/Library/MobileSubstrate/DynamicLibraries") == 0 &&
               file_exists("/usr/lib/tweaks") == 0) {
        // Move existing tweaks to /usr/lib/tweaks and delete the MobSub folder
        NSArray *fileList = [fileMgr contentsOfDirectoryAtPath:@"/Library/MobileSubstrate/DynamicLibraries" error:nil];
        for (NSString *item in fileList) {
            NSString *fullPath = [NSString stringWithFormat:@"/Library/MobileSubstrate/DynamicLibraries/%@", item];
            [fileMgr moveItemAtPath:fullPath toPath:@"/usr/lib/tweaks" error:nil];
        }
        [fileMgr removeItemAtPath:@"/Library/MobileSubstrate/DynamicLibraries" error:nil];
    } else if (file_exists("/Library/MobileSubstrate/DynamicLibraries") != 0 &&
               file_exists("/usr/lib/tweaks") != 0) {
        // Just create /usr/lib/tweaks - /Lib/MobSub/DynLibs doesn't exist
        mkdir("/Library/MobileSubstrate", 0755);
        mkdir("/usr/lib/tweaks", 0755);
    } else if (file_exists("/Library/MobileSubstrate/DynamicLibraries") != 0 &&
               file_exists("/usr/lib/tweaks") == 0) {
        // We should be fine in this case
        mkdir("/Library/MobileSubstrate", 0755);
    }
    
    // Symlink it!
    symlink("/usr/lib/tweaks", "/Library/MobileSubstrate/DynamicLibraries");
}

int extractBootstrap(int *exitCode) {
    int rv;
    
    // extract system-base.tar
    rv = extract_bundle_tar("system-base.tar");
    if (rv != 0) {
        *exitCode = rv;
        return 1;
    }
    
    // extract installer-base.tar
    rv = extract_bundle_tar("installer-base.tar");
    if (rv != 0) {
        *exitCode = rv;
        return 2;
    }
    
    if (file_exists("/private/var/lib/dpkg/status") != 0) {
        rv = extract_bundle_tar("dpkgdb-base.tar");
        if (rv != 0) {
            *exitCode = rv;
            return 3;
        }
    }
    
    // extract cydia-base.tar
    rv = extract_bundle_tar("cydia-base.tar");
    if (rv != 0) {
        *exitCode = rv;
        return 4;
    }
    
    // extract optional-base.tar
    rv = extract_bundle_tar("optional-base.tar");
    if (rv != 0) {
        *exitCode = rv;
        return 5;
    }
    
    enableHiddenApps();
    
    touch_file("/meridian/.bootstrap");
    
    rv = uicache();
    if (rv != 0) {
        *exitCode = rv;
        return 6;
    }
    
    return 0;
}

int defecateAmfi() {
    // write kslide to file
    unlink("/meridian/kernel_slide");
    FILE *fd = fopen("/meridian/kernel_slide", "w");
    fprintf(fd, "%016llx", kslide);
    fclose(fd);
    
    // trust our payload
    int ret = inject_trust("/meridian/amfid_payload.dylib");
    if (ret != 0) return -1;
    
    unlink("/var/tmp/amfid_payload.alive");
    
    pid_t pid = get_pid_for_name("amfid");
    if (pid == 0) {
        return -2;
    }
    
    ret = inject_library(pid, "/meridian/amfid_payload.dylib");
    if (ret != 0) return -2;
    
    int tries = 0;
    while (file_exists("/var/tmp/amfid_payload.alive") != 0) {
        if (tries >= 100) {
            NSLog(@"failed to patch amfid (%d tries)", tries);
            return tries;
        }
        
        NSLog(@"waiting for amfid patch...");
        usleep(100000); // 0.1 sec
        tries++;
    }
    
    return 0;
}

int launchDropbear() {
    return start_launchdaemon("/meridian/dropbear/dropbear.plist");
}

void setUpSubstitute() {
    // link CydiaSubstrate.framework -> /usr/lib/libsubstrate.dylib
    if (file_exists("/Library/Frameworks/CydiaSubstrate.framework") == 0) {
        [fileMgr removeItemAtPath:@"/Library/Frameworks/CydiaSubstrate.framework" error:nil];
    }
    mkdir("/Library/Frameworks", 0755);
    mkdir("/Library/Frameworks/CydiaSubstrate.framework", 0755);
    symlink("/usr/lib/libsubstrate.dylib", "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate");
}

int startJailbreakd() {
    unlink("/var/tmp/jailbreakd.pid");
    
    NSData *blob = [NSData dataWithContentsOfFile:@"/meridian/jailbreakd/jailbreakd.plist"];
    NSMutableDictionary *job = [NSPropertyListSerialization propertyListWithData:blob options:NSPropertyListMutableContainers format:nil error:nil];
    
    job[@"EnvironmentVariables"][@"KernelBase"] = [NSString stringWithFormat:@"0x%16llx", kernel_base];
    job[@"EnvironmentVariables"][@"KernProcAddr"] = [NSString stringWithFormat:@"0x%16llx", kernprocaddr];
    job[@"EnvironmentVariables"][@"ZoneMapOffset"] = [NSString stringWithFormat:@"0x%16llx", offsets.zone_map];
    [job writeToFile:@"/meridian/jailbreakd/jailbreakd.plist" atomically:YES];
    chmod("/meridian/jailbreakd/jailbreakd.plist", 0600);
    chown("/meridian/jailbreakd/jailbreakd.plist", 0, 0);
    
    int rv = start_launchdaemon("/meridian/jailbreakd/jailbreakd.plist");
    if (rv != 0) return 1;
    
    int tries = 0;
    while (file_exists("/var/tmp/jailbreakd.pid") != 0) {
        printf("Waiting for jailbreakd \n");
        tries++;
        usleep(300000); // 300ms
        
        if (tries >= 100) {
            NSLog(@"too many tries for jbd - %d", tries);
            return tries;
        }
    }
    
    usleep(100000);
    
    // tell jailbreakd to platformize launchd
    // this adds skip-lib-val to MACF slot and allows us
    // to inject pspawn without it being in trust cache
    // (plus FAT/multiarch in trust cache is a pain to code, i'm lazy)
    rv = call_jailbreakd(JAILBREAKD_COMMAND_ENTITLE, 1);
    if (rv != 0) return 2;
    
    // inject pspawn_hook.dylib to launchd
    rv = inject_library(1, "/usr/lib/pspawn_hook.dylib");
    if (rv != 0) return 3;
    
    return 0;
}

int loadLaunchDaemons() {
    NSArray *daemons = [fileMgr contentsOfDirectoryAtPath:@"/Library/LaunchDaemons" error:nil];
    for (NSString *file in daemons) {
        NSString *path = [NSString stringWithFormat:@"/Library/LaunchDaemons/%@", file];
        NSLog(@"found launchdaemon: %@", path);
        chmod([path UTF8String], 0755);
        chown([path UTF8String], 0, 0);
    }
    
    return start_launchdaemon("/Library/LaunchDaemons");
}

void enableHiddenApps() {
    // enable showing of system apps on springboard
    // this is some funky killall stuff tho
    killall("cfprefsd", "-SIGSTOP");
    NSMutableDictionary* md = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.apple.springboard.plist"];
    [md setObject:[NSNumber numberWithBool:YES] forKey:@"SBShowNonDefaultSystemApps"];
    [md writeToFile:@"/var/mobile/Library/Preferences/com.apple.springboard.plist" atomically:YES];
    killall("cfprefsd", "-9");
}
