- Replace bad_query with forcequitOS/worse_query (App Group based) - Read the App Group from the code signature entitlements - Add pathleak container enumeration and iOS 27 system container lookup - Route descriptor/CarPlay writes through worse_query - Add sign_personal.sh and POCKET_POSTER_SELFTEST diagnostics - Keep bad_query as an iOS 26 fallback
276 lines
13 KiB
Swift
276 lines
13 KiB
Swift
//
|
|
// SettingsView.swift
|
|
// Pocket Poster
|
|
//
|
|
// Created by lemin on 6/1/25.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct SettingsView: View {
|
|
// Prefs
|
|
@AppStorage("pbHash") var pbHash: String = "" // PosterBoard hash
|
|
@AppStorage("cpHash") var cpHash: String = "" // CarPlay hash
|
|
@AppStorage("ignoreDurationLimit") var ignoreDurationLimit: Bool = false
|
|
|
|
@State var checkingForHash: Bool = false
|
|
@State var hashCheckTask: Task<Void, any Error>? = nil
|
|
@State var detectingOnDevice: Bool = false
|
|
|
|
var body: some View {
|
|
List {
|
|
Section {
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
TextField("Enter PosterBoard App Hash", text: $pbHash)
|
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
.font(.system(.body, design: .monospaced))
|
|
if CarPlayManager.supportsCarPlay() {
|
|
TextField("Enter CarPlayWallpaper App Hash", text: $cpHash)
|
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
.font(.system(.body, design: .monospaced))
|
|
}
|
|
|
|
if SymHandler.prefersSandboxEscape {
|
|
Text(SandboxEscape.isUsingWorseQuery
|
|
? "worse_query available — on-device detect works (no PC). App Group: \(SandboxEscape.appGroupIdentifier ?? "unknown")"
|
|
: "bad_query fallback available (iOS 26) — on-device detect works (no PC).")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
HStack {
|
|
Spacer()
|
|
// On-device detect via worse_query (iOS 26/27)
|
|
if SymHandler.prefersSandboxEscape {
|
|
Button(action: {
|
|
detectOnDevice()
|
|
}) {
|
|
if detectingOnDevice {
|
|
ProgressView()
|
|
} else {
|
|
Text("Detect On-Device")
|
|
}
|
|
}
|
|
.foregroundStyle(.blue)
|
|
.disabled(detectingOnDevice)
|
|
}
|
|
|
|
// Run task to check until file exists from Nugget pc over AFC
|
|
Button(action: {
|
|
if !FileManager.default.fileExists(atPath: SymHandler.getPosterBoardHashURL().path()) {
|
|
// don't show the alert because it is already there
|
|
UIApplication.shared.confirmAlert(title: NSLocalizedString("Waiting for app hash...", comment: ""), body: NSLocalizedString("Connect your device to Nugget and click the \"Pocket Poster Helper\" button.", comment: ""), confirmTitle: NSLocalizedString("Cancel", comment: ""), onOK: {
|
|
cancelWaitForHash()
|
|
}, noCancel: true)
|
|
}
|
|
startWaitForHash()
|
|
}) {
|
|
Text(SymHandler.prefersSandboxEscape ? "Wait for Nugget" : "Detect")
|
|
}
|
|
.foregroundStyle(.green)
|
|
.onChange(of: checkingForHash) { _ in
|
|
if !checkingForHash {
|
|
// hide ui alert
|
|
UIApplication.shared.dismissAlert(animated: true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} header: {
|
|
Label("App Hash", systemImage: "lock.app.dashed")
|
|
}
|
|
|
|
Section {
|
|
Toggle(isOn: $ignoreDurationLimit, label: {
|
|
Label("Disable Video Duration Limit", systemImage: "ruler")
|
|
})
|
|
} header: {
|
|
Label("Preferences", systemImage: "gear")
|
|
}
|
|
|
|
Section {
|
|
Button(action: {
|
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
|
UserDefaults.standard.set(false, forKey: "finishedTutorial")
|
|
}) {
|
|
Label("Replay Tutorial", systemImage: "questionmark.circle")
|
|
}
|
|
|
|
Button(action: {
|
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
|
do {
|
|
try PosterBoardManager.clearCache()
|
|
Haptic.shared.notify(.success)
|
|
UIApplication.shared.alert(title: NSLocalizedString("App Cache Successfully Cleared!", comment: ""), body: "")
|
|
} catch {
|
|
Haptic.shared.notify(.error)
|
|
UIApplication.shared.alert(body: error.localizedDescription)
|
|
}
|
|
}) {
|
|
Label("Clear App Cache", systemImage: "trash.circle")
|
|
}
|
|
.foregroundStyle(.red)
|
|
|
|
Button(action: {
|
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
|
UserDefaults.standard.set(nil, forKey: "ActiveCarPlayWallpapers")
|
|
try? FileManager.default.removeItem(at: CarPlayManager.getCarPlayPhotosURL())
|
|
Haptic.shared.notify(.success)
|
|
UIApplication.shared.alert(title: NSLocalizedString("CarPlay Applied Wallpapers Successfully Cleared!", comment: ""), body: "")
|
|
}) {
|
|
Label("Reset CarPlay Applied Wallpapers", systemImage: "trash.circle")
|
|
}
|
|
.foregroundStyle(.red)
|
|
} header: {
|
|
Label("Actions", systemImage: "gear")
|
|
}
|
|
|
|
// MARK: Links
|
|
Section {
|
|
if let scURL = URL(string: PosterBoardManager.ShortcutURL) {
|
|
Link(destination: scURL) {
|
|
Label("Download Fallback Shortcut", systemImage: "arrow.down.circle")
|
|
}
|
|
}
|
|
if let fbURL = URL(string: "shortcuts://run-shortcut?name=PosterBoard&input=text&text=troubleshoot") {
|
|
Link(destination: fbURL) {
|
|
Label("Create Additional Fallback Method", systemImage: "appclip")
|
|
}
|
|
}
|
|
if let nURL = URL(string: "https://github.com/leminlimez/Nugget") {
|
|
Link(destination: nURL) {
|
|
Label("Nugget GitHub", image: "github.fill")
|
|
}
|
|
}
|
|
} header: {
|
|
Label("Links", systemImage: "link")
|
|
}
|
|
|
|
// MARK: Socials
|
|
Section {
|
|
Link(destination: URL(string: "https://github.com/leminlimez/Pocket-Poster")!) {
|
|
Label("View on GitHub", image: "github.fill")
|
|
}
|
|
Link(destination: URL(string: "https://discord.gg/MN8JgqSAqT")!) {
|
|
Label("Join the Discord", image: "discord.fill")
|
|
}
|
|
Link(destination: URL(string: "https://ko-fi.com/leminlimez")!) {
|
|
Label("Support on Ko-Fi", image: "ko-fi")
|
|
}
|
|
} header: {
|
|
Label("Socials", systemImage: "globe")
|
|
}
|
|
|
|
// MARK: Credits
|
|
Section {
|
|
LinkCell(imageName: "Mak5er", url: "https://github.com/Mak5er", title: "Mak5er", contribution: "worse_query port · iOS 27.2 build", circle: true)
|
|
LinkCell(imageName: "leminlimez", url: "https://github.com/leminlimez", title: "LeminLimez", contribution: NSLocalizedString("Main Developer", comment: "leminlimez's contribution"), circle: true)
|
|
LinkCell(imageName: "serstars", url: "https://github.com/SerStars", title: "SerStars", contribution: NSLocalizedString("Website Designer", comment: ""), circle: true)
|
|
LinkCell(imageName: "Nathan", url: "https://github.com/verygenericname", title: "Nathan", contribution: NSLocalizedString("Exploit (.Trash)", comment: ""), circle: true)
|
|
LinkCell(imageName: "duy", url: "https://github.com/khanhduytran0", title: "DuyKhanhTran", contribution: NSLocalizedString("Exploit (.Trash)", comment: ""), circle: true)
|
|
LinkCell(imageName: "sky", url: "https://github.com/forcequitOS/worse_query", title: "forcequitOS", contribution: "worse_query (iOS 26/27)", circle: false)
|
|
LinkCell(imageName: "sky", url: "https://bsky.app/profile/did:plc:xykfeb7ieeo335g3aly6vev4", title: "dootskyre", contribution: NSLocalizedString("Fallback Shortcut Creator", comment: ""), circle: true)
|
|
LinkCell(imageName: "POEditor", url: "https://poeditor.com/join/project/MPZOsunwVj", title: NSLocalizedString("Community Translators", comment: ""), contribution: "POEditor")
|
|
} header: {
|
|
Label("Credits", systemImage: "wrench.and.screwdriver")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Scan containers on-device with worse_query / fsgetpath — no computer needed.
|
|
func detectOnDevice() {
|
|
detectingOnDevice = true
|
|
UIApplication.shared.alert(
|
|
title: "Scanning containers…",
|
|
body: "Looking for PosterBoard via worse_query. This may take a moment.",
|
|
animated: true,
|
|
withButton: false
|
|
)
|
|
|
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
var pb: String?
|
|
var cp: String?
|
|
var errMsg: String?
|
|
|
|
do {
|
|
pb = try SandboxEscape.findPosterBoardHash()
|
|
} catch {
|
|
errMsg = error.localizedDescription
|
|
}
|
|
|
|
if CarPlayManager.supportsCarPlay() {
|
|
cp = try? SandboxEscape.findCarPlayHash()
|
|
}
|
|
|
|
// Always show a closeable result alert (loading alert has no OK button —
|
|
// dismiss must complete before the next alert is presented).
|
|
DispatchQueue.main.async {
|
|
detectingOnDevice = false
|
|
|
|
if let pb {
|
|
pbHash = pb.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if let cp {
|
|
cpHash = cp.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
}
|
|
Haptic.shared.notify(.success)
|
|
let body = cp != nil
|
|
? "PosterBoard:\n\(pbHash)\n\nCarPlay:\n\(cpHash)"
|
|
: "PosterBoard:\n\(pbHash)"
|
|
// alert() auto-dismisses any previous (buttonless) sheet first
|
|
UIApplication.shared.alert(title: "Hash found!", body: body, withButton: true)
|
|
} else {
|
|
Haptic.shared.notify(.error)
|
|
UIApplication.shared.alert(
|
|
title: "Detect failed",
|
|
body: errMsg ?? "Could not find PosterBoard container. Open Wallpaper settings once, then retry.",
|
|
withButton: true
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func startWaitForHash() {
|
|
checkingForHash = true
|
|
hashCheckTask = Task {
|
|
let filePath = SymHandler.getPosterBoardHashURL()
|
|
while !FileManager.default.fileExists(atPath: filePath.path()) {
|
|
try? await Task.sleep(nanoseconds: 500_000_000) // Sleep 0.5s
|
|
try Task.checkCancellation()
|
|
}
|
|
|
|
do {
|
|
let contents = try String(contentsOf: filePath)
|
|
try? FileManager.default.removeItem(at: filePath)
|
|
await MainActor.run {
|
|
pbHash = contents
|
|
}
|
|
// check for carplay hash
|
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
|
let carplayPath = SymHandler.getCarPlayHashURL()
|
|
if FileManager.default.fileExists(atPath: carplayPath.path()) {
|
|
let carplayContents = try String(contentsOf: carplayPath)
|
|
try? FileManager.default.removeItem(at: carplayPath)
|
|
await MainActor.run {
|
|
cpHash = carplayContents
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
print(error.localizedDescription)
|
|
}
|
|
|
|
await MainActor.run {
|
|
checkingForHash = false
|
|
hashCheckTask = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func cancelWaitForHash() {
|
|
hashCheckTask?.cancel()
|
|
hashCheckTask = nil
|
|
checkingForHash = false
|
|
}
|
|
}
|