From 5f8e6f2bf10a8d1fdb00feac9fc457c5f33f2578 Mon Sep 17 00:00:00 2001 From: Yann Renard Date: Wed, 22 Jul 2026 21:46:07 +0200 Subject: [PATCH] feat: add PPQ protection mode picker (Original vs Ryuk) Replace the boolean PPQ Protection toggle with a picker offering two modes: - Original: keeps the app's original bundle identifier unchanged - Ryuk: transforms the identifier (ryuk prefix, keyword replacement, PPQ suffix) This has been a highly requested feature by many users who want the flexibility to keep the original bundle identifier when desired. The Ryuk method remains the default for backward compatibility. Migration from old Bool format is handled silently in the decoder. --- .../Backend/Observable/OptionsManager.swift | 24 +++++++++++++++---- .../Signing/Shared/SigningOptionsView.swift | 8 +++---- RyukSign/Views/Signing/SigningView.swift | 6 ++--- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/RyukSign/Backend/Observable/OptionsManager.swift b/RyukSign/Backend/Observable/OptionsManager.swift index e31c441..19dbf14 100644 --- a/RyukSign/Backend/Observable/OptionsManager.swift +++ b/RyukSign/Backend/Observable/OptionsManager.swift @@ -70,8 +70,8 @@ struct Options: Codable, Equatable { var injectFolder: InjectFolder /// Random string appended to the app identifier var ppqString: String - /// Basic protection against PPQ - var ppqProtection: Bool + /// Protection mode against PPQ + var ppqProtection: PPQProtection /// (Better) protection against PPQ var dynamicProtection: Bool /// App identifiers list which matches and replaces @@ -119,7 +119,7 @@ struct Options: Codable, Equatable { var post_installAppAfterSigned: Bool /// This will delete your imported application after signing, to save on using unneeded space. var post_deleteAppAfterSigned: Bool - + // MARK: - Defaults static let defaultOptions = Options( @@ -134,7 +134,7 @@ struct Options: Codable, Equatable { injectPath: .executable_path, injectFolder: .frameworks, ppqString: "AN0X1", - ppqProtection: true, + ppqProtection: .ryuk, dynamicProtection: false, identifiers: [:], displayNames: [:], @@ -220,6 +220,20 @@ struct Options: Codable, Equatable { case root = "/" case frameworks = "/Frameworks/" } + + enum PPQProtection: String, Codable, CaseIterable, LocalizedDescribable { + /// Keep the original bundle identifier (no transformation) + case `default` + /// Ryuk method: prefix with "ryuk", replace known keywords, append ppqString + case ryuk = "Ryuk" + + var localizedDescription: String { + switch self { + case .default: .localized("Original") + case .ryuk: "Ryuk" + } + } + } /// Default random value for `ppqString` static func randomString() -> String { @@ -244,7 +258,7 @@ extension Options { injectPath = try c.decodeIfPresent(InjectPath.self, forKey: .injectPath) ?? d.injectPath injectFolder = try c.decodeIfPresent(InjectFolder.self, forKey: .injectFolder) ?? d.injectFolder ppqString = try c.decodeIfPresent(String.self, forKey: .ppqString) ?? d.ppqString - ppqProtection = try c.decodeIfPresent(Bool.self, forKey: .ppqProtection) ?? d.ppqProtection + ppqProtection = (try? c.decode(PPQProtection.self, forKey: .ppqProtection)) ?? d.ppqProtection dynamicProtection = try c.decodeIfPresent(Bool.self, forKey: .dynamicProtection) ?? d.dynamicProtection identifiers = try c.decodeIfPresent([String: String].self, forKey: .identifiers) ?? d.identifiers displayNames = try c.decodeIfPresent([String: String].self, forKey: .displayNames) ?? d.displayNames diff --git a/RyukSign/Views/Signing/Shared/SigningOptionsView.swift b/RyukSign/Views/Signing/Shared/SigningOptionsView.swift index 05fdc63..18c9cb9 100644 --- a/RyukSign/Views/Signing/Shared/SigningOptionsView.swift +++ b/RyukSign/Views/Signing/Shared/SigningOptionsView.swift @@ -17,14 +17,14 @@ struct SigningOptionsView: View { var body: some View { if (temporaryOptions == nil) { NBSection(.localized("Protection")) { - _toggle( + Self.picker( .localized("PPQ Protection"), systemImage: "shield", - isOn: $options.ppqProtection, - temporaryValue: temporaryOptions?.ppqProtection + selection: $options.ppqProtection, + values: Options.PPQProtection.allCases ) } footer: { - Text(.localized("Enabling any protection will append a random string to the bundleidentifiers of the apps you sign, this is to ensure your Apple ID does not get flagged by Apple. However, when using a signing service you can ignore this.")) + Text(.localized("Default keeps the original bundle identifier unchanged. Ryuk transforms it (ryuk prefix, keyword replacement, PPQ suffix) to help prevent your Apple ID from being flagged by Apple. When using a signing service you can ignore this.")) } } diff --git a/RyukSign/Views/Signing/SigningView.swift b/RyukSign/Views/Signing/SigningView.swift index dbd49a7..9c92a15 100644 --- a/RyukSign/Views/Signing/SigningView.swift +++ b/RyukSign/Views/Signing/SigningView.swift @@ -168,10 +168,8 @@ struct SigningView: View { // ppq protection if - _optionsManager.options.ppqProtection, - let identifier = app.identifier, - let cert = _selectedCert(), - cert.ppQCheck + _optionsManager.options.ppqProtection == .ryuk, + let identifier = app.identifier { var modifiedId = identifier .replacingOccurrences(of: "google", with: "ryu", options: .caseInsensitive)