Skip to content

Instantly share code, notes, and snippets.

@NikolaRusakov
Created September 9, 2022 20:39
Show Gist options
  • Select an option

  • Save NikolaRusakov/73a21f7391b6a309232ea40772ede269 to your computer and use it in GitHub Desktop.

Select an option

Save NikolaRusakov/73a21f7391b6a309232ea40772ede269 to your computer and use it in GitHub Desktop.

Revisions

  1. NikolaRusakov created this gist Sep 9, 2022.
    1,391 changes: 1,391 additions & 0 deletions sketchcrapp.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1391 @@
    #!/bin/bash
    # RUP Review every time when new verison update part.
    # Latest supported version
    latestVersion="v71.2"

    # Support version list.
    declare -a version_list

    # RUP Review every time when new verison update part.
    # Address parameter array and other parameters for each version.
    # Version 53
    declare -a address_param_513
    version_list+=("51.3")
    address_param_513+=("3bc0e7")
    address_param_513+=("3bab6d")
    address_param_513+=("3bacbc")
    exe_hash_513="21aed0fe8e29e9eaf9a1194b316ae6ee0ec342f7"
    # Version 53
    declare -a address_param_530
    version_list+=("53")
    address_param_530+=("4143c2")
    address_param_530+=("412f2d")
    address_param_530+=("413079")
    address_param_530+=("4d2f09")
    exe_hash_530="f8a7efc0814bbd26c591dcb8b2922cdf17016c04"
    # Version 58
    declare -a address_param_580
    version_list+=("58")
    address_param_580+=("3912bf")
    address_param_580+=("3912c2")
    address_param_580+=("38ff2e")
    address_param_580+=("39007d")
    exe_hash_580="5669dd6d7452a96ac3523ea8fa1a41d9ce70f50f"
    # Version 63.1
    declare -a address_param_631
    version_list+=("63.1")
    address_param_631+=("4a2a4f")
    address_param_631+=("4a2a52")
    address_param_631+=("4a173a")
    address_param_631+=("4a1879")
    exe_hash_631="db9b88f3aa6abc484be104660fa911275d9f2515"
    # Version 64
    declare -a address_param_640
    version_list+=("64")
    address_param_640+=("4cde6f")
    address_param_640+=("4cde72")
    address_param_640+=("4ccb5a")
    address_param_640+=("4ccc99")
    exe_hash_640="a4d16224ebb8caf84c94a6863db183fd306002da"
    # Version 65.1
    declare -a address_param_651
    version_list+=("65.1")
    address_param_651+=("4db4ff")
    address_param_651+=("4db502")
    address_param_651+=("4da1ea")
    address_param_651+=("4da329")
    exe_hash_651="0e7cad9b81284d127d652b3a8c962315770cd905"
    # Version 66.1
    declare -a address_param_661
    version_list+=("66.1")
    address_param_661+=("4f374f")
    address_param_661+=("4f3752")
    address_param_661+=("4f243a")
    address_param_661+=("4f2579")
    exe_hash_661="97d6273be93546a9b3caa7c8e1f97fe2246e673b"
    # Version 67 & 67.1
    declare -a address_param_670
    version_list+=("67")
    address_param_670+=("50a6cf")
    address_param_670+=("50a6d2")
    address_param_670+=("5093aa")
    address_param_670+=("5094e9")
    exe_hash_670="b9866183ded9573aea6be0a004f31c8941059f3f"
    version_list+=("67.1")
    exe_hash_671="708e9203a8628c5cee767eb75546c6145b69df57"
    # Version 67.2
    declare -a address_param_672
    version_list+=("67.2")
    address_param_672+=("50a78f")
    address_param_672+=("50a792")
    address_param_672+=("50946a")
    address_param_672+=("5095a9")
    exe_hash_672="9762906ced4d5589e27b297012ce862665e65a29"
    # Version 68
    declare -a address_param_680
    version_list+=("68")
    address_param_680+=("54d2af")
    address_param_680+=("54d2b2")
    address_param_680+=("54bf8a")
    address_param_680+=("54c0c9")
    exe_hash_680="ad9ccdce3ac270b2441f0efb8f3233935fb1900a"
    # Version 68.1 & 68.2
    declare -a address_param_681
    version_list+=("68.1")
    address_param_681+=("54d34f")
    address_param_681+=("54d352")
    address_param_681+=("54c02a")
    address_param_681+=("54c169")
    exe_hash_681="bc22987f7b3a7580aba1ac260c59d66d0a3622e7"
    version_list+=("68.2")
    exe_hash_682="651f3263305e004133253c2706fcdf5b16e20558"
    # Version 69
    declare -a address_param_690
    version_list+=("69")
    address_param_690+=("5cf76f")
    address_param_690+=("5cf772")
    address_param_690+=("5ce44a")
    address_param_690+=("5ce589")
    exe_hash_690="2d4027890e2b72175c4a562f59c5d1adb2655b8c"
    # Version 69.1 & 69.2
    declare -a address_param_691
    version_list+=("69.1")
    address_param_691+=("5d09df")
    address_param_691+=("5d09e2")
    address_param_691+=("5cf57e")
    address_param_691+=("5cf6ae")
    exe_hash_691="4ce06aa34c40040244c60608a02c152186f23c32"
    version_list+=("69.2")
    exe_hash_692="a7ba7ddf8e15e1e03ae88c00ab5070dabecc06b2"
    # Version 70.2
    declare -a address_param_702
    version_list+=("70.2")
    address_param_702+=("5861bf")
    address_param_702+=("5861c2")
    address_param_702+=("584e0e")
    address_param_702+=("584f3e")
    address_param_702+=("66d03f")
    address_param_702+=("66d04f")
    address_param_702+=("dcd72c")
    address_param_702+=("dcd730")
    address_param_702+=("dcc3a4")
    address_param_702+=("dcc4c7")
    address_param_702+=("ed1031")
    address_param_702+=("ed1041")
    exe_hash_702="e3376ad6f58bdf1e24306bf6d94ee67b082a94db"
    # Version 70.3
    declare -a address_param_703
    version_list+=("70.3")
    address_param_703+=("58619f")
    address_param_703+=("5861a2")
    address_param_703+=("584dee")
    address_param_703+=("584f1e")
    address_param_703+=("66d058")
    address_param_703+=("66d068")
    address_param_703+=("dcd574")
    address_param_703+=("dcd578")
    address_param_703+=("dcc1ec")
    address_param_703+=("dcc30f")
    address_param_703+=("ed0fd7")
    address_param_703+=("ed0fe7")
    exe_hash_703="9a0e6b7721c275b75e1fb6b70b55cda7ab99c4a8"
    # Version 70.4
    declare -a address_param_704
    version_list+=("70.4")
    address_param_704+=("58606f")
    address_param_704+=("586072")
    address_param_704+=("584cbe")
    address_param_704+=("584dee")
    address_param_704+=("66cffb")
    address_param_704+=("66d00b")
    address_param_704+=("dcd484")
    address_param_704+=("dcd488")
    address_param_704+=("dcc0fc")
    address_param_704+=("dcc21f")
    address_param_704+=("ed0fa5")
    address_param_704+=("ed0fb5")
    exe_hash_704="6adf7ee4c29bb1a61739b8804c7ce6d94c791f36"
    # Version 70.5 & 70.6
    declare -a address_param_705
    version_list+=("70.5")
    address_param_705+=("585eef")
    address_param_705+=("585ef2")
    address_param_705+=("584b3e")
    address_param_705+=("584c6e")
    address_param_705+=("66ce63")
    address_param_705+=("66ce73")
    address_param_705+=("dcd404")
    address_param_705+=("dcd408")
    address_param_705+=("dcc07c")
    address_param_705+=("dcc19f")
    address_param_705+=("ed0fbc")
    address_param_705+=("ed0fcc")
    exe_hash_705="2f1f2fff46d692f2ffe67cec150da4d5d67218eb"
    version_list+=("70.6")
    exe_hash_706="33d438d86cbb81bf945f30d0906395522680a03f"
    # Version 71.1
    declare -a address_param_711
    version_list+=("71.1")
    address_param_711+=("5d904f")
    address_param_711+=("5d9052")
    address_param_711+=("5d7c9e")
    address_param_711+=("5d7dce")
    address_param_711+=("6cb001")
    address_param_711+=("6cb011")
    address_param_711+=("e95d84")
    address_param_711+=("e95d88")
    address_param_711+=("e949f4")
    address_param_711+=("e94b17")
    address_param_711+=("fa6458")
    address_param_711+=("fa6468")
    exe_hash_711="abd3ce2193b113c28d1bf5e28739bb303b52a556"
    # Version 71.2
    declare -a address_param_712
    version_list+=("71.2")
    address_param_712+=("5dccbf")
    address_param_712+=("5dccc2")
    address_param_712+=("5db90e")
    address_param_712+=("5dba3e")
    address_param_712+=("6cef41")
    address_param_712+=("6cef51")
    address_param_712+=("e9992c")
    address_param_712+=("e99930")
    address_param_712+=("e9859c")
    address_param_712+=("e986bf")
    address_param_712+=("faa308")
    address_param_712+=("faa318")
    exe_hash_712="35d64aa153bca44a4325b47211ae19ae2bf16fac"
    # Value old parameter array.
    declare -a value_ancient_param
    value_ancient_param+=("\00")
    value_ancient_param+=("\00\00")
    value_ancient_param+=("\165")
    # Value old parameter array.
    declare -a value_old_param
    value_old_param+=("\00")
    value_old_param+=("\00\00")
    value_old_param+=("\165")
    value_old_param+=("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") # x86_remove_register
    # Value parameter array.
    declare -a value_legacy_param
    value_legacy_param+=("\00")
    value_legacy_param+=("\00")
    value_legacy_param+=("\00\00")
    value_legacy_param+=("\165")
    declare -a value_param
    value_param+=("\00") # x86_1
    value_param+=("\00") # x86_2
    value_param+=("\00\00") # x86_3
    value_param+=("\165") # x86_4
    value_param+=("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") # x86_remove_register
    value_param+=("\40\123\153\145\164\143\150\103\162\141\160\160\40") # x86_modified_day_left
    value_param+=("\01") # aarch_1
    value_param+=("\24") # aarch_2
    value_param+=("\165\00") # aarch_3
    value_param+=("\64") # aarch_4
    value_param+=("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") # aarch_remove_register
    value_param+=("\40\123\153\145\164\143\150\103\162\141\160\160\40") # aarch_modified_day_left
    declare -a nametag_ancient_ver
    nametag_ancient_ver+=("51.3")
    declare -a nametag_old_ver
    nametag_old_ver+=("53")

    # OpenSSL configuration.
    CONFIG="
    [ req ]
    distinguished_name=name
    [ name ]
    [ self ]
    keyUsage = critical,digitalSignature
    extendedKeyUsage = codeSigning
    subjectKeyIdentifier = hash
    basicConstraints = critical,CA:false
    "

    downloadURLString=""

    # Banner block
    banner() {
    cat <<"EOF"
    __ __ __
    ___ / /_____ / /_____/ / ___________ ____ ___
    ( _-</ '_/ -_) __/ __/ _ \/ __/ __/ _ `/ _ \/ _ \
    /___/_/\_\\__/\__/\__/_//_/\__/_/ \_,_/ .__/ .__/
    /_/ /_/
    Sketch.App Patch Tool (https://github.com/duraki/SketchCrapp)
    by @duraki & @elijahtsai
    EOF
    }

    # RUP Review every time when new verison update part.
    # Last function to run before exit.
    finally() {
    local status="$1"
    local pds="2021-10-16 serial 002"
    printf "[+] SketchCrapp last published date: \e[38;5;14m$pds\e[0m\n"
    exit $status
    }

    # RUP Review every time when new verison update part.
    # Help messages block.
    usage() {
    echo "Usage:"
    echo "./sketchcrapp [-h] [-a] <applicationPath> [-m] [-g] <version>"
    echo "Supported versions: v51.3, v53, v58, v63.1, v64.0, v65.1, v66.1, v67"
    echo "v67.1, v67.2, v68, v68.1, v68.2, v69, v69.1, v69.2, v70.2, v70.3, v70.4"
    echo "v70.5, v70.6, v71.1, v71.2"
    finally 0;
    }

    # Clean up all related files.
    clean() {
    printf "[+] Cleaning up file(s) ... "
    if [ -f pk.pem ]; then
    rm -f pk.pem
    fi
    if [ -f crt.pem ]; then
    rm -f crt.pem
    fi
    if [ -f pkcs.p12 ]; then
    rm -f pkcs.p12
    fi
    if [ -f "/tmp/app.zip" ]; then
    rm -f "/tmp/app.zip"
    if ! [ "$?" -eq "0" ]; then
    printf "\e[38;5;9mError\e[0m\n"
    echo "[-] Fail to remove zip file of latest application. remove by yourself."
    finally 1
    fi
    fi
    if [ -f "/tmp/Sketch.app" ]; then
    rm -f "/tmp/Sketch.app"
    if ! [ "$?" -eq "0" ]; then
    printf "\e[38;5;9mError\e[0m\n"
    echo "[-] Fail to remove application bundle. remove by yourself."
    finally 1
    fi
    fi
    if [ -f "/tmp/select.swift" ]; then
    rm -f "/tmp/select.swift"
    if ! [ "$?" -eq "0" ]; then
    printf "\e[38;5;9mError\e[0m\n"
    echo "[-] Fail to remove swift script file. remove by yourself."
    finally 1
    fi
    fi
    if [ -f "/tmp/list.xml" ]; then
    rm -f "/tmp/list.xml"
    if ! [ "$?" -eq "0" ]; then
    printf "\e[38;5;9mError\e[0m\n"
    echo "[-] Fail to remove list xml file. remove by yourself."
    finally 1
    fi
    fi
    printf "\e[38;5;10mCleaned\e[0m\n"
    }

    # Generate self-signed certificate for codesign. Required for pass-tru
    # code-signature detection by Sketch. Built-in via MacOS openssl library.
    genSelfSignCert() {
    echo "[+] Generating self-signed certificate ..."
    openssl req -new -newkey ec:<(openssl ecparam -name secp521r1) \
    -config <(echo "$CONFIG") \
    -extensions self -days 3650 -nodes -x509 \
    -subj "/CN=sketchcrapp"\
    -keyform pem -keyout pk.pem \
    -outform pem -out crt.pem
    echo "[+] Creating pkcs package..."
    openssl pkcs12 -export -out pkcs.p12 -in crt.pem -inkey pk.pem \
    -name "sketchcrapp" -nodes -passout pass:1234
    }

    # Import code-signature certificate to keychain. Must be included and trusted
    # by the OS internals.
    # - First: The default keychain that under the user profile.
    importSelfSignCert() {

    local userDefaultKeychain="$1"

    echo "[+] Importing private key and self-signed certificate"
    security import pkcs.p12 -k "$userDefaultKeychain" -f pkcs12 -P 1234
    }

    # Equivalent to code-signature application in Sketch.
    # Sign Sketch with generated certificate.
    # - Parameters:
    # - First: The application bundle path.
    # - Second: The default keychain that under the user profile.
    signApplication() {

    local appPath="$1"

    local userDefaultKeychain="$2"

    echo "[+] Signing the patched *.app bundle. This may require root privilege."
    echo "[+] If asked, enter your login password. Choose \"Always Allow\" to \
    not be asked again."
    codesign --deep --force -s "sketchcrapp" "$appPath" --verbose --keychain "$userDefaultKeychain"
    if ! [ "$?" -eq "0" ]; then
    echo "[-] Failed to sign Sketch bundle."
    echo "[+] Automatic fix process started."
    echo "[+] Removing certificate ..."
    security delete-certificate -c "sketchcrapp" "$userDefaultKeychain"
    if ! [ "$?" -eq "0" ]; then
    echo "[-] Unable to delete <sketchcrapp> certificate from Keychain"
    clean
    finally 1
    fi

    echo "[+] Re-creating signature identity ..."
    genSelfSignCert

    echo "[+] Re-importing signature identity ..."
    importSelfSignCert "$userDefaultKeychain"

    echo "[+] Resigning application bundle again using signature identity ..."
    codesign --deep --force -s "sketchcrapp" "$appPath" --verbose --keychain "$userDefaultKeychain"
    if ! [ "$?" -eq "0" ]; then
    printf "[\e[38;5;9mERR\e[0m] Failed to sign Sketch bundle. Automatic method failed.\n"
    printf "[\e[38;5;11mINFO\e[0m] Copy the full log and open a new issue on GitHub \
    repository: https://github.com/duraki/SketchCrapp\n"
    clean
    finally 1
    fi

    fi
    }

    checkVersionSupported() {

    local versionString="$1"

    for versionElement in "${version_list[@]}"
    do
    if [ "$versionString" = "$versionElement" ]; then
    return 0
    fi
    done
    return 1
    }

    generateSelectVersionScript(){
    echo "[+] Generating swift script: target Version ..."
    cat <<'EOF' > /tmp/select.swift
    import Foundation
    var testString = ""
    while let line = readLine() {
    testString += "\(line)\n"
    }
    let pattern = #"v?(?:(\d\.\d\.\d)|(\d\d?\d?\.\d)|(\d\d))"#
    let regex = try! NSRegularExpression(pattern: pattern)
    let stringRange = NSRange(location: 0, length: testString.utf16.count)
    let matches = regex.matches(in: testString, range: stringRange)
    var versions: String
    versions="(null)"
    for match in matches {
    var groups: String = ""
    for rangeIndex in 1 ..< match.numberOfRanges {
    if match.range(at: rangeIndex).location > testString.utf16.count {
    continue
    }
    groups = (testString as NSString).substring(with: match.range(at: rangeIndex))
    }
    versions=groups
    }
    print(versions)
    EOF
    }

    generateSelectURLScript(){
    echo "[+] Generating swift script: target URL ..."
    cat <<'EOF' > /tmp/select.swift
    import Foundation
    var testString = ""
    while let line = readLine() {
    testString += "\(line)\n"
    }
    let pattern = #".*<enclosure url[^"]"(.*)" len.*\/>.*"#
    let regex = try! NSRegularExpression(pattern: pattern)
    let stringRange = NSRange(location: 0, length: testString.utf16.count)
    let matches = regex.matches(in: testString, range: stringRange)
    var urls: [String] = []
    for match in matches {
    var groups: String = ""
    for rangeIndex in 1 ..< match.numberOfRanges {
    if match.range(at: rangeIndex).location > testString.utf16.count {
    continue
    }
    groups = (testString as NSString).substring(with: match.range(at: rangeIndex))
    }
    urls.append(groups)
    }
    for u in urls {
    print(u)
    }
    EOF
    }

    getURLFromVersionString() {

    local versionString="$1"

    local testVersionString=""

    echo "[+] Checking if version $versionString is supported ..."

    if [ -z "$versionString" ]; then
    versionString="(null)"
    else
    generateSelectVersionScript
    testVersionString=$(echo $versionString | swift "/tmp/select.swift")
    checkVersionSupported "$testVersionString"
    fi

    if ! [ "$?" -eq "0" ] || [ -z $testVersionString ]; then
    printf "[\e[38;5;9mERR\e[0m] Version $testVersionString is not supported, \
    please carefully review README file again.\n"
    printf "[\e[38;5;11mINFO\e[0m] Copy the details below and open a new issue on GitHub \
    repository: https://github.com/duraki/SketchCrapp\n"
    echo "+==================================================================="
    echo "+ Issue details ‹s:sketchcrapp:VersionUserEnter›"
    echo "+ Passed version : $versionString"
    echo "+ Normalize version : $testVersionString"
    echo "+ Error : Version $testVersionString is not supported."
    echo "+==================================================================="
    clean
    finally 1
    fi

    local versionListXMLURL="https://download.sketch.com/sketch-versions.xml"

    echo "[+] Fetching $versionListXMLURL ... "
    curl -L "$versionListXMLURL" --output "/tmp/list.xml"

    if ! [ "$?" -eq "0" ]; then
    printf "[\e[38;5;9mERR\e[0m] Failed while downloading version list xml file!\n"
    printf "[\e[38;5;11mINFO\e[0m] Are you connected to the internet? Check your network connection.\n"
    clean
    finally 1
    fi

    generateSelectURLScript

    if ! [ -f "/tmp/select.swift" ]; then
    echo "[-] Swift script does not exists under the /tmp folder."
    printf "[\e[38;5;9mERR\e[0m] Couldn't find select.swift at /tmp/select.swift\n"
    printf "[\e[38;5;11mINFO\e[0m] We don't know what's going on, try to use -m to get latest patch.\n"
    clean
    finally 1
    fi

    LANG=en_GB.UTF-8
    OIFS=$IFS
    IFS=$'\n'
    testURL=$(cat /tmp/list.xml | \
    sed -e 's/^[[:space:]]*//g' | \
    sed -e 's/[[:space:]]*$//g'| \
    tr -d '\n' | \
    sed -e $'s/<enclos/\\\n<enclos/g' | \
    swift "/tmp/select.swift" | \
    grep "\-$testVersionString\-")
    IFS=$OIFS

    downloadURLString="$testURL"

    if [ -z "$downloadURLString" ]; then
    printf "[\e[38;5;9mERR\e[0m] Version $versionString is not supported, \
    please carefully review README file again.\n"
    printf "[\e[38;5;11mINFO\e[0m] Copy the details below and open a new issue on GitHub \
    repository: https://github.com/duraki/SketchCrapp\n"
    echo "+==================================================================="
    echo "+ Issue details ‹s:sketchcrapp:UnknowDownloadURLString"
    echo "+ Passed version : $versionString"
    echo "+ Normalize version : $testVersionString"
    echo "+ Error : Version $versionString is not supported."
    echo "+==================================================================="
    clean
    finally 1
    fi

    echo "[+] Download URL set to: $downloadURLString"
    }


    # Get binary hash from CFBundleShortVersionString
    # - Parameters:
    # - First: The application bundle CFBundleShortVersionString.
    getHashFromVersionString() {

    local bundleVersionString="$1"

    # RUP Review every time when new verison update part.
    case "$bundleVersionString" in
    "51.3")
    echo "$exe_hash_513"
    ;;
    "53")
    echo "$exe_hash_530"
    ;;
    "58")
    echo "$exe_hash_580"
    ;;
    "63.1")
    echo "$exe_hash_631"
    ;;
    "64")
    echo "$exe_hash_640"
    ;;
    "65.1")
    echo "$exe_hash_651"
    ;;
    "66.1")
    echo "$exe_hash_661"
    ;;
    "67")
    echo "$exe_hash_670"
    ;;
    "67.1")
    echo "$exe_hash_671"
    ;;
    "67.2")
    echo "$exe_hash_672"
    ;;
    "68")
    echo "$exe_hash_680"
    ;;
    "68.1")
    echo "$exe_hash_681"
    ;;
    "68.2")
    echo "$exe_hash_682"
    ;;
    "69")
    echo "$exe_hash_690"
    ;;
    "69.1")
    echo "$exe_hash_691"
    ;;
    "69.2")
    echo "$exe_hash_692"
    ;;
    "70.2")
    echo "$exe_hash_702"
    ;;
    "70.3")
    echo "$exe_hash_703"
    ;;
    "70.4")
    echo "$exe_hash_704"
    ;;
    "70.5")
    echo "$exe_hash_705"
    ;;
    "70.6")
    echo "$exe_hash_706"
    ;;
    "71.1")
    echo "$exe_hash_711"
    ;;
    "71.2")
    echo "$exe_hash_712"
    ;;
    *)
    echo "Input version string invaild, cannot lookup correct hash value."
    esac
    }

    # Verify the application by using hash value.
    # - Parameters:
    # - First: The application bundle path.
    analysisApplication() {

    echo "[+] Analysing application bundle ... Starting"

    local appPath="$1"

    printf "[+] Finding executable file ... "

    # Get the path of application executable.
    local execPath="$appPath/Contents/MacOS/Sketch"

    if ! [ -f "$execPath" ]; then
    printf "\e[38;5;9mError\e[0m\n"
    echo "[-] Executable file does not exists under the given application folder."
    printf "[\e[38;5;9mERR\e[0m] Couldn't find executable file at $execPath\n"
    printf "[\e[38;5;11mINFO\e[0m] Please make sure you pass clean app to script.\n"
    finally 1
    fi

    printf "\e[38;5;10mOK\e[0m\n"

    printf "[+] Finding Info.plist ... "

    # Get the path of application info plist.
    local infoPath="$appPath/Contents/Info"

    if ! [ -f "$infoPath.plist" ]; then
    printf "\e[38;5;9mError\e[0m\n"
    echo "[-] Info file does not exists under the given application folder."
    printf "[\e[38;5;9mERR\e[0m] Couldn't find Info.plist at $infoPath.plist\n"
    printf "[\e[38;5;11mINFO\e[0m] Please make sure you pass clean app to script.\n"
    finally 1
    fi

    printf "\e[38;5;10mOK\e[0m\n"

    printf "[+] Checking Info.plist for CFBundleShortVersionString ... "

    # Get the CFBundleShortVersionString from info plist.
    local bundleVersionString="$(defaults read $infoPath CFBundleShortVersionString)"

    if [ -z "$bundleVersionString" ]; then
    printf "\e[38;5;9mError\e[0m\n"
    printf "[\e[38;5;9mERR\e[0m] Couldn't find value of CFBundleShortVersionString\n"
    printf "[\e[38;5;11mINFO\e[0m] Please make sure you pass clean app to script.\n"
    finally 1
    fi

    printf "\e[38;5;10mOK\e[0m\n"

    # Get the hash of application executable
    local appSHA1="$(shasum -a 1 "$execPath" | cut -f 1 -d ' ')"

    checkVersionSupported "$bundleVersionString"

    if ! [ "$?" -eq "0" ]; then
    printf "[\e[38;5;9mERR\e[0m] Version $bundleVersionString is not supported, \
    please carefully review README file again.\n"
    printf "[\e[38;5;11mINFO\e[0m] Copy the details below and open a new issue on GitHub \
    repository: https://github.com/duraki/SketchCrapp\n"
    echo "+==================================================================="
    echo "+ Issue details ‹s:sketchcrapp›"
    echo "+ Application Path : $appPath"
    echo "+ Application Binary: $execPath"
    echo "+ Passed version : $bundleVersionString"
    echo "+ Binary SHA1 : $appSHA1"
    echo "+ Error : Version $bundleVersionString is not supported."
    echo "+==================================================================="
    finally 1
    fi

    printf "[+] Validating executable file ... "

    local testBundleVersionString=""
    # RUP Review every time when new verison update part.
    case "$appSHA1" in
    "$exe_hash_513")
    testBundleVersionString="51.3"
    ;;
    "$exe_hash_530")
    testBundleVersionString="53"
    ;;
    "$exe_hash_580")
    testBundleVersionString="58"
    ;;
    "$exe_hash_631")
    testBundleVersionString="63.1"
    ;;
    "$exe_hash_640")
    testBundleVersionString="64"
    ;;
    "$exe_hash_651")
    testBundleVersionString="65.1"
    ;;
    "$exe_hash_661")
    testBundleVersionString="66.1"
    ;;
    "$exe_hash_670")
    testBundleVersionString="67"
    ;;
    "$exe_hash_671")
    testBundleVersionString="67.1"
    ;;
    "$exe_hash_672")
    testBundleVersionString="67.2"
    ;;
    "$exe_hash_680")
    testBundleVersionString="68"
    ;;
    "$exe_hash_681")
    testBundleVersionString="68.1"
    ;;
    "$exe_hash_682")
    testBundleVersionString="68.2"
    ;;
    "$exe_hash_690")
    testBundleVersionString="69"
    ;;
    "$exe_hash_691")
    testBundleVersionString="69.1"
    ;;
    "$exe_hash_692")
    testBundleVersionString="69.2"
    ;;
    "$exe_hash_702")
    testBundleVersionString="70.2"
    ;;
    "$exe_hash_703")
    testBundleVersionString="70.3"
    ;;
    "$exe_hash_704")
    testBundleVersionString="70.4"
    ;;
    "$exe_hash_705")
    testBundleVersionString="70.5"
    ;;
    "$exe_hash_706")
    testBundleVersionString="70.6"
    ;;
    "$exe_hash_711")
    testBundleVersionString="71.1"
    ;;
    "$exe_hash_712")
    testBundleVersionString="71.2"
    ;;
    *)
    testBundleVersionString="binaryerr››"
    printf "\e[38;5;9mError\e[0m\n"
    printf "[\e[38;5;9mERR\e[0m] Can't find Sketch with that signature. Hash is invalid.\n"
    printf "[\e[38;5;11mINFO\e[0m] Carefully review README file again\n"
    printf "[\e[38;5;11mINFO\e[0m] If you still have problem copy the details below and open a new issue\n"
    printf "[\e[38;5;11mINFO\e[0m] on GitHub repository: https://github.com/duraki/SketchCrapp\n"
    echo "+==================================================================="
    echo "+ Application Path : $appPath"
    echo "+ Application Binary: $execPath"
    echo "+ Passed version : $bundleVersionString"
    echo "+ Correct hash : $(getHashFromVersionString "$bundleVersionString")"
    echo "+ Binary SHA1 : $appSHA1"
    echo "+ Error : Can't find Sketch with that signature. Hash is invalid."
    echo "+==================================================================="
    finally 1
    esac

    if [ "$bundleVersionString" = "$testBundleVersionString" ]; then
    printf "\e[38;5;10mOK\e[0m\n"
    engin "$bundleVersionString" "$appPath" "$execPath"
    else
    printf "\e[38;5;9mError\e[0m\n"
    printf "[\e[38;5;9mFATAL\e[0m] Executable SHA1 hash returned version value does not \
    equal to the CFBundleShortVersionString\n"
    printf "[\e[38;5;11mINFO\e[0m] Carefully review README file again, if you still have problem\n"
    printf "[\e[38;5;11mINFO\e[0m] open a new issue on GitHub repository: https://github.com/duraki/SketchCrapp\n"
    finally 1
    fi
    }

    # Patch ancient process.
    # - Parameters:
    # - First: An array of address of specific version.
    # - Second: A path of application executable to patch.
    patchAncient() {

    echo "Starting ancient arch patch via bash&seek ..."

    local addressArray=(${1})

    local execPath=${2}

    for i in {0..2}; do
    echo "[+] Patching address at offset: 0x${addressArray[$i]} \
    with value: ${value_ancient_param[$i]}"
    printf "${value_ancient_param[$i]}" | dd seek="$((0x${addressArray[$i]}))" conv=notrunc bs=1 of="$execPath"
    if ! [ "$?" -eq "0" ]; then
    printf "[\e[38;5;9mFATAL\e[0m] Patch process resulted in failure. That's all we know.\n"
    printf "[\e[38;5;11mINFO\e[0m] Open a new issue and tell us about this \
    on GitHub repository: https://github.com/duraki/SketchCrapp\n"
    finally 1
    fi
    done
    }

    # Patch old process.
    # - Parameters:
    # - First: An array of address of specific version.
    # - Second: A path of application executable to patch.
    patchOld() {

    echo "Starting old arch patch via bash&seek ..."

    local addressArray=(${1})

    local execPath=${2}

    for i in {0..3}; do
    echo "[+] Patching address at offset: 0x${addressArray[$i]} \
    with value: ${value_old_param[$i]}"
    printf "${value_old_param[$i]}" | dd seek="$((0x${addressArray[$i]}))" conv=notrunc bs=1 of="$execPath"
    if ! [ "$?" -eq "0" ]; then
    printf "[\e[38;5;9mFATAL\e[0m] Patch process resulted in failure. That's all we know.\n"
    printf "[\e[38;5;11mINFO\e[0m] Open a new issue and tell us about this \
    on GitHub repository: https://github.com/duraki/SketchCrapp\n"
    finally 1
    fi
    done
    }

    # Patch legacy process.
    # - Parameters:
    # - First: An array of address of specific version.
    # - Second: A path of application executable to patch.
    patchLegacy() {

    echo "Starting legacy arch patch via bash&seek ..."

    local addressArray=(${1})

    local execPath=${2}

    for i in {0..3}; do
    echo "[+] Patching address at offset: 0x${addressArray[$i]} \
    with value: ${value_legacy_param[$i]}"
    printf "${value_legacy_param[$i]}" | dd seek="$((0x${addressArray[$i]}))" conv=notrunc bs=1 of="$execPath"
    if ! [ "$?" -eq "0" ]; then
    printf "[\e[38;5;9mFATAL\e[0m] Patch process resulted in failure. That's all we know.\n"
    printf "[\e[38;5;11mINFO\e[0m] Open a new issue and tell us about this \
    on GitHub repository: https://github.com/duraki/SketchCrapp\n"
    finally 1
    fi
    done
    }

    # Patch process.
    # - Parameters:
    # - First: An array of address of specific version.
    # - Second: A path of application executable to patch.
    patch() {

    echo "Starting patch via bash&seek ..."

    local addressArray=(${1})

    local execPath=${2}

    for i in {0..11}; do
    echo "[+] Patching address at offset: 0x${addressArray[$i]} \
    with value: ${value_param[$i]}"
    printf "${value_param[$i]}" | dd seek="$((0x${addressArray[$i]}))" conv=notrunc bs=1 of="$execPath"
    if ! [ "$?" -eq "0" ]; then
    printf "[\e[38;5;9mFATAL\e[0m] Patch process resulted in failure. That's all we know.\n"
    printf "[\e[38;5;11mINFO\e[0m] Open a new issue and tell us about this \
    on GitHub repository: https://github.com/duraki/SketchCrapp\n"
    finally 1
    fi
    done
    }

    # Install and register cracktag + credits for ancient version
    nameTagAncient() {

    local appPath="$1"

    local LangPath="$appPath/Contents/Resources/"

    if ! [ -d "$LangPath" ]; then
    if [ -d "$appPath" ]; then
    rm -rf "$appPath"
    clean
    finally 1
    fi
    fi

    local LangFilePath="$LangPath/Localizable.strings"

    if ! [ -f "$LangFilePath" ]; then
    if [ -d "$appPath" ]; then
    rm -rf "$appPath"
    clean
    finally 1
    fi
    fi

    local LangFilePathBak="$LangPath/Localizable.strings.bak"

    mv "$LangFilePath" "$LangFilePathBak"

    cat "$LangFilePathBak" | iconv -f UTF-16LE -t UTF-8 | sed -e "s/1 Day Left/SketchCrapp/g" -e "s/ - Register Now//g" | iconv -f UTF-8 -t UTF-16LE > "$LangFilePath"

    rm -f "$LangFilePathBak"
    }

    # Install and register cracktag + credits for older version
    nameTagOld() {

    local appPath="$1"

    local LangPath="$appPath/Contents/Resources/en.lproj"

    if ! [ -d "$LangPath" ]; then
    if [ -d "$appPath" ]; then
    rm -rf "$appPath"
    clean
    finally 1
    fi
    fi

    local LangFilePath="$LangPath/Localizable.stringsdict"

    if ! [ -f "$LangFilePath" ]; then
    if [ -d "$appPath" ]; then
    rm -rf "$appPath"
    clean
    finally 1
    fi
    fi

    local LangFilePathBak="$LangPath/Localizable.stringsdict.bak"

    mv "$LangFilePath" "$LangFilePathBak"

    cat "$LangFilePathBak" | iconv -f UTF-16LE -t UTF-8 | sed -e "s/1 Day Left/SketchCrapp/g" | iconv -f UTF-8 -t UTF-16LE > "$LangFilePath"

    rm -f "$LangFilePathBak"
    }

    # Install and register cracktag + credits
    nameTag() {

    local appPath="$1"

    local engLangPath="$appPath/Contents/Resources/en.lproj"

    local zhLangPath="$appPath/Contents/Resources/zh-Hans.lproj"

    if ! [ -d "$engLangPath" ] || ! [ -d "$zhLangPath" ]; then
    if [ -d "$appPath" ]; then
    rm -rf "$appPath"
    clean
    finally 1
    fi
    fi

    echo '"gWf-Nl-VLs.title" = "Sketch Cloud is currently not supported by SketchCrapp. Sorry :(";
    "h0z-lo-HfZ.ibShadowedIsNilPlaceholder" = "@0xduraki & @elijahtsai";
    "h1X-KO-1Ed.title" = "Name";
    "Ja3-jS-bo6.title" = "Crack It";
    "kCf-Hg-sTQ.ibShadowedIsNilPlaceholder" = "Cracked by @0xduraki & @elijahtsai";
    "kUG-6E-w0F.ibShadowedIsNilPlaceholder" = "sketchcrapp@github.com";
    "mSb-kZ-v2e.ibShadowedLabels[0]" = "SketchCrapp";
    "mSb-kZ-v2e.ibShadowedLabels[1]" = "github.com/duraki/SketchCrapp";
    "oEr-Dw-WUj.title" = "Forgot to crack it?";
    "pfs-Mp-bgn.title" = "Wh00ps, what did you do :)";
    "yfB-jh-Is0.placeholderString" = "Cracked by @0xduraki & @elijahtsai";' > "$engLangPath/MSRegistrationWindow.strings"

    if ! [ -f "$zhLangPath/MSRegistrationWindow.strings" ]; then
    if [ -d "$appPath" ]; then
    rm -rf "$appPath"
    clean
    finally 1
    fi
    fi
    /usr/bin/sed -i -e 's/"gWf-Nl-VLs.title"\ =\ [^;]*/"gWf-Nl-VLs.title" = "SketchCrapp 還沒支援 Sketch Cloud, 再等等吧 :("/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"h0z-lo-HfZ.ibShadowedIsNilPlaceholder"\ =\ [^;]*/"h0z-lo-HfZ.ibShadowedIsNilPlaceholder" = "@0xduraki \& @elijahtsai"/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"h1X-KO-1Ed.title"\ =\ [^;]*/"h1X-KO-1Ed.title" = "名稱"/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"Ja3-jS-bo6.title"\ =\ [^;]*/"Ja3-jS-bo6.title" = "破解"/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"kCf-Hg-sTQ.ibShadowedIsNilPlaceholder"\ =\ [^;]*/"kCf-Hg-sTQ.ibShadowedIsNilPlaceholder" = "由 @0xduraki \& @elijahtsai 破解"/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"kUG-6E-w0F.ibShadowedIsNilPlaceholder"\ =\ [^;]*/"kUG-6E-w0F.ibShadowedIsNilPlaceholder" = "sketchcrapp@github.com"/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"mSb-kZ-v2e.ibShadowedLabels\[0\]"\ =\ [^;]*/"mSb-kZ-v2e.ibShadowedLabels[0]" = "SketchCrapp"/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"mSb-kZ-v2e.ibShadowedLabels\[1\]"\ =\ [^;]*/"mSb-kZ-v2e.ibShadowedLabels[1]" = "github.com\/duraki\/SketchCrapp"/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"oEr-Dw-WUj.title"\ =\ [^;]*/"oEr-Dw-WUj.title" = "忘記破解了嗎?"/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"pfs-Mp-bgn.title"\ =\ [^;]*/"pfs-Mp-bgn.title" = "哇!您做了什麼!"/g' "$zhLangPath/MSRegistrationWindow.strings"
    /usr/bin/sed -i -e 's/"yfB-jh-Is0.placeholderString"\ =\ [^;]*/"yfB-jh-Is0.placeholderString" = "由 @0xduraki \& @elijahtsai 破解"/g' "$zhLangPath/MSRegistrationWindow.strings"
    }

    # All the code and logic flow to patch the Sketch.app binary, do a code-signature
    # and link-resolve the patched Sketch.app.
    # - Parameters:
    # - First: A version of current application bundle.
    # - Second: An array of address of specific version.
    # - Third: A path of application executable to patch.
    engin() {

    local appVersion="$1"

    local appPath="$2"

    local execPath="$3"

    # Version Selector.
    echo "[+] Selected Sketch.app version is $appVersion ... SketchCrapp starting ... OK"
    echo "[+] Patching offsets for $appVersion ... "
    # RUP Review every time when new verison update part.
    case "$appVersion" in
    "51.3")
    patchAncient "${address_param_513[*]}" "$execPath"
    ;;
    "53")
    patchOld "${address_param_530[*]}" "$execPath"
    ;;
    "58")
    patchLegacy "${address_param_580[*]}" "$execPath"
    ;;
    "63.1")
    patchLegacy "${address_param_631[*]}" "$execPath"
    ;;
    "64")
    patchLegacy "${address_param_640[*]}" "$execPath"
    ;;
    "65.1")
    patchLegacy "${address_param_651[*]}" "$execPath"
    ;;
    "66.1")
    patchLegacy "${address_param_661[*]}" "$execPath"
    ;;
    "67"|"67.1")
    patchLegacy "${address_param_670[*]}" "$execPath"
    ;;
    "67.2")
    patchLegacy "${address_param_672[*]}" "$execPath"
    ;;
    "68")
    patchLegacy "${address_param_680[*]}" "$execPath"
    ;;
    "68.1")
    patchLegacy "${address_param_681[*]}" "$execPath"
    ;;
    "68.2")
    patchLegacy "${address_param_681[*]}" "$execPath"
    ;;
    "69")
    patchLegacy "${address_param_690[*]}" "$execPath"
    ;;
    "69.1")
    patchLegacy "${address_param_691[*]}" "$execPath"
    ;;
    "69.2")
    patchLegacy "${address_param_691[*]}" "$execPath"
    ;;
    "70.2")
    patch "${address_param_702[*]}" "$execPath"
    ;;
    "70.3")
    patch "${address_param_703[*]}" "$execPath"
    ;;
    "70.4")
    patch "${address_param_704[*]}" "$execPath"
    ;;
    "70.5")
    patch "${address_param_705[*]}" "$execPath"
    ;;
    "70.6")
    patch "${address_param_705[*]}" "$execPath"
    ;;
    "71.1")
    patch "${address_param_711[*]}" "$execPath"
    ;;
    "71.2")
    patch "${address_param_712[*]}" "$execPath"
    ;;
    *)
    printf "[\e[38;5;9mERR\e[0m] Something went wrong, this line should never execute.\n"
    printf "[\e[38;5;11mINFO\e[0m] Copy the details below and open a new issue on GitHub repository: \
    https://github.com/duraki/SketchCrapp\n"
    echo "+==================================================================="
    echo "+ Issue details ‹s:sketchcrapp›"
    echo "+ Application Path : $appPath"
    echo "+ Application Binary: $execPath"
    echo "+ Passed version : $bundleVersionString"
    echo "+ Binary SHA1 : $appSHA1"
    echo "+ Error : patcherr››"
    echo "+==================================================================="
    finally 1
    esac

    # Install name tag
    if [[ "${nametag_ancient_ver[*]}" =~ "$appVersion" ]]; then
    nameTagAncient "$appPath"
    elif [[ "${nametag_old_ver[*]}" =~ "$appVersion" ]]; then
    nameTagOld "$appPath"
    else
    nameTag "$appPath"
    fi

    # Get the path of user default keychain.
    userKeyChain="$(security default-keychain -d user | sed -e 's/^[ ]*//g' -e 's/\"//g')"

    printf "[+] Checking user default keychain ... "

    if ! [ -f "$userKeyChain" ]; then
    printf "\e[38;5;9mNot exist\e[0m\n"
    echo "[-] User default Keychain does not exist: $userKeyChain"
    clean
    finally 1
    fi

    printf "\e[38;5;10mExist\e[0m\n"

    # CodeSigning area.
    # Check if sketchcrapp certificate already exist.
    printf "[+] Checking SketchCrapp identity ... "
    security find-identity -p codesigning | grep sketchcrapp >/dev/null

    if ! [ "$?" -eq "0" ]; then
    printf "\e[38;5;11mNot exist\e[0m\n"
    # Certificate does not exist, generate one.
    genSelfSignCert

    # Import the certificate.
    importSelfSignCert "$userKeyChain"
    else
    printf "\e[38;5;10mExist\e[0m\n"
    echo "[+] Skipping certificate creation"
    fi

    # Sign the application.
    signApplication "$appPath" "$userKeyChain"

    # Call cleaner to do some housekeeping.
    clean
    printf "[+] SketchCrapp process \e[38;5;10mcompleted\e[0m. Sketch.app has been patched :)\n"
    printf "[+] \e[38;5;11m-- Notice: \e[0m\n"
    echo "[+] If a dialogue shows up with message: “Sketch 3.app” can’t be opened"
    echo "[+] please right-click the application and select open, "
    echo "[+] or go to Settings -› Security and allow opening Sketch.app application."
    echo "[+] "
    echo "[+] If you are using an old version and a dialogue shows up asking for password"
    echo "[+] about \"com.bohemiancoding.sketch3.HockeySDK\""
    echo "[+] please enter your login password. Choose \"Always Allow\" to not be asked again."
    echo ""
    echo "[+] SketchCrapp (A Sketch.app cracking tool)"
    echo "[+] https://github.com/duraki/SketchCrapp [by @duraki & @elijahtsai]"
    }

    # An auto function to patch latest app.
    magic() {

    local testVersionString="$1"

    if [ -z "$testVersionString" ]; then
    testVersionString="$latestVersion"
    fi
    echo "[+] Hello, The magic show is about to start! Are you ready?"

    getURLFromVersionString "$testVersionString"

    # Check if missing cURL
    if ! command -v curl &> /dev/null; then
    echo "cURL is not installed on your system."
    echo "This should not happen, macOS have cURL built-in."
    printf "[\e[38;5;11mFIX\e[0m] Try: brew install curl\n"
    printf "[\e[38;5;11mFIX\e[0m] Try: port install curl\n"
    printf "[\e[38;5;11mFIX\e[0m] Try: install cURL manually\n"
    finally 1;
    fi

    # Check if missing UNZIP
    if ! command -v unzip &> /dev/null; then
    echo "UNZIP is not installed on your system."
    echo "This should not happen, macOS have UNZIP built-in."
    printf "[\e[38;5;11mFIX\e[0m] Try: brew install unzip\n"
    printf "[\e[38;5;11mFIX\e[0m] Try: port install unzip\n"
    printf "[\e[38;5;11mFIX\e[0m] Try: install UNZIP manually\n"
    finally 1;
    fi

    printf "[+] Checking directory tmp existence ... "

    if ! [ -d /tmp ]; then
    printf "\e[38;5;9mError\e[0m\n"
    echo "Directory tmp does not exist."
    finally 1
    fi

    printf "\e[38;5;10mOK\e[0m\n"

    echo "[+] Fetching $downloadURLString ... "

    curl "$downloadURLString" --output "/tmp/app.zip"

    if ! [ "$?" -eq "0" ]; then
    echo "[-] Failed while downloading latest application version!"
    echo "[-] Are you connected to the internet? Check your network connection."
    clean
    finally 1
    fi

    echo -n "[+] Checking if Sketch.app exist in /tmp ... "
    if [ -d "/tmp/Sketch.app" ]; then
    echo "Exist. Removing."
    rm -rf "/tmp/Sketch.app"
    if ! [ "$?" -eq St"0" ]; then
    echo "[-] Can't remove existing Sketch.app from /tmp directory."
    clean
    finally 1
    fi
    else
    echo "Not exist. Continuous."
    fi

    unzip -q "/tmp/app.zip" -d "/tmp"

    if ! [ "$?" -eq "0" ]; then
    echo "[-] Can't unzip downloaded archived file of the latest application version."
    clean
    finally 1
    fi

    echo -n "[+] Checking if Sketch.app exist in /Applications ... "
    if [ -d "/Applications/Sketch.app" ]; then
    echo "Exist. Removing."
    rm -rf "/Applications/Sketch.app"
    if ! [ "$?" -eq "0" ]; then
    echo "Fail to remove exist Sketch.app in /Applications directory."
    clean
    finally 1
    fi
    else
    echo "Not exist. Continuous."
    fi

    printf "[+] Moving Sketch.app to /Applications directory ... "

    reason="$(mv "/tmp/Sketch.app" "/Applications" 2>&1)"

    if ! [ "$?" -eq "0" ]; then
    printf "\e[38;5;9mFail\e[0m\n"
    echo "[-] Failed while moving /tmp/Sketch.app to /Applications directory."
    printf "[\e[38;5;11mINFO\e[0m] Reason: $reason\n"
    clean
    finally 1
    fi

    printf "\e[38;5;10mSuccessfully.\e[0m\n"

    analysisApplication "/Applications/Sketch.app"
    }

    # Command Line Interface initialization.
    # Script startup point. How about we start from banner shell we?
    banner

    # Check if missing UNZIP
    if ! command -v sed &> /dev/null; then
    echo "SED is not installed on your system."
    echo "This should not happen, macOS have sed built-in."
    printf "[\e[38;5;11mFIX\e[0m] Try: brew install sed\n"
    printf "[\e[38;5;11mFIX\e[0m] Try: port install sed\n"
    printf "[\e[38;5;11mFIX\e[0m] Try: install SED manually\n"
    finally 1;
    fi

    # Check if missing OpenSSL library
    if ! command -v openssl &> /dev/null; then
    echo "OpenSSL is not installed on your system."
    echo "This should not happen, macOS have OpenSSL built-in."
    printf "[\e[38;5;11mFIX\e[0m] Try: brew install openssl\n"
    printf "[\e[38;5;11mFIX\e[0m] Try: port install openssl\n"
    printf "[\e[38;5;11mFIX\e[0m] Try: install openssl manually\n"
    finally 1;
    fi

    # If no option was given by default search /Application or ~/Application .
    if [ $# -eq 0 ]; then
    echo "SketchCrapp is finding application bundle path ..."
    if [ -d "/Applications/Sketch.app" ]; then
    # Sketch is found in /Application .
    echo "[+] Selected Sketch.app path is </Applications> (auto-detected) ... OK"
    analysisApplication "/Applications/Sketch.app"
    elif [ -d "$HOME/Applications/Sketch.app" ]; then
    # Sketch is found in ~/Application .
    echo "[+] Selected Sketch.app path is <$HOME/Applications> (auto-detected) ... OK"
    analysisApplication "$HOME/Applications/Sketch.app"
    else
    printf "[\e[38;5;9mERR\e[0m] Application not found in /Applications or ~/Applications\n"
    echo "Try: ./sketchcrapp -a /Custom/Path/For/Applications/Sketch.app"
    echo "To get the latest supported version of Sketch"
    echo "Try: ./sketchcrapp -m"
    finally 1
    fi
    finally 0
    fi

    # Option filter (Command-Line Interface parser).
    while getopts "ha:mg:" argv; do
    case "${argv}" in
    h)
    usage
    ;;
    a)
    appPath="${OPTARG}"
    if [ -d "$appPath" ]; then
    analysisApplication "$appPath"
    else
    printf "[\e[38;5;9mERR\e[0m] Given directory is either invaild or not exist.\n"
    finally 1
    fi
    ;;
    m)
    magic
    ;;
    g)
    version="${OPTARG}"
    magic "$version"
    ;;
    *)
    echo "Use -h for more information."
    finally 0
    ;;
    esac
    done

    finally 0