前言

Cydia Impactor 已經停止更新好一段時間了,所幸當時還在越獄環境下,即時裝上 ReProvision 代替。

寒假出國一趟,回來發現 ReProvision 也失效了,且原作者似乎不打算再繼續更新了。

最後只能試試皮樂大大的 怪招,沒想到成功了!不但可以使用,皮樂大大也將其開源。

重簽 IPA 輸入 Apple 帳號密碼的程序,從 Cydia Impactor 時代就一直讓我感到些許麻煩。

我習慣使用 unc0ver 3.5.5 版本,跟皮樂大大預設的 unc0ver 版本不同,需自行輸入網址。

加上 AltServer 是於程式執行中下載 IPA 安裝檔,網速不理想的話容易導致連線階段逾時。

既然程式碼已經開源,我打算再將其魔改一次,將一些繁瑣的過程自動化。


實作過程

準備專案檔案

於終端機下指令,下載經皮樂大大修改過的 AltStore(jailbreak 分支):

git clone --recursive https://github.com/hirakujira/AltStore-Jailbreak.git -b jailbreak

下載完畢後進入專案目錄,使用 Carthage 下載所有相依類庫:

cd AltStore-Jailbreak
carthage update

最後,以 Xcode 開啟.xcworkspace,而非.xcodeproj,並更改設定如下:


修改 AltStore/AltServer/AppDelegate.swift

這邊部分修改installAltStore()函式,按下 Install 按鈕後自動開啟郵件,預設輸入 Apple ID 和密碼。

並將變數ipaUrl設為 unc0ver(Undecimus-v3.5.5.ipa)的本機路徑。

// ...
@objc func installAltStore(_ item: NSMenuItem)
{
    guard case let index = self.connectedDevicesMenu.index(of: item), index != -1 else { return }

//        自動開啟郵件
    NSWorkspace.shared.launchApplication("Mail")
    let alert = NSAlert()
    alert.messageText = NSLocalizedString("Please enter your Apple ID and password.", comment: "")
    alert.informativeText = NSLocalizedString("Your Apple ID and password are not saved and are only sent to Apple for authentication.", comment: "")

    let textFieldSize = NSSize(width: 300, height: 22)

    let appleIDTextField = NSTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
    appleIDTextField.delegate = self
    appleIDTextField.translatesAutoresizingMaskIntoConstraints = false
    appleIDTextField.placeholderString = NSLocalizedString("Apple ID", comment: "")
//        預設輸入 Apple ID
    appleIDTextField.stringValue = "[email protected]"
    alert.window.initialFirstResponder = appleIDTextField
    self.authenticationAppleIDTextField = appleIDTextField

    let passwordTextField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
    passwordTextField.delegate = self
    passwordTextField.translatesAutoresizingMaskIntoConstraints = false
    passwordTextField.placeholderString = NSLocalizedString("Password", comment: "")
//        預設輸入密碼
    passwordTextField.stringValue = "your_apple_id_password"
    self.authenticationPasswordTextField = passwordTextField

    let customUrlTextField = NSTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
    customUrlTextField.delegate = self
    customUrlTextField.translatesAutoresizingMaskIntoConstraints = false
    customUrlTextField.placeholderString = NSLocalizedString("HTTPS IPA file URL", comment: "")
    customUrlTextField.isHidden = true
    self.customUrlTextField = customUrlTextField

    let jailbreakPicker = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 300, height: 22))
//        jailbreakPicker.addItems(withTitles: ["Unc0ver", "Chimera", "Electra", "Home Depot", "Phoenix", "h3lix", "Pangu", "Custom..."])
    jailbreakPicker.addItems(withTitles: ["unc0ver v3.5.5"])
    jailbreakPicker.action = #selector(jailbreakPickerChanged)
    self.jailbreakPicker = jailbreakPicker

    appleIDTextField.nextKeyView = passwordTextField
    passwordTextField.nextKeyView = jailbreakPicker

    let stackView = NSStackView(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height * 4))
    stackView.orientation = .vertical
    stackView.distribution = .equalSpacing
    stackView.spacing = 0
    stackView.addArrangedSubview(appleIDTextField)
    stackView.addArrangedSubview(passwordTextField)
    stackView.addArrangedSubview(customUrlTextField)
    stackView.addArrangedSubview(jailbreakPicker)
    alert.accessoryView = stackView

    alert.addButton(withTitle: NSLocalizedString("Install", comment: ""))
    alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))

    self.authenticationAlert = alert
    self.validate()

    NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)

    let response = alert.runModal()
    guard response == .alertFirstButtonReturn else { return }

    let username = appleIDTextField.stringValue
    let password = passwordTextField.stringValue
    var ipaUrl = ""

    switch jailbreakPicker.selectedItem?.title {
    case "unc0ver v3.5.5":
        ipaUrl = "/Users/yuripeyamashita/Desktop/資料/Dev-iOS/unc0ver/Undecimus-v3.5.5.ipa"
        break
//        case "Chimera":
//            ipaUrl = "https://chimera.sh/downloads/ios/1.3.9-12.0-12.4.ipa"
//            break
//        case "Electra":
//            ipaUrl = "https://github.com/coolstar/electra-ipas/raw/master/Electra1141-1.3.2.ipa"
//            break
//        case "Pangu":
//            ipaUrl = "https://gginin.de/jb/NvwaStone/NvwaStone_1.1.ipa"
//            break
//        case "h3lix":
//            ipaUrl = "https://gginin.de/jb/h3lix/h3lix-RC6.ipa"
//            break
//        case "Phoenix":
//            ipaUrl = "https://gginin.de/jb/Phoenix/Phoenix5.ipa"
//            break
//        case "Home Depot":
//            ipaUrl = "https://gginin.de/jb/HomeDepot/MixtapePlayerRC3.ipa"
//            break
//        case "Custom...":
//            ipaUrl = self.customUrlTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
//            break
    default:
        print("None")
        break
    }
// ...
}
// ...

修改 AltStore/AltServer/Devices/ ALTDeviceManager+Installation.swift

註解掉下載 IPA 的功能函式downloadApp()與原本刪除暫存 IPA 的工序。

並將變數fileURL改為本機 unc0ver 路徑:ipaUrl

// ...
//self.downloadApp { (result) in
    do
    {
//        let fileURL = try result.get()
        let fileURL = URL(fileURLWithPath: ipaUrl)

        try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil)

        let appBundleURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: destinationDirectoryURL)

//        do
//        {
//            try FileManager.default.removeItem(at: fileURL)
//        }
//        catch
//        {
//          print("Failed to remove downloaded .ipa.", error)
//        }

        guard let application = ALTApplication(fileURL: appBundleURL) else { throw ALTError(.invalidApp) }

        self.registerAppID(name: "AltStore", identifier: "com.rileytestut.AltStore", team: team, session: session) { (result) in
            do
            {
                let appID = try result.get()

                self.updateFeatures(for: appID, app: application, team: team, session: session) { (result) in
                    do
                    {
                        let appID = try result.get()

                        self.fetchProvisioningProfile(for: appID, team: team, session: session) { (result) in
                            do
                            {
                                let provisioningProfile = try result.get()

                                self.install(application, to: device, team: team, appID: appID, certificate: certificate, profile: provisioningProfile) { (result) in
                                    finish(result.error, title: "Failed to Install AltStore")
                                }
                            }
                            catch
                            {
                                finish(error, title: "Failed to Fetch Provisioning Profile")
                            }
                        }
                    }
                    catch
                    {
                        finish(error, title: "Failed to Update App ID")
                    }
                }
            }
            catch
            {
                finish(error, title: "Failed to Register App")
            }
        }
    }
    catch
    {
        finish(error, title: "Failed to Download AltStore")
        return
    }
//}
// ...

修改完成後,Command-R執行,順利執行的話就可以於專案目錄下的Build/Products找到 .app。