From 0da743e9a666cabc0c257394201dc3432e626986 Mon Sep 17 00:00:00 2001 From: Magesh K <47920326+mahee96@users.noreply.github.com> Date: Tue, 14 Jan 2025 07:23:23 +0530 Subject: [PATCH] [diagnostics]: Added switches for OperationLogging to use them for debugging/diagnostics on device --- AltStore.xcodeproj/project.pbxproj | 38 ++- .../FetchAnisetteDataOperation.swift | 119 ++++---- AltStore/Operations/Operation.swift | 3 +- .../OperationsLoggingContolView.swift | 277 ++++++++++++++++++ AltStore/Settings/Settings.storyboard | 65 +++- .../Settings/SettingsViewController.swift | 8 + .../operations/OperationsLoggingControl.swift | 57 ++++ 7 files changed, 485 insertions(+), 82 deletions(-) create mode 100644 AltStore/Settings/OperationsLoggingContolView.swift create mode 100644 SideStore/Utils/dignostics/operations/OperationsLoggingControl.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index f6def7c5..cdf8e34b 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -65,6 +65,8 @@ A859ED5D2D1EE827003DCC58 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A868CFE42D31999A002F1201 /* SingletonGenericMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A868CFE32D319988002F1201 /* SingletonGenericMap.swift */; }; A8696EE42D34512C00E96389 /* RemoveAppExtensionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */; }; + A88B8C492D35AD3200F53F9D /* OperationsLoggingContolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88B8C482D35AD3200F53F9D /* OperationsLoggingContolView.swift */; }; + A88B8C552D35F1EC00F53F9D /* OperationsLoggingControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88B8C542D35F1EC00F53F9D /* OperationsLoggingControl.swift */; }; A8945AA62D059B6100D86CBE /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8945AA52D059B6100D86CBE /* Roxas.framework */; }; A8A543302D04F14400D72399 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8A5432F2D04F0C100D72399 /* libfragmentzip.a */; }; A8A853AF2D3065A300995795 /* ActiveAppsTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A853AE2D3065A300995795 /* ActiveAppsTimelineProvider.swift */; }; @@ -647,6 +649,8 @@ A86202332D1F35640091187B /* AltStoreCore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStoreCore.xcconfig; sourceTree = ""; }; A868CFE32D319988002F1201 /* SingletonGenericMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingletonGenericMap.swift; sourceTree = ""; }; A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAppExtensionsOperation.swift; sourceTree = ""; }; + A88B8C482D35AD3200F53F9D /* OperationsLoggingContolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationsLoggingContolView.swift; sourceTree = ""; }; + A88B8C542D35F1EC00F53F9D /* OperationsLoggingControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationsLoggingControl.swift; sourceTree = ""; }; A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A8A853AE2D3065A300995795 /* ActiveAppsTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveAppsTimelineProvider.swift; sourceTree = ""; }; A8AD35582D31BF29003A28B4 /* PageInfoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageInfoManager.swift; sourceTree = ""; }; @@ -1211,6 +1215,14 @@ path = xcconfigs; sourceTree = ""; }; + A88B8C532D35F1E800F53F9D /* operations */ = { + isa = PBXGroup; + children = ( + A88B8C542D35F1EC00F53F9D /* OperationsLoggingControl.swift */, + ); + path = operations; + sourceTree = ""; + }; A8A543222D04F0C100D72399 /* Products */ = { isa = PBXGroup; children = ( @@ -1249,6 +1261,7 @@ A8B516DE2D2666900047047C /* dignostics */ = { isa = PBXGroup; children = ( + A88B8C532D35F1E800F53F9D /* operations */, A8B516DF2D2666A00047047C /* database */, ); path = dignostics; @@ -1995,6 +2008,7 @@ BFDB69FB22A9A7A6007EA6D6 /* Settings */ = { isa = PBXGroup; children = ( + A88B8C482D35AD3200F53F9D /* OperationsLoggingContolView.swift */, BFE60737231ADF49002B0E8E /* Settings.storyboard */, BFE60739231ADF82002B0E8E /* SettingsViewController.swift */, 0EA426392C2230150026D7FB /* AnisetteServerList.swift */, @@ -2018,30 +2032,30 @@ children = ( D513F6152A12CE210061EAA1 /* Errors */, BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */, - BF770E5722BC3D0F002A40FE /* RefreshGroup.swift */, BF770E5322BC044E002A40FE /* OperationContexts.swift */, + BF770E5722BC3D0F002A40FE /* RefreshGroup.swift */, BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */, + D561AF812B21669400BF59C6 /* VerifyAppPledgeOperation.swift */, BFC1F38C22AEE3A4003AC21A /* DownloadAppOperation.swift */, - BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */, + BFCCB519245E3401001853EA /* VerifyAppOperation.swift */, + A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */, + BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */, BF3BEFBE2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift */, - BF3BEFC024086A1E00DE7D55 /* RefreshAppOperation.swift */, + BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */, BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */, BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */, - BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */, - BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */, + BF3BEFC024086A1E00DE7D55 /* RefreshAppOperation.swift */, BF56D2AB23DF8E170006506D /* FetchAppIDsOperation.swift */, - BFC57A642416C72400EB891E /* DeactivateAppOperation.swift */, - BFCCB519245E3401001853EA /* VerifyAppOperation.swift */, - BF44EEFB246B4550002A52F2 /* RemoveAppOperation.swift */, + BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */, + D5E1E7C028077DE90016FC96 /* UpdateKnownSourcesOperation.swift */, BF3432FA246B894F0052F4A1 /* BackupAppOperation.swift */, BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */, + BF44EEFB246B4550002A52F2 /* RemoveAppOperation.swift */, + BFC57A642416C72400EB891E /* DeactivateAppOperation.swift */, BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */, D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */, D5DAE0952804DF430034D8D4 /* UpdatePatronsOperation.swift */, - D5E1E7C028077DE90016FC96 /* UpdateKnownSourcesOperation.swift */, D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */, - D561AF812B21669400BF59C6 /* VerifyAppPledgeOperation.swift */, - A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */, BF7B44062725A4B8005288A4 /* Patch App */, ); path = Operations; @@ -3063,6 +3077,7 @@ BF770E5822BC3D0F002A40FE /* RefreshGroup.swift in Sources */, 19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */, 0E13E5862CC8F55900E9C0DF /* ProcessInfo+SideStore.swift in Sources */, + A88B8C492D35AD3200F53F9D /* OperationsLoggingContolView.swift in Sources */, D59162AB29BA60A9005CBF47 /* SourceHeaderView.swift in Sources */, BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */, BF3D649F22E7B24C00E9056B /* CollapsingTextView.swift in Sources */, @@ -3072,6 +3087,7 @@ BFC84A4D2421A19100853474 /* SourcesViewController.swift in Sources */, BFF0B696232242D3007A79E1 /* LicensesViewController.swift in Sources */, D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */, + A88B8C552D35F1EC00F53F9D /* OperationsLoggingControl.swift in Sources */, D50107EC2ADF2E1A0069F2A1 /* AddSourceTextFieldCell.swift in Sources */, D5151BD92A8FF64300C96F28 /* RefreshAllAppsIntent.swift in Sources */, BFBE0007250AD0E70080826E /* ViewAppIntentHandler.swift in Sources */, diff --git a/AltStore/Operations/FetchAnisetteDataOperation.swift b/AltStore/Operations/FetchAnisetteDataOperation.swift index 37d25eb1..93e10383 100644 --- a/AltStore/Operations/FetchAnisetteDataOperation.swift +++ b/AltStore/Operations/FetchAnisetteDataOperation.swift @@ -14,6 +14,8 @@ import AltStoreCore import AltSign import Roxas +class ANISETTE_VERBOSITY: Operation {} // dummy tag iface + @objc(FetchAnisetteDataOperation) final class FetchAnisetteDataOperation: ResultOperation, WebSocketDelegate { @@ -58,7 +60,7 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc UserDefaults.standard.menuAnisetteURL = urlString let url = URL(string: urlString) self.url = url - print("Anisette URL: \(self.url!.absoluteString)") + self.printOut("Anisette URL: \(self.url!.absoluteString)") if let identifier = Keychain.shared.identifier, let adiPb = Keychain.shared.adiPb { @@ -107,7 +109,7 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc guard let url = URL(string: currentServerUrlString) else { // Invalid URL, skip to next let errmsg = "Skipping invalid URL: \(currentServerUrlString)" - print(errmsg) + self.printOut(errmsg) showToast(viewContext: viewContext, message: errmsg) tryNextServer(from: serverUrls, viewContext, currentIndex: currentIndex + 1, completion: completion) return @@ -118,7 +120,7 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc if success { // If the server is reachable, return the URL let okmsg = "Found working server: \(url.absoluteString)" - print(okmsg) + self.printOut(okmsg) if(currentIndex > 0){ // notify user if available server is different the user-specified one self.showToast(viewContext: viewContext, message: okmsg) @@ -127,7 +129,7 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc } else { // If not, try the next URL let errmsg = "Failed to reach server: \(url.absoluteString), trying next server." - print(errmsg) + self.printOut(errmsg) self.showToast(viewContext: viewContext, message: errmsg) self.tryNextServer(from: serverUrls, viewContext, currentIndex: currentIndex + 1, completion: completion) } @@ -170,10 +172,10 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc if v3 { if json["result"] == "GetHeadersError" { let message = json["message"] - print("Error getting V3 headers: \(message ?? "no message")") + self.printOut("Error getting V3 headers: \(message ?? "no message")") if let message = message, message.contains("-45061") { - print("Error message contains -45061 (not provisioned), resetting adi.pb and retrying") + self.printOut("Error message contains -45061 (not provisioned), resetting adi.pb and retrying") Keychain.shared.adiPb = nil return provision() } else { throw OperationError.anisetteV3Error(message: message ?? "Unknown error") } @@ -214,16 +216,16 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc if let response = response, let version = response.value(forHTTPHeaderField: "Implementation-Version") { - print("Implementation-Version: \(version)") - } else { print("No Implementation-Version header") } + self.printOut("Implementation-Version: \(version)") + } else { self.printOut("No Implementation-Version header") } - print("Anisette used: \(formattedJSON)") - print("Original JSON: \(json)") + self.printOut("Anisette used: \(formattedJSON)") + self.printOut("Original JSON: \(json)") if let anisette = ALTAnisetteData(json: formattedJSON) { - print("Anisette is valid!") + self.printOut("Anisette is valid!") self.finish(.success(anisette)) } else { - print("Anisette is invalid!!!!") + self.printOut("Anisette is invalid!!!!") if v3 { throw OperationError.anisetteV3Error(message: "Invalid anisette (the returned data may not have all the required fields)") } else { @@ -242,22 +244,22 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc // MARK: - V1 func handleV1() { - print("Server is V1") + self.printOut("Server is V1") if UserDefaults.shared.trustedServerURL == AnisetteManager.currentURLString { - print("Server has already been trusted, fetching anisette") + self.printOut("Server has already been trusted, fetching anisette") return self.fetchAnisetteV1() } - print("Alerting user about outdated server") + self.printOut("Alerting user about outdated server") let alert = UIAlertController(title: "WARNING: Outdated anisette server", message: "We've detected you are using an older anisette server. Using this server has a higher likelihood of locking your account and causing other issues. Are you sure you want to continue?", preferredStyle: UIAlertController.Style.alert) alert.addAction(UIAlertAction(title: "Continue", style: UIAlertAction.Style.destructive, handler: { action in - print("Fetching anisette via V1") + self.printOut("Fetching anisette via V1") UserDefaults.shared.trustedServerURL = AnisetteManager.currentURLString self.fetchAnisetteV1() })) alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: { action in - print("Cancelled anisette operation") + self.printOut("Cancelled anisette operation") self.finish(.failure(OperationError.cancelled)) })) @@ -273,14 +275,14 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc } func fetchAnisetteV1() { - print("Fetching anisette V1") + self.printOut("Fetching anisette V1") URLSession.shared.dataTask(with: self.url!) { data, response, error in do { guard let data = data, error == nil else { throw OperationError.anisetteV1Error(message: "Unable to fetch data\(error != nil ? " (\(error!.localizedDescription))" : "")") } try self.extractAnisetteData(data, response as? HTTPURLResponse, v3: false) } catch let error as NSError { - print("Failed to load: \(error.localizedDescription)") + self.printOut("Failed to load: \(error.localizedDescription)") self.finish(.failure(error)) } }.resume() @@ -290,7 +292,7 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc func provision() { fetchClientInfo { - print("Getting provisioning URLs") + self.printOut("Getting provisioning URLs") var request = self.buildAppleRequest(url: URL(string: "https://gsa.apple.com/grandslam/GsService2/lookup")!) request.httpMethod = "GET" URLSession.shared.dataTask(with: request) { data, response, error in @@ -302,12 +304,12 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc let endProvisioningURL = URL(string: endProvisioningString) { self.startProvisioningURL = startProvisioningURL self.endProvisioningURL = endProvisioningURL - print("startProvisioningURL: \(self.startProvisioningURL!.absoluteString)") - print("endProvisioningURL: \(self.endProvisioningURL!.absoluteString)") - print("Starting a provisioning session") + self.printOut("startProvisioningURL: \(self.startProvisioningURL!.absoluteString)") + self.printOut("endProvisioningURL: \(self.endProvisioningURL!.absoluteString)") + self.printOut("Starting a provisioning session") self.startProvisioningSession() } else { - print("Apple didn't give valid URLs! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")") + self.printOut("Apple didn't give valid URLs! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")") self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid URLs. Please try again later", message: nil))) } }.resume() @@ -329,19 +331,19 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc do { if let json = try JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: []) as? [String: Any] { guard let result = json["result"] as? String else { - print("The server didn't give us a result") + self.printOut("The server didn't give us a result") client.disconnect(closeCode: 0) self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us a result", message: nil))) return } - print("Received result: \(result)") + self.printOut("Received result: \(result)") switch result { case "GiveIdentifier": - print("Giving identifier") + self.printOut("Giving identifier") client.json(["identifier": Keychain.shared.identifier!]) case "GiveStartProvisioningData": - print("Getting start provisioning data") + self.printOut("Getting start provisioning data") let body = [ "Header": [String: Any](), "Request": [String: Any](), @@ -353,19 +355,19 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc if let data = data, let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary>, let spim = plist["Response"]?["spim"] as? String { - print("Giving start provisioning data") + self.printOut("Giving start provisioning data") client.json(["spim": spim]) } else { - print("Apple didn't give valid start provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")") + self.printOut("Apple didn't give valid start provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")") client.disconnect(closeCode: 0) self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid start provisioning data. Please try again later", message: nil))) } }.resume() case "GiveEndProvisioningData": - print("Getting end provisioning data") + self.printOut("Getting end provisioning data") guard let cpim = json["cpim"] as? String else { - print("The server didn't give us a cpim") + self.printOut("The server didn't give us a cpim") client.disconnect(closeCode: 0) self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us a cpim", message: nil))) return @@ -384,20 +386,20 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary>, let ptm = plist["Response"]?["ptm"] as? String, let tk = plist["Response"]?["tk"] as? String { - print("Giving end provisioning data") + self.printOut("Giving end provisioning data") client.json(["ptm": ptm, "tk": tk]) } else { - print("Apple didn't give valid end provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")") + self.printOut("Apple didn't give valid end provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")") client.disconnect(closeCode: 0) self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid end provisioning data. Please try again later", message: nil))) } }.resume() case "ProvisioningSuccess": - print("Provisioning succeeded!") + self.printOut("Provisioning succeeded!") client.disconnect(closeCode: 0) guard let adiPb = json["adi_pb"] as? String else { - print("The server didn't give us an adi.pb file") + self.printOut("The server didn't give us an adi.pb file") self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us an adi.pb file", message: nil))) return } @@ -406,27 +408,27 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc default: if result.contains("Error") || result.contains("Invalid") || result == "ClosingPerRequest" || result == "Timeout" || result == "TextOnly" { - print("Failing because of \(result)") + self.printOut("Failing because of \(result)") self.finish(.failure(OperationError.provisioningError(result: result, message: json["message"] as? String))) } } } } catch let error as NSError { - print("Failed to handle text: \(error.localizedDescription)") + self.printOut("Failed to handle text: \(error.localizedDescription)") self.finish(.failure(OperationError.provisioningError(result: error.localizedDescription, message: nil))) } case .connected: - print("Connected") + self.printOut("Connected") case .disconnected(let string, let code): - print("Disconnected: \(code); \(string)") + self.printOut("Disconnected: \(code); \(string)") case .error(let error): - print("Got error: \(String(describing: error))") + self.printOut("Got error: \(String(describing: error))") default: - print("Unknown event: \(event)") + self.printOut("Unknown event: \(event)") } } @@ -460,10 +462,10 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc self.mdLu != nil && self.deviceId != nil && Keychain.shared.identifier != nil { - print("Skipping client_info fetch since all the properties we need aren't nil") + self.printOut("Skipping client_info fetch since all the properties we need aren't nil") return callback() } - print("Trying to get client_info") + self.printOut("Trying to get client_info") let clientInfoURL = self.url!.appendingPathComponent("v3").appendingPathComponent("client_info") URLSession.shared.dataTask(with: clientInfoURL) { data, response, error in do { @@ -473,20 +475,20 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] { if let clientInfo = json["client_info"] { - print("Server is V3") + self.printOut("Server is V3") self.clientInfo = clientInfo self.userAgent = json["user_agent"]! - print("Client-Info: \(self.clientInfo!)") - print("User-Agent: \(self.userAgent!)") + self.printOut("Client-Info: \(self.clientInfo!)") + self.printOut("User-Agent: \(self.userAgent!)") if Keychain.shared.identifier == nil { - print("Generating identifier") + self.printOut("Generating identifier") var bytes = [Int8](repeating: 0, count: 16) let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) if status != errSecSuccess { - print("ERROR GENERATING IDENTIFIER!!! \(status)") + self.printOut("ERROR GENERATING IDENTIFIER!!! \(status)") return self.finish(.failure(OperationError.provisioningError(result: "Couldn't generate identifier", message: nil))) } @@ -495,16 +497,16 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc let decoded = Data(base64Encoded: Keychain.shared.identifier!)! self.mdLu = decoded.sha256().hexEncodedString() - print("X-Apple-I-MD-LU: \(self.mdLu!)") + self.printOut("X-Apple-I-MD-LU: \(self.mdLu!)") let uuid: UUID = decoded.object() self.deviceId = uuid.uuidString.uppercased() - print("X-Mme-Device-Id: \(self.deviceId!)") + self.printOut("X-Mme-Device-Id: \(self.deviceId!)") callback() } else { self.handleV1() } } else { self.finish(.failure(OperationError.anisetteV3Error(message: "Couldn't fetch client info. The returned data may not be in JSON"))) } } catch let error as NSError { - print("Failed to load: \(error.localizedDescription)") + self.printOut("Failed to load: \(error.localizedDescription)") self.handleV1() } }.resume() @@ -512,7 +514,7 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc func fetchAnisetteV3(_ identifier: String, _ adiPb: String) { fetchClientInfo { - print("Fetching anisette V3") + self.printOut("Fetching anisette V3") let url = UserDefaults.standard.menuAnisetteURL var request = URLRequest(url: self.url!.appendingPathComponent("v3").appendingPathComponent("get_headers")) request.httpMethod = "POST" @@ -527,12 +529,21 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc try self.extractAnisetteData(data, response as? HTTPURLResponse, v3: true) } catch let error as NSError { - print("Failed to load: \(error.localizedDescription)") + self.printOut("Failed to load: \(error.localizedDescription)") self.finish(.failure(error)) } }.resume() } } + + + private func printOut(_ text: String?){ + let isInternalLoggingEnabled = OperationsLoggingControl.getFromDatabase(for: ANISETTE_VERBOSITY.self) + if(isInternalLoggingEnabled){ + // logging enabled, so log it + text.map{ _ in print(text!) } ?? print() + } + } } extension WebSocketClient { diff --git a/AltStore/Operations/Operation.swift b/AltStore/Operations/Operation.swift index 4480925f..4ba9c94f 100644 --- a/AltStore/Operations/Operation.swift +++ b/AltStore/Operations/Operation.swift @@ -39,7 +39,8 @@ class ResultOperation: Operation } // Diagnostics: perform verbose logging of the operations only if enabled (so as to not flood console logs) - if UserDefaults.standard.isVerboseOperationsLoggingEnabled { + let isLoggingEnabledForThisOperation = OperationsLoggingControl.getFromDatabase(for: type(of: self)) + if UserDefaults.standard.isVerboseOperationsLoggingEnabled && isLoggingEnabledForThisOperation { // diagnostics logging let resultStatus = String(describing: result).prefix("success".count).uppercased() print("\n ====> OPERATION: `\(type(of: self))` completed with: \(resultStatus) <====\n\n" + diff --git a/AltStore/Settings/OperationsLoggingContolView.swift b/AltStore/Settings/OperationsLoggingContolView.swift new file mode 100644 index 00000000..1c784f4f --- /dev/null +++ b/AltStore/Settings/OperationsLoggingContolView.swift @@ -0,0 +1,277 @@ +// +// SettingsView.swift +// AltStore +// +// Created by Magesh K on 14/01/25. +// Copyright © 2025 SideStore. All rights reserved. +// + + +import SwiftUI +import AltStoreCore + + +private final class DummyConformance: EnableJITContext +{ + private init(){} // non instantiatable + var installedApp: AltStoreCore.InstalledApp? + var error: (any Error)? +} + + +struct OperationsLoggingControlView: View { + let TITLE = "Operations Logging" + let BACKGROUND_COLOR = Color(.settingsBackground) + + var viewModel = OperationsLoggingControl() + + var body: some View { + NavigationView { + ZStack { +// BACKGROUND_COLOR.ignoresSafeArea() // Force background to cover the entire screen + VStack{ + Group{}.padding(12) + + CustomList { + CustomSection(header: Text("Install Operations")) + { + CustomToggle("1. Authentication", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: AuthenticationOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: AuthenticationOperation.self, value: value) + } + )) + + CustomToggle("2. VerifyAppPledge", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: VerifyAppPledgeOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: VerifyAppPledgeOperation.self, value: value) + } + )) + + CustomToggle("3. DownloadApp", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: DownloadAppOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: DownloadAppOperation.self, value: value) + } + )) + + CustomToggle("4. VerifyApp", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: VerifyAppOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: VerifyAppOperation.self, value: value) + } + )) + + CustomToggle("5. RemoveAppExtensions", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: RemoveAppExtensionsOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: RemoveAppExtensionsOperation.self, value: value) + } + )) + + CustomToggle("6. FetchAnisetteData", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: FetchAnisetteDataOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: FetchAnisetteDataOperation.self, value: value) + } + )) + + CustomToggle("7. FetchProvisioningProfiles", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: FetchProvisioningProfilesOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: FetchProvisioningProfilesOperation.self, value: value) + } + )) + + CustomToggle("8. ResignApp", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: ResignAppOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: ResignAppOperation.self, value: value) + } + )) + + CustomToggle("9. SendApp", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: SendAppOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: SendAppOperation.self, value: value) + } + )) + + CustomToggle("10. InstallApp", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: InstallAppOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: InstallAppOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("Refresh Operations")) + { + CustomToggle("1. RefreshApp", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: RefreshAppOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: RefreshAppOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("AppIDs related Operations")) + { + CustomToggle("1. FetchAppIDs", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: FetchAppIDsOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: FetchAppIDsOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("Sources related Operations")) + { + CustomToggle("1. FetchSource", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: FetchSourceOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: FetchSourceOperation.self, value: value) + } + )) + + CustomToggle("2. UpdateKnownSources", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: UpdateKnownSourcesOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: UpdateKnownSourcesOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("Backup Operations")) + { + CustomToggle("1. BackupApp", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: BackupAppOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: BackupAppOperation.self, value: value) + } + )) + + CustomToggle("2. RemoveAppBackup", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: RemoveAppBackupOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: RemoveAppBackupOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("Activate/Deactive Operations")) + { + CustomToggle("1. RemoveApp", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: RemoveAppOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: RemoveAppOperation.self, value: value) + } + )) + CustomToggle("2. DeactivateApp", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: DeactivateAppOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: DeactivateAppOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("Background refresh Operations")) + { + CustomToggle("1. BackgroundRefreshApps", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: BackgroundRefreshAppsOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: BackgroundRefreshAppsOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("Enable JIT Operations")) + { + CustomToggle("1. EnableJIT", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: EnableJITOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: EnableJITOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("Patrons Operations")) + { + CustomToggle("1. UpdatePatrons", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: UpdatePatronsOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: UpdatePatronsOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("Cache Operations")) + { + CustomToggle("1. ClearAppCache", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: ClearAppCacheOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: ClearAppCacheOperation.self, value: value) + } + )) + } + + CustomSection(header: Text("Misc Logging")) + { + CustomToggle("1. Anisette Internal Logging", isOn: Binding( + // enable anisette internal logging by default since it was already printing before + get: { OperationsLoggingControl.getUpdatedFromDatabase( + for: ANISETTE_VERBOSITY.self, defaultVal: true + )}, + set: { value in + self.viewModel.updateDatabase(for: ANISETTE_VERBOSITY.self, value: value) + } + )) + } + } + } + } + .navigationTitle(TITLE) + } + .ignoresSafeArea(edges: .all) + } + + private func CustomList(@ViewBuilder content: () -> Content) -> some View { +// ScrollView { + List { + content() + } +// .listStyle(.plain) +// .listStyle(InsetGroupedListStyle()) // Or PlainListStyle for iOS 15 +// .background(Color.clear) +// .background(Color(.settingsBackground)) +// .onAppear(perform: { +// // cache the current background color +// UITableView.appearance().backgroundColor = UIColor.red +// }) +// .onDisappear(perform: { +// // reset the background color to the cached value +// UITableView.appearance().backgroundColor = UIColor.systemBackground +// }) + } + + private func CustomSection(header: Text, @ViewBuilder content: () -> Content) -> some View { + Section(header: header) { + content() + } +// .listRowBackground(Color.clear) + } + + private func CustomToggle(_ title: String, isOn: Binding) -> some View { + Toggle(title, isOn: isOn) + .padding(3) +// .foregroundColor(.white) // Ensures text color is always white +// .font(.headline) + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + OperationsLoggingControlView() + } +} diff --git a/AltStore/Settings/Settings.storyboard b/AltStore/Settings/Settings.storyboard index 55758be5..c5d60803 100644 --- a/AltStore/Settings/Settings.storyboard +++ b/AltStore/Settings/Settings.storyboard @@ -22,7 +22,7 @@ - +