From a081397f95344f8357ec6b0a9d8ff870557983a7 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 17:55:58 -0400 Subject: [PATCH 001/391] Delete feather.xcodeproj/xcshareddata/xcschemes/feather (Release).xcscheme --- .../xcschemes/feather (Release).xcscheme | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 feather.xcodeproj/xcshareddata/xcschemes/feather (Release).xcscheme diff --git a/feather.xcodeproj/xcshareddata/xcschemes/feather (Release).xcscheme b/feather.xcodeproj/xcshareddata/xcschemes/feather (Release).xcscheme deleted file mode 100644 index 8ff8c7ba..00000000 --- a/feather.xcodeproj/xcshareddata/xcschemes/feather (Release).xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 49ba6408d8f398bb324264d287a3cbbb15b03ecd Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 17:56:13 -0400 Subject: [PATCH 002/391] Add files via upload --- .../xcschemes/feather (Release).xcscheme | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 feather.xcodeproj/xcshareddata/xcschemes/feather (Release).xcscheme diff --git a/feather.xcodeproj/xcshareddata/xcschemes/feather (Release).xcscheme b/feather.xcodeproj/xcshareddata/xcschemes/feather (Release).xcscheme new file mode 100644 index 00000000..bd6f9f42 --- /dev/null +++ b/feather.xcodeproj/xcshareddata/xcschemes/feather (Release).xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From de2502fd41bdf72c9987cf3405160d8cff8a0f37 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 18:38:54 -0400 Subject: [PATCH 003/391] Update main.yml --- .github/workflows/main.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1094c0dd..aacba7e6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,6 +16,11 @@ jobs: sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid brew install 7zip gnu-sed + - name: Update Optimization Level + run: | + sed -i '' 's/SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";/SWIFT_OPTIMIZATION_LEVEL = "-Onone";/g' feather.xcodeproj/project.pbxproj + sed -i '' 's/SWIFT_OPTIMIZATION_LEVEL = "-O";/SWIFT_OPTIMIZATION_LEVEL = "-Onone";/g' feather.xcodeproj/project.pbxproj + - name: Compile f run: | mkdir upload @@ -44,4 +49,4 @@ jobs: fail_on_unmatched_files: true draft: true env: - GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} + GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} \ No newline at end of file From 7533ac776c084ba0c05ec1c3d487340193940026 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 18:42:33 -0400 Subject: [PATCH 004/391] Update LogsViewController.swift --- iOS/Views/Settings/View Logs/LogsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Settings/View Logs/LogsViewController.swift b/iOS/Views/Settings/View Logs/LogsViewController.swift index 7d13b90c..b4bcee46 100644 --- a/iOS/Views/Settings/View Logs/LogsViewController.swift +++ b/iOS/Views/Settings/View Logs/LogsViewController.swift @@ -110,7 +110,7 @@ class LogsViewController: UIViewController { fileHandle.seek(toFileOffset: currentFileSize) let newData = fileHandle.readDataToEndOfFile() - if let newContent = String(data: newData, encoding: .utf8), !newContent isEmpty { + if let newContent = String(data: newData, encoding: .utf8), !newContent.isEmpty { logTextView.text.append(newContent) scrollToBottom() } From 694a1668143bf8d7d4274c02f1957f5ddb1bcec7 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 19:23:44 -0400 Subject: [PATCH 005/391] Create App-repo.json --- App-repo.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 App-repo.json diff --git a/App-repo.json b/App-repo.json new file mode 100644 index 00000000..14dc69d6 --- /dev/null +++ b/App-repo.json @@ -0,0 +1,24 @@ +{ + "name": "Backdoor Repository", + "identifier": "com.bdg.backdoor-repo", + "iconURL": "https://raw.githubusercontent.com/814bdg/App/refs/heads/main/Wing3x.png?raw=true", + "apps": [ + { + "name": "Backdoor", + "bundleIdentifier": "com.bdg.backdoor", + "developerName": "BDG", + "iconURL": "https://raw.githubusercontent.com/814bdg/App/refs/heads/main/Wing3x.png?raw=true", + "localizedDescription": "Backdoor is a free on-device iOS application manager/installe.", + "subtitle": "On-device signing application", + "tintColor": "848ef9", + "versions": [ + { + "version": "1.4.0", + "date": "2025-03-08T18:35:10Z", + "size": 12375230, + "downloadURL": "https://github.com/BDGHubNoKey/Backdoor/releases/download/v0.0.8/feather_v0.0.8.ipa" + } + ] + } + ] +} From 7c52a70d554bd3f3dfb96b9f4f766b45e27276ad Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 19:38:52 -0400 Subject: [PATCH 006/391] Update App-repo.json --- App-repo.json | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/App-repo.json b/App-repo.json index 14dc69d6..08b9811c 100644 --- a/App-repo.json +++ b/App-repo.json @@ -1,24 +1 @@ -{ - "name": "Backdoor Repository", - "identifier": "com.bdg.backdoor-repo", - "iconURL": "https://raw.githubusercontent.com/814bdg/App/refs/heads/main/Wing3x.png?raw=true", - "apps": [ - { - "name": "Backdoor", - "bundleIdentifier": "com.bdg.backdoor", - "developerName": "BDG", - "iconURL": "https://raw.githubusercontent.com/814bdg/App/refs/heads/main/Wing3x.png?raw=true", - "localizedDescription": "Backdoor is a free on-device iOS application manager/installe.", - "subtitle": "On-device signing application", - "tintColor": "848ef9", - "versions": [ - { - "version": "1.4.0", - "date": "2025-03-08T18:35:10Z", - "size": 12375230, - "downloadURL": "https://github.com/BDGHubNoKey/Backdoor/releases/download/v0.0.8/feather_v0.0.8.ipa" - } - ] - } - ] -} +m \ No newline at end of file From bcbd308cdcb5da9fb171f4d6fbba2d285711d8d7 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 19:44:10 -0400 Subject: [PATCH 007/391] Update AboutViewController.swift --- iOS/Views/Settings/About/AboutViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Settings/About/AboutViewController.swift b/iOS/Views/Settings/About/AboutViewController.swift index fc5e8168..9005fc2b 100644 --- a/iOS/Views/Settings/About/AboutViewController.swift +++ b/iOS/Views/Settings/About/AboutViewController.swift @@ -97,7 +97,7 @@ class AboutViewController: FRSTableViewController { @objc func shareButtonTapped() { - let shareText = "Feather - https://beacons.ai/bdgs" + let shareText = "Backdoor - https://beacons.ai/bdgs" let activityViewController = UIActivityViewController(activityItems: [shareText], applicationActivities: nil) if let popoverController = activityViewController.popoverPresentationController { From 161551d06554be9a91908b08de349c8cb1f9d44a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 19:48:00 -0400 Subject: [PATCH 008/391] Update SettingsViewController.swift --- iOS/Views/Settings/SettingsViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Settings/SettingsViewController.swift b/iOS/Views/Settings/SettingsViewController.swift index 2c6e4975..e2993e78 100644 --- a/iOS/Views/Settings/SettingsViewController.swift +++ b/iOS/Views/Settings/SettingsViewController.swift @@ -12,7 +12,7 @@ import SwiftUI class SettingsViewController: FRSTableViewController { let aboutSection = [ - String.localized("SETTINGS_VIEW_CONTROLLER_CELL_ABOUT", arguments: "Feather") + String.localized("SETTINGS_VIEW_CONTROLLER_CELL_ABOUT", arguments: "Backdoor") ] let displaySection = [ @@ -97,7 +97,7 @@ extension SettingsViewController { cell.textLabel?.text = cellText switch cellText { - case String.localized("SETTINGS_VIEW_CONTROLLER_CELL_ABOUT", arguments: "Feather"): + case String.localized("SETTINGS_VIEW_CONTROLLER_CELL_ABOUT", arguments: "Backdoor"): cell.setAccessoryIcon(with: "info.circle") cell.selectionStyle = .default @@ -155,7 +155,7 @@ extension SettingsViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let itemTapped = tableData[indexPath.section][indexPath.row] switch itemTapped { - case String.localized("SETTINGS_VIEW_CONTROLLER_CELL_ABOUT", arguments: "Feather"): + case String.localized("SETTINGS_VIEW_CONTROLLER_CELL_ABOUT", arguments: "Backdoor"): let l = AboutViewController() navigationController?.pushViewController(l, animated: true) From 02d036ccef3a0b7667a8ebcf3cafb15b4bc6a010 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 20:25:28 -0400 Subject: [PATCH 009/391] Update AppDelegate.swift --- iOS/Delegates/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Delegates/AppDelegate.swift b/iOS/Delegates/AppDelegate.swift index 9f5372d3..be98287f 100644 --- a/iOS/Delegates/AppDelegate.swift +++ b/iOS/Delegates/AppDelegate.swift @@ -274,7 +274,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle url: "https://raw.githubusercontent.com/814bdg/App/c56e7beebe634db3065b8cf763c6e4a049ca73c1/App-repo.json" ) { _ in Debug.shared.log(message: "Added default repos!") - Preferences.defaultRepos = true + Preferences.defaultRepos = false } } } From e82801feec8df8231479ff0d05721edad35f0c2f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 20:31:06 -0400 Subject: [PATCH 010/391] Update Localizable.strings --- Shared/Localizations/en.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index 3235a33d..a78881b4 100644 --- a/Shared/Localizations/en.lproj/Localizable.strings +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -201,7 +201,7 @@ "SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_GENERAL" = "General"; "SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_SIGNING" = "Signing"; "SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_SIGNING_SERVER" = "Signing Server"; -"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_ISSUES" = "If any issues occur within Feather please report it via the GitHub repository. When submitting an issue, be sure to submit any logs."; +"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_ISSUES" = "If any issues occur within Backdoor please report it via my website. When submitting an issue, be sure to submit any logs."; "SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_SERVER_LIMITATIONS" = "Sadly due to limitations server certificates will need to be re-renewed every year to keep Feathers local features working properly, tap this button to retrieve the most up-to-date files from our repositories."; "SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_DEFAULT_SERVER" = "Default server goes to %@"; "SETTINGS_VIEW_CONTROLLER_TITLE" = "Server Options"; From 111cfcf9918ea3d7106a700fe80b1a337bc1d622 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 20:34:54 -0400 Subject: [PATCH 011/391] Update Localizable.strings --- Shared/Localizations/en.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index a78881b4..03786567 100644 --- a/Shared/Localizations/en.lproj/Localizable.strings +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -202,7 +202,7 @@ "SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_SIGNING" = "Signing"; "SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_SIGNING_SERVER" = "Signing Server"; "SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_ISSUES" = "If any issues occur within Backdoor please report it via my website. When submitting an issue, be sure to submit any logs."; -"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_SERVER_LIMITATIONS" = "Sadly due to limitations server certificates will need to be re-renewed every year to keep Feathers local features working properly, tap this button to retrieve the most up-to-date files from our repositories."; +"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_SERVER_LIMITATIONS" = "Sadly due to limitations server certificates will need to be re-renewed every year to keep Backdoor's local features working properly, tap this button to retrieve the most up-to-date files from our repositories."; "SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_DEFAULT_SERVER" = "Default server goes to %@"; "SETTINGS_VIEW_CONTROLLER_TITLE" = "Server Options"; "SETTINGS_VIEW_CONTROLLER_TITLE_ONLINE" = "Online"; @@ -248,7 +248,7 @@ "DISPLAY_VIEW_CONTROLLER_SECTION_TITLE_APP_APPEARENCE" = "App Appearence"; "DISPLAY_VIEW_CONTROLLER_SECTION_TITLE_STORE" = "Store"; "DISPLAY_VIEW_CONTROLLER_CELL_DEFAULT_SUBTITLE" = "Default Subtitle"; -"DISPLAY_VIEW_CONTROLLER_CELL_DEFAULT_SUBTITLE_DESCRIPTION" = "Default style for feather, hides localized description and only includes subtitle."; +"DISPLAY_VIEW_CONTROLLER_CELL_DEFAULT_SUBTITLE_DESCRIPTION" = "Default style for backdoor, hides localized description and only includes subtitle."; "DISPLAY_VIEW_CONTROLLER_CELL_LOCALIZED_SUBTITLE" = "Localized Subtitle"; "DISPLAY_VIEW_CONTROLLER_CELL_LOCALIZED_SUBTITLE_DESCRIPTION" = "Replaces subtitle with app description."; "DISPLAY_VIEW_CONTROLLER_CELL_BIG_DESCRIPTION" = "Big Description"; From e0868259371ed9d7d22e38275e552834d2223c6f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 20:46:19 -0400 Subject: [PATCH 012/391] Update AppDelegate.swift --- iOS/Delegates/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Delegates/AppDelegate.swift b/iOS/Delegates/AppDelegate.swift index be98287f..ca7d76e9 100644 --- a/iOS/Delegates/AppDelegate.swift +++ b/iOS/Delegates/AppDelegate.swift @@ -368,7 +368,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle extension UIOnboardingViewConfiguration { static func setUp() -> Self { let welcomeToLine = NSMutableAttributedString(string: String.localized("ONBOARDING_WELCOMETITLE_1")) - let featherLine = NSMutableAttributedString(string: "Feather", attributes: [ + let featherLine = NSMutableAttributedString(string: "Backdoor", attributes: [ .foregroundColor: UIColor.tintColor, ]) From 1d9aad00c6c8c3e4dbd7f0270947795278368d60 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 20:53:44 -0400 Subject: [PATCH 013/391] Update TabbarView.swift --- iOS/Views/TabbarView.swift | 151 +++++++++++++++++++++---------------- 1 file changed, 84 insertions(+), 67 deletions(-) diff --git a/iOS/Views/TabbarView.swift b/iOS/Views/TabbarView.swift index 1a86f95f..c0ddcb72 100644 --- a/iOS/Views/TabbarView.swift +++ b/iOS/Views/TabbarView.swift @@ -9,78 +9,95 @@ import SwiftUI struct TabbarView: View { - @State private var selectedTab: Tab = Tab(rawValue: UserDefaults.standard.string(forKey: "selectedTab") ?? "sources") ?? .sources - - enum Tab: String { - case sources - case library - case settings - } + @State private var selectedTab: Tab = Tab(rawValue: UserDefaults.standard.string(forKey: "selectedTab") ?? "home") ?? .home + + enum Tab: String { + case home + case sources + case library + case settings + } - var body: some View { - TabView(selection: $selectedTab) { - tab(for: .sources) - tab(for: .library) - tab(for: .settings) - } - .onChange(of: selectedTab) { newTab in - // Save the selected tab to UserDefaults - UserDefaults.standard.set(newTab.rawValue, forKey: "selectedTab") - // Trigger animation for tab change - withAnimation(.spring(response: 0.5, dampingFraction: 0.7, blendDuration: 0.5)) { - // No explicit action needed here; the animation will apply to the tab transition - } - } - } + var body: some View { + TabView(selection: $selectedTab) { + tab(for: .home) + tab(for: .sources) + tab(for: .library) + tab(for: .settings) + } + .onChange(of: selectedTab) { newTab in + // Save the selected tab to UserDefaults + UserDefaults.standard.set(newTab.rawValue, forKey: "selectedTab") + // Trigger animation for tab change + withAnimation(.spring(response: 0.5, dampingFraction: 0.7, blendDuration: 0.5)) { + // No explicit action needed here; the animation will apply to the tab transition + } + } + } - @ViewBuilder - func tab(for tab: Tab) -> some View { - switch tab { - case .sources: - NavigationViewController(SourcesViewController.self, title: String.localized("TAB_SOURCES")) - .edgesIgnoringSafeArea(.all) - .tabItem { - Label( - String.localized("TAB_SOURCES"), - systemImage: { - if #available(iOS 16.0, *) { - return "globe.desk.fill" - } else { - return "books.vertical.fill" - } - }() - ) - } - .tag(Tab.sources) - case .library: - NavigationViewController(LibraryViewController.self, title: String.localized("TAB_LIBRARY")) - .edgesIgnoringSafeArea(.all) - .tabItem { Label(String.localized("TAB_LIBRARY"), systemImage: "square.grid.2x2.fill") } - .tag(Tab.library) - case .settings: - NavigationViewController(SettingsViewController.self, title: String.localized("TAB_SETTINGS")) - .edgesIgnoringSafeArea(.all) - .tabItem { Label(String.localized("TAB_SETTINGS"), systemImage: "gearshape.2.fill") } - .tag(Tab.settings) - } - } + @ViewBuilder + func tab(for tab: Tab) -> some View { + switch tab { + case .home: + NavigationViewController(HomeViewController.self, title: String.localized("TAB_HOME")) + .edgesIgnoringSafeArea(.all) + .tabItem { + Label(String.localized("TAB_HOME"), systemImage: "house.fill") + } + .tag(Tab.home) + case .sources: + NavigationViewController(SourcesViewController.self, title: String.localized("TAB_SOURCES")) + .edgesIgnoringSafeArea(.all) + .tabItem { + Label( + String.localized("TAB_SOURCES"), + systemImage: { + if #available(iOS 16.0, *) { + return "globe.desk.fill" + } else { + return "books.vertical.fill" + } + }() + ) + } + .tag(Tab.sources) + case .library: + NavigationViewController(LibraryViewController.self, title: String.localized("TAB_LIBRARY")) + .edgesIgnoringSafeArea(.all) + .tabItem { Label(String.localized("TAB_LIBRARY"), systemImage: "square.grid.2x2.fill") } + .tag(Tab.library) + case .settings: + NavigationViewController(SettingsViewController.self, title: String.localized("TAB_SETTINGS")) + .edgesIgnoringSafeArea(.all) + .tabItem { Label(String.localized("TAB_SETTINGS"), systemImage: "gearshape.2.fill") } + .tag(Tab.settings) + } + } } - struct NavigationViewController: UIViewControllerRepresentable { - let content: Content.Type - let title: String + let content: Content.Type + let title: String - init(_ content: Content.Type, title: String) { - self.content = content - self.title = title - } + init(_ content: Content.Type, title: String) { + self.content = content + self.title = title + } - func makeUIViewController(context: Context) -> UINavigationController { - let viewController = content.init() - viewController.navigationItem.title = title - return UINavigationController(rootViewController: viewController) - } - - func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {} + func makeUIViewController(context: Context) -> UINavigationController { + let viewController = content.init() + viewController.navigationItem.title = title + return UINavigationController(rootViewController: viewController) + } + + func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {} } + +// Assuming HomeViewController exists or will be created +class HomeViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemBackground + // Additional setup for your home tab's view controller + } +} \ No newline at end of file From ea8e21794953b4d067724edd7f29f7c205c735f4 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 20:57:56 -0400 Subject: [PATCH 014/391] Update Localizable.strings --- Shared/Localizations/en.lproj/Localizable.strings | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index 03786567..8ce0b256 100644 --- a/Shared/Localizations/en.lproj/Localizable.strings +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -56,6 +56,7 @@ "TAB_SOURCES" = "Sources"; "TAB_LIBRARY" = "Library"; "TAB_SETTINGS" = "Settings"; +"TAB_HOME" = "Home"; // MARK: - TransferPreview "TRANSFER_PREVIEW_PACKAGING" = "Packaging..."; @@ -300,4 +301,5 @@ "LOGS_VIEW_SECTION_TITLE_SHARE" = "Share Logs"; "LOGS_VIEW_SECTION_TITLE_COPY" = "Copy Logs"; "LOGS_VIEW_SUCCESS_DESCRIPTION" = "Log contents have been copied to clipboard."; -"LOGS_VIEW_FAIL_DESCRIPTION" = "Failed to copy log contents."; \ No newline at end of file +"LOGS_VIEW_FAIL_DESCRIPTION" = "Failed to copy log contents."; +```​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ \ No newline at end of file From 19a0d5354e3d3db41ed11f57bc824f7694d319c8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 22:08:41 -0400 Subject: [PATCH 015/391] Add files via upload --- iOS/Views/Home/text.txt | 141 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 iOS/Views/Home/text.txt diff --git a/iOS/Views/Home/text.txt b/iOS/Views/Home/text.txt new file mode 100644 index 00000000..775944f4 --- /dev/null +++ b/iOS/Views/Home/text.txt @@ -0,0 +1,141 @@ +import UIKit +import Zip + +class HomeViewController: UIViewController { + + // MARK: - Properties + + private var ipaPath: String = "" + private var fileList: [String] = [] + + // MARK: - UI Elements + private let selectIPAButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Select IPA", for: .normal) + button.addTarget(self, action: #selector(selectIPAButtonTapped), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let listFilesButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("List Files", for: .normal) + button.addTarget(self, action: #selector(listFilesButtonTapped), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let fileListTableView: UITableView = { + let tableView = UITableView() + tableView.translatesAutoresizingMaskIntoConstraints = false + return tableView + }() + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + fileListTableView.delegate = self + fileListTableView.dataSource = self + } + + // MARK: - UI Setup + + private func setupUI() { + view.backgroundColor = .white + + // Add UI elements to the view + view.addSubview(selectIPAButton) + view.addSubview(listFilesButton) + view.addSubview(fileListTableView) + + // Set up constraints + NSLayoutConstraint.activate([ + selectIPAButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), + selectIPAButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + listFilesButton.topAnchor.constraint(equalTo: selectIPAButton.bottomAnchor, constant: 20), + listFilesButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + fileListTableView.topAnchor.constraint(equalTo: listFilesButton.bottomAnchor, constant: 20), + fileListTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + fileListTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20), + fileListTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) + ]) + + // Register the table view cell + fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") + } + + // MARK: - Actions + + @objc private func selectIPAButtonTapped() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.ipa], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + @objc private func listFilesButtonTapped() { + listFiles() + } + + // MARK: - ZIP Handling + + private func listFiles() { + guard !ipaPath.isEmpty else { + print("Please select an IPA file first.") + return + } + + do { + let zipFilePath = URL(fileURLWithPath: ipaPath) + let fileManager = FileManager.default + let destinationPath = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("extracted") + + // Check if destination directory exists, create if necessary + if !fileManager.fileExists(atPath: destinationPath.path) { + try fileManager.createDirectory(at: destinationPath, withIntermediateDirectories: true, attributes: nil) + } + + try Zip.unzipFile(zipFilePath, destination: destinationPath, overwrite: true, password: nil) { (progress) -> () in + print(String(format: "Progress: %.2f", progress*100)) + } + + // List files after extraction + let contents = try fileManager.contentsOfDirectory(atPath: destinationPath.path) + fileList = contents + fileListTableView.reloadData() // Reload the table view to display the files + + } catch { + print("Extraction failed with error: \(error)") + } + } +} + +// MARK: - UIDocumentPickerViewControllerDelegate + +extension HomeViewController: UIDocumentPickerViewControllerDelegate { + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + ipaPath = selectedFileURL.path + print("Selected IPA: \(ipaPath)") + } +} + +// MARK: - UITableViewDelegate, UITableViewDataSource + +extension HomeViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) + cell.textLabel?.text = fileList[indexPath.row] + return cell + } +} \ No newline at end of file From a05b2d364fa16a0666d3ce9e68f87f68b9e015af Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 22:09:55 -0400 Subject: [PATCH 016/391] Update text.txt --- iOS/Views/Home/text.txt | 141 ---------------------------------------- 1 file changed, 141 deletions(-) diff --git a/iOS/Views/Home/text.txt b/iOS/Views/Home/text.txt index 775944f4..e69de29b 100644 --- a/iOS/Views/Home/text.txt +++ b/iOS/Views/Home/text.txt @@ -1,141 +0,0 @@ -import UIKit -import Zip - -class HomeViewController: UIViewController { - - // MARK: - Properties - - private var ipaPath: String = "" - private var fileList: [String] = [] - - // MARK: - UI Elements - private let selectIPAButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Select IPA", for: .normal) - button.addTarget(self, action: #selector(selectIPAButtonTapped), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let listFilesButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("List Files", for: .normal) - button.addTarget(self, action: #selector(listFilesButtonTapped), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let fileListTableView: UITableView = { - let tableView = UITableView() - tableView.translatesAutoresizingMaskIntoConstraints = false - return tableView - }() - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - setupUI() - fileListTableView.delegate = self - fileListTableView.dataSource = self - } - - // MARK: - UI Setup - - private func setupUI() { - view.backgroundColor = .white - - // Add UI elements to the view - view.addSubview(selectIPAButton) - view.addSubview(listFilesButton) - view.addSubview(fileListTableView) - - // Set up constraints - NSLayoutConstraint.activate([ - selectIPAButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), - selectIPAButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - listFilesButton.topAnchor.constraint(equalTo: selectIPAButton.bottomAnchor, constant: 20), - listFilesButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - fileListTableView.topAnchor.constraint(equalTo: listFilesButton.bottomAnchor, constant: 20), - fileListTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - fileListTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20), - fileListTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) - ]) - - // Register the table view cell - fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") - } - - // MARK: - Actions - - @objc private func selectIPAButtonTapped() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.ipa], asCopy: true) - documentPicker.delegate = self - documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) - } - - @objc private func listFilesButtonTapped() { - listFiles() - } - - // MARK: - ZIP Handling - - private func listFiles() { - guard !ipaPath.isEmpty else { - print("Please select an IPA file first.") - return - } - - do { - let zipFilePath = URL(fileURLWithPath: ipaPath) - let fileManager = FileManager.default - let destinationPath = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("extracted") - - // Check if destination directory exists, create if necessary - if !fileManager.fileExists(atPath: destinationPath.path) { - try fileManager.createDirectory(at: destinationPath, withIntermediateDirectories: true, attributes: nil) - } - - try Zip.unzipFile(zipFilePath, destination: destinationPath, overwrite: true, password: nil) { (progress) -> () in - print(String(format: "Progress: %.2f", progress*100)) - } - - // List files after extraction - let contents = try fileManager.contentsOfDirectory(atPath: destinationPath.path) - fileList = contents - fileListTableView.reloadData() // Reload the table view to display the files - - } catch { - print("Extraction failed with error: \(error)") - } - } -} - -// MARK: - UIDocumentPickerViewControllerDelegate - -extension HomeViewController: UIDocumentPickerViewControllerDelegate { - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - ipaPath = selectedFileURL.path - print("Selected IPA: \(ipaPath)") - } -} - -// MARK: - UITableViewDelegate, UITableViewDataSource - -extension HomeViewController: UITableViewDelegate, UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) - cell.textLabel?.text = fileList[indexPath.row] - return cell - } -} \ No newline at end of file From 9846cdce7501c19aa9f235d721a55028aefb8bb3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 22:11:22 -0400 Subject: [PATCH 017/391] Update HomeViewController.swift --- iOS/Views/Home/text.txt | 141 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/iOS/Views/Home/text.txt b/iOS/Views/Home/text.txt index e69de29b..775944f4 100644 --- a/iOS/Views/Home/text.txt +++ b/iOS/Views/Home/text.txt @@ -0,0 +1,141 @@ +import UIKit +import Zip + +class HomeViewController: UIViewController { + + // MARK: - Properties + + private var ipaPath: String = "" + private var fileList: [String] = [] + + // MARK: - UI Elements + private let selectIPAButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Select IPA", for: .normal) + button.addTarget(self, action: #selector(selectIPAButtonTapped), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let listFilesButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("List Files", for: .normal) + button.addTarget(self, action: #selector(listFilesButtonTapped), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let fileListTableView: UITableView = { + let tableView = UITableView() + tableView.translatesAutoresizingMaskIntoConstraints = false + return tableView + }() + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + fileListTableView.delegate = self + fileListTableView.dataSource = self + } + + // MARK: - UI Setup + + private func setupUI() { + view.backgroundColor = .white + + // Add UI elements to the view + view.addSubview(selectIPAButton) + view.addSubview(listFilesButton) + view.addSubview(fileListTableView) + + // Set up constraints + NSLayoutConstraint.activate([ + selectIPAButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), + selectIPAButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + listFilesButton.topAnchor.constraint(equalTo: selectIPAButton.bottomAnchor, constant: 20), + listFilesButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + fileListTableView.topAnchor.constraint(equalTo: listFilesButton.bottomAnchor, constant: 20), + fileListTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + fileListTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20), + fileListTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) + ]) + + // Register the table view cell + fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") + } + + // MARK: - Actions + + @objc private func selectIPAButtonTapped() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.ipa], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + @objc private func listFilesButtonTapped() { + listFiles() + } + + // MARK: - ZIP Handling + + private func listFiles() { + guard !ipaPath.isEmpty else { + print("Please select an IPA file first.") + return + } + + do { + let zipFilePath = URL(fileURLWithPath: ipaPath) + let fileManager = FileManager.default + let destinationPath = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("extracted") + + // Check if destination directory exists, create if necessary + if !fileManager.fileExists(atPath: destinationPath.path) { + try fileManager.createDirectory(at: destinationPath, withIntermediateDirectories: true, attributes: nil) + } + + try Zip.unzipFile(zipFilePath, destination: destinationPath, overwrite: true, password: nil) { (progress) -> () in + print(String(format: "Progress: %.2f", progress*100)) + } + + // List files after extraction + let contents = try fileManager.contentsOfDirectory(atPath: destinationPath.path) + fileList = contents + fileListTableView.reloadData() // Reload the table view to display the files + + } catch { + print("Extraction failed with error: \(error)") + } + } +} + +// MARK: - UIDocumentPickerViewControllerDelegate + +extension HomeViewController: UIDocumentPickerViewControllerDelegate { + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + ipaPath = selectedFileURL.path + print("Selected IPA: \(ipaPath)") + } +} + +// MARK: - UITableViewDelegate, UITableViewDataSource + +extension HomeViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) + cell.textLabel?.text = fileList[indexPath.row] + return cell + } +} \ No newline at end of file From 2a3dd4d5de9002a8131ef54dfd064f2612ecbfc6 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 22:14:39 -0400 Subject: [PATCH 018/391] Delete iOS/Views/Home/text.txt --- iOS/Views/Home/text.txt | 141 ---------------------------------------- 1 file changed, 141 deletions(-) delete mode 100644 iOS/Views/Home/text.txt diff --git a/iOS/Views/Home/text.txt b/iOS/Views/Home/text.txt deleted file mode 100644 index 775944f4..00000000 --- a/iOS/Views/Home/text.txt +++ /dev/null @@ -1,141 +0,0 @@ -import UIKit -import Zip - -class HomeViewController: UIViewController { - - // MARK: - Properties - - private var ipaPath: String = "" - private var fileList: [String] = [] - - // MARK: - UI Elements - private let selectIPAButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Select IPA", for: .normal) - button.addTarget(self, action: #selector(selectIPAButtonTapped), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let listFilesButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("List Files", for: .normal) - button.addTarget(self, action: #selector(listFilesButtonTapped), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let fileListTableView: UITableView = { - let tableView = UITableView() - tableView.translatesAutoresizingMaskIntoConstraints = false - return tableView - }() - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - setupUI() - fileListTableView.delegate = self - fileListTableView.dataSource = self - } - - // MARK: - UI Setup - - private func setupUI() { - view.backgroundColor = .white - - // Add UI elements to the view - view.addSubview(selectIPAButton) - view.addSubview(listFilesButton) - view.addSubview(fileListTableView) - - // Set up constraints - NSLayoutConstraint.activate([ - selectIPAButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), - selectIPAButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - listFilesButton.topAnchor.constraint(equalTo: selectIPAButton.bottomAnchor, constant: 20), - listFilesButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - fileListTableView.topAnchor.constraint(equalTo: listFilesButton.bottomAnchor, constant: 20), - fileListTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - fileListTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20), - fileListTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) - ]) - - // Register the table view cell - fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") - } - - // MARK: - Actions - - @objc private func selectIPAButtonTapped() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.ipa], asCopy: true) - documentPicker.delegate = self - documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) - } - - @objc private func listFilesButtonTapped() { - listFiles() - } - - // MARK: - ZIP Handling - - private func listFiles() { - guard !ipaPath.isEmpty else { - print("Please select an IPA file first.") - return - } - - do { - let zipFilePath = URL(fileURLWithPath: ipaPath) - let fileManager = FileManager.default - let destinationPath = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("extracted") - - // Check if destination directory exists, create if necessary - if !fileManager.fileExists(atPath: destinationPath.path) { - try fileManager.createDirectory(at: destinationPath, withIntermediateDirectories: true, attributes: nil) - } - - try Zip.unzipFile(zipFilePath, destination: destinationPath, overwrite: true, password: nil) { (progress) -> () in - print(String(format: "Progress: %.2f", progress*100)) - } - - // List files after extraction - let contents = try fileManager.contentsOfDirectory(atPath: destinationPath.path) - fileList = contents - fileListTableView.reloadData() // Reload the table view to display the files - - } catch { - print("Extraction failed with error: \(error)") - } - } -} - -// MARK: - UIDocumentPickerViewControllerDelegate - -extension HomeViewController: UIDocumentPickerViewControllerDelegate { - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - ipaPath = selectedFileURL.path - print("Selected IPA: \(ipaPath)") - } -} - -// MARK: - UITableViewDelegate, UITableViewDataSource - -extension HomeViewController: UITableViewDelegate, UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) - cell.textLabel?.text = fileList[indexPath.row] - return cell - } -} \ No newline at end of file From 0a0ea50bcbebef194cb122e65da6d93c06af25d1 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 22:15:32 -0400 Subject: [PATCH 019/391] Add files via upload --- iOS/Views/Home/HomeViewController.swift | 141 ++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 iOS/Views/Home/HomeViewController.swift diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift new file mode 100644 index 00000000..775944f4 --- /dev/null +++ b/iOS/Views/Home/HomeViewController.swift @@ -0,0 +1,141 @@ +import UIKit +import Zip + +class HomeViewController: UIViewController { + + // MARK: - Properties + + private var ipaPath: String = "" + private var fileList: [String] = [] + + // MARK: - UI Elements + private let selectIPAButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Select IPA", for: .normal) + button.addTarget(self, action: #selector(selectIPAButtonTapped), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let listFilesButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("List Files", for: .normal) + button.addTarget(self, action: #selector(listFilesButtonTapped), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let fileListTableView: UITableView = { + let tableView = UITableView() + tableView.translatesAutoresizingMaskIntoConstraints = false + return tableView + }() + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + fileListTableView.delegate = self + fileListTableView.dataSource = self + } + + // MARK: - UI Setup + + private func setupUI() { + view.backgroundColor = .white + + // Add UI elements to the view + view.addSubview(selectIPAButton) + view.addSubview(listFilesButton) + view.addSubview(fileListTableView) + + // Set up constraints + NSLayoutConstraint.activate([ + selectIPAButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), + selectIPAButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + listFilesButton.topAnchor.constraint(equalTo: selectIPAButton.bottomAnchor, constant: 20), + listFilesButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + fileListTableView.topAnchor.constraint(equalTo: listFilesButton.bottomAnchor, constant: 20), + fileListTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + fileListTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20), + fileListTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) + ]) + + // Register the table view cell + fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") + } + + // MARK: - Actions + + @objc private func selectIPAButtonTapped() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.ipa], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + @objc private func listFilesButtonTapped() { + listFiles() + } + + // MARK: - ZIP Handling + + private func listFiles() { + guard !ipaPath.isEmpty else { + print("Please select an IPA file first.") + return + } + + do { + let zipFilePath = URL(fileURLWithPath: ipaPath) + let fileManager = FileManager.default + let destinationPath = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("extracted") + + // Check if destination directory exists, create if necessary + if !fileManager.fileExists(atPath: destinationPath.path) { + try fileManager.createDirectory(at: destinationPath, withIntermediateDirectories: true, attributes: nil) + } + + try Zip.unzipFile(zipFilePath, destination: destinationPath, overwrite: true, password: nil) { (progress) -> () in + print(String(format: "Progress: %.2f", progress*100)) + } + + // List files after extraction + let contents = try fileManager.contentsOfDirectory(atPath: destinationPath.path) + fileList = contents + fileListTableView.reloadData() // Reload the table view to display the files + + } catch { + print("Extraction failed with error: \(error)") + } + } +} + +// MARK: - UIDocumentPickerViewControllerDelegate + +extension HomeViewController: UIDocumentPickerViewControllerDelegate { + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + ipaPath = selectedFileURL.path + print("Selected IPA: \(ipaPath)") + } +} + +// MARK: - UITableViewDelegate, UITableViewDataSource + +extension HomeViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) + cell.textLabel?.text = fileList[indexPath.row] + return cell + } +} \ No newline at end of file From 0b36bbf93f22f42d95c96c5418c10608aea997a4 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 22:23:40 -0400 Subject: [PATCH 020/391] Update Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 219 +----------------- 1 file changed, 11 insertions(+), 208 deletions(-) diff --git a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e6cb2c16..39f9b07b 100644 --- a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -19,213 +19,7 @@ "version" : "1.22.0" } }, - { - "identity" : "async-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/async-kit.git", - "state" : { - "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", - "version" : "1.20.0" - } - }, - { - "identity" : "bitbytedata", - "kind" : "remoteSourceControl", - "location" : "https://github.com/tsolomko/BitByteData", - "state" : { - "revision" : "cdcdc5177ad536cfb11b95c620f926a81014b7fe", - "version" : "2.0.4" - } - }, - { - "identity" : "console-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/console-kit.git", - "state" : { - "revision" : "78c0dd739df8cb9ee14a8bbbf770facc4fc3402a", - "version" : "4.15.0" - } - }, - { - "identity" : "multipart-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/multipart-kit.git", - "state" : { - "revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68", - "version" : "4.7.0" - } - }, - { - "identity" : "nuke", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/Nuke", - "state" : { - "revision" : "0ead44350d2737db384908569c012fe67c421e4d", - "version" : "12.8.0" - } - }, - { - "identity" : "openssl-swift-package", - "kind" : "remoteSourceControl", - "location" : "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", - "state" : { - "branch" : "main", - "revision" : "309092bce7787397a183df4ad90b8291744535ae" - } - }, - { - "identity" : "routing-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/routing-kit.git", - "state" : { - "revision" : "8c9a227476555c55837e569be71944e02a056b72", - "version" : "4.9.1" - } - }, - { - "identity" : "swcompression", - "kind" : "remoteSourceControl", - "location" : "https://github.com/tsolomko/SWCompression", - "state" : { - "revision" : "390e0b0af8dd19a600005a242a89e570ff482e09", - "version" : "4.8.6" - } - }, - { - "identity" : "swift-algorithms", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-algorithms.git", - "state" : { - "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", - "version" : "1.2.0" - } - }, - { - "identity" : "swift-atomics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", - "state" : { - "revision" : "cd142fd2f64be2100422d658e7411e39489da985", - "version" : "1.2.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections.git", - "state" : { - "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", - "version" : "1.1.2" - } - }, - { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "a53a7e8f858902659d4784322bede34f4e49097e", - "version" : "3.6.1" - } - }, - { - "identity" : "swift-http-types", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-http-types", - "state" : { - "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", - "version" : "1.3.0" - } - }, - { - "identity" : "swift-log", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log.git", - "state" : { - "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", - "version" : "1.6.1" - } - }, - { - "identity" : "swift-metrics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-metrics.git", - "state" : { - "revision" : "e0165b53d49b413dd987526b641e05e246782685", - "version" : "2.5.0" - } - }, - { - "identity" : "swift-nio", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio.git", - "state" : { - "revision" : "4c4453b489cf76e6b3b0f300aba663eb78182fad", - "version" : "2.70.0" - } - }, - { - "identity" : "swift-nio-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-extras.git", - "state" : { - "revision" : "d1ead62745cc3269e482f1c51f27608057174379", - "version" : "1.24.0" - } - }, - { - "identity" : "swift-nio-http2", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-http2.git", - "state" : { - "revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94", - "version" : "1.34.0" - } - }, - { - "identity" : "swift-nio-ssl", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-ssl.git", - "state" : { - "revision" : "7b84abbdcef69cc3be6573ac12440220789dcd69", - "version" : "2.27.2" - } - }, - { - "identity" : "swift-nio-transport-services", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-transport-services.git", - "state" : { - "revision" : "38ac8221dd20674682148d6451367f89c2652980", - "version" : "1.21.0" - } - }, - { - "identity" : "swift-numerics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-numerics.git", - "state" : { - "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", - "version" : "1.0.2" - } - }, - { - "identity" : "swift-system", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-system.git", - "state" : { - "revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5", - "version" : "1.3.2" - } - }, - { - "identity" : "uionboarding-18", - "kind" : "remoteSourceControl", - "location" : "https://github.com/khcrysalis/UIOnboarding-18", - "state" : { - "branch" : "main", - "revision" : "e163395903d80c032b9e47eadbbc342679b2ad77" - } - }, + // ... (other existing pins) { "identity" : "vapor", "kind" : "remoteSourceControl", @@ -252,7 +46,16 @@ "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", "version" : "0.9.19" } + }, + { + "identity" : "swiftzip", + "kind" : "remoteSourceControl", + "location" : "https://github.com/marmelroy/Zip.git", + "state" : { + "revision" : "a884555d1c53152378707c6e1487e46738a35300", + "version" : "2.1.2" + } } ], "version" : 3 -} +} \ No newline at end of file From 41b689ace57c73b0a1846e449fac91fab788e146 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 22:41:40 -0400 Subject: [PATCH 021/391] Update Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 221 +++++++++++++++++- 1 file changed, 210 insertions(+), 11 deletions(-) diff --git a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 39f9b07b..e472d3dd 100644 --- a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -19,7 +19,213 @@ "version" : "1.22.0" } }, - // ... (other existing pins) + { + "identity" : "async-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/async-kit.git", + "state" : { + "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", + "version" : "1.20.0" + } + }, + { + "identity" : "bitbytedata", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tsolomko/BitByteData", + "state" : { + "revision" : "cdcdc5177ad536cfb11b95c620f926a81014b7fe", + "version" : "2.0.4" + } + }, + { + "identity" : "console-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/console-kit.git", + "state" : { + "revision" : "78c0dd739df8cb9ee14a8bbbf770facc4fc3402a", + "version" : "4.15.0" + } + }, + { + "identity" : "multipart-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/multipart-kit.git", + "state" : { + "revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68", + "version" : "4.7.0" + } + }, + { + "identity" : "nuke", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke", + "state" : { + "revision" : "0ead44350d2737db384908569c012fe67c421e4d", + "version" : "12.8.0" + } + }, + { + "identity" : "openssl-swift-package", + "kind" : "remoteSourceControl", + "location" : "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", + "state" : { + "branch" : "main", + "revision" : "309092bce7787397a183df4ad90b8291744535ae" + } + }, + { + "identity" : "routing-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/routing-kit.git", + "state" : { + "revision" : "8c9a227476555c55837e569be71944e02a056b72", + "version" : "4.9.1" + } + }, + { + "identity" : "swcompression", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tsolomko/SWCompression", + "state" : { + "revision" : "390e0b0af8dd19a600005a242a89e570ff482e09", + "version" : "4.8.6" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", + "version" : "1.1.2" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "a53a7e8f858902659d4784322bede34f4e49097e", + "version" : "3.6.1" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" + } + }, + { + "identity" : "swift-metrics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-metrics.git", + "state" : { + "revision" : "e0165b53d49b413dd987526b641e05e246782685", + "version" : "2.5.0" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "4c4453b489cf76e6b3b0f300aba663eb78182fad", + "version" : "2.70.0" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "d1ead62745cc3269e482f1c51f27608057174379", + "version" : "1.24.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94", + "version" : "1.34.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "7b84abbdcef69cc3be6573ac12440220789dcd69", + "version" : "2.27.2" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "38ac8221dd20674682148d6451367f89c2652980", + "version" : "1.21.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5", + "version" : "1.3.2" + } + }, + { + "identity" : "uionboarding-18", + "kind" : "remoteSourceControl", + "location" : "https://github.com/khcrysalis/UIOnboarding-18", + "state" : { + "branch" : "main", + "revision" : "e163395903d80c032b9e47eadbbc342679b2ad77" + } + }, { "identity" : "vapor", "kind" : "remoteSourceControl", @@ -46,16 +252,9 @@ "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", "version" : "0.9.19" } - }, - { - "identity" : "swiftzip", - "kind" : "remoteSourceControl", - "location" : "https://github.com/marmelroy/Zip.git", - "state" : { - "revision" : "a884555d1c53152378707c6e1487e46738a35300", - "version" : "2.1.2" - } } ], "version" : 3 -} \ No newline at end of file +} + +Add swift zip to this \ No newline at end of file From fb3f8e059655196bea08e25da7690ade50070ee2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 22:42:01 -0400 Subject: [PATCH 022/391] Update Package.resolved --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e472d3dd..c52c4ca0 100644 --- a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -255,6 +255,4 @@ } ], "version" : 3 -} - -Add swift zip to this \ No newline at end of file +} \ No newline at end of file From ba6143debf198628fd7e107a7ebc490301446fce Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 23:05:53 -0400 Subject: [PATCH 023/391] Update main.yml --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aacba7e6..9875e9e9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,6 +16,9 @@ jobs: sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid brew install 7zip gnu-sed + - name: Resolve Swift Package Manager dependencies + run: xcodebuild -resolvePackageDependencies -project feather.xcodeproj + - name: Update Optimization Level run: | sed -i '' 's/SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";/SWIFT_OPTIMIZATION_LEVEL = "-Onone";/g' feather.xcodeproj/project.pbxproj From ca8d40853dab6c97bceb4a64ce1a16e8b4f1fc43 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 23:29:06 -0400 Subject: [PATCH 024/391] Update .gitignore --- .gitignore | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index d5ec250a..8b137891 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1 @@ -*xcuserdata* -*.DS_Store -.theos -Packages -packages* -*xcuserstate* -Payload* -Payload -/.vscode + From e1628987014c4831a1d6219675d7d1958a4bb500 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Fri, 14 Mar 2025 23:58:16 -0400 Subject: [PATCH 025/391] Create package.swift --- package.swift | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 package.swift diff --git a/package.swift b/package.swift new file mode 100644 index 00000000..b5cbf4a5 --- /dev/null +++ b/package.swift @@ -0,0 +1,25 @@ +// swift-tools-version:5.7 +import PackageDescription + +let package = Package( + name: "Backdoor", + platforms: [ + .iOS(.v15) // Adjust this to your minimum deployment target + ], + dependencies: [ + // SwiftZip + .package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.2") // Use the latest version here + ], + targets: [ + .target( + name: "Backdoor", + dependencies: [ + .product(name: "Zip", package: "Zip") + ] + ), + .testTarget( + name: "BackdoorTests", + dependencies: ["Backdoor"] + ) + ] +) \ No newline at end of file From faf760d8532c5cc0856ec1c37b48ef098c1df475 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 00:00:29 -0400 Subject: [PATCH 026/391] Update .gitignore --- .gitignore | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8b137891..72061e82 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,9 @@ - +*xcuserdata* +*.DS_Store +.theos +Packages +packages* +*xcuserstate* +Payload* +Payload +/.vscode \ No newline at end of file From 5c9505a47e7bb7ac0f211b6478b10d493fe6ac3f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 00:38:06 -0400 Subject: [PATCH 027/391] Update Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved b/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved index ec37cda8..c31f4cb6 100644 --- a/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -288,7 +288,16 @@ "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", "version" : "0.9.19" } + }, + { + "identity" : "swift-zip", + "kind" : "remoteSourceControl", + "location" : "https://github.com/marmelroy/Zip", + "state" : { + "revision" : "a1b2c3d4e5f67890123456789abcdef012345678", + "version" : "2.1.1" + } } ], "version" : 3 -} +} \ No newline at end of file From 616c251688e6bad4ee481ae984842e45102ebc9e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 00:42:35 -0400 Subject: [PATCH 028/391] Update Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c52c4ca0..a2dae1c3 100644 --- a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -252,6 +252,15 @@ "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", "version" : "0.9.19" } + }, + { + "identity" : "swift-zip", + "kind" : "remoteSourceControl", + "location" : "https://github.com/marmelroy/Zip", + "state" : { + "revision" : "a1b2c3d4e5f67890123456789abcdef012345678", + "version" : "2.1.1" + } } ], "version" : 3 From 5b3528af6f6a2b0a6a7b2035f0cd3db14ff36b64 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 01:15:32 -0400 Subject: [PATCH 029/391] Create devcontainer.json --- .devcontainer/devcontainer.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..b34dc333 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,10 @@ +{ + "image": "swift:latest", + "customizations": { + "vscode": { + "extensions": [ + "sswg.swift-lang" + ] + } + } +} From 5533c807822af7546c96bb2764d0108b0892a678 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 17:29:19 -0400 Subject: [PATCH 030/391] Create Package.swift --- Package.swift | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Package.swift diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..fcaa0c49 --- /dev/null +++ b/Package.swift @@ -0,0 +1,59 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "Backdoor", + platforms: [ + .iOS(.v13) + ], + products: [ + .library( + name: "Backdoor", + targets: ["Backdoor"] + ) + ], + dependencies: [ + // ... all your dependencies here ... + ], + targets: [ + .target( + name: "Backdoor", + dependencies: [ + "AlertKit", + "AsyncHTTPClient", + "AsyncKit", + "BitByteData", + "ConsoleKit", + "MultipartKit", + "Nuke", + "OpenSSL", + "RoutingKit", + "SWCompression", + "Algorithms", + "Atomics", + "Collections", + "Crypto", + "HTTPTypes", + "Logging", + "Metrics", + "NIO", + "NIOExtras", + "NIOHTTP2", + "NIOSSL", + "NIOTransportServices", + "Numerics", + "System", + "UIOnboarding", + "Vapor", + "WebSocketKit", + "ZIPFoundation", + "Zip" + ], + path: "main/Backdoor" // Add this line for the custom path + ), + .testTarget( + name: "BackdoorTests", + dependencies: ["Backdoor"] + ) + ] +) \ No newline at end of file From 26356dc176ca3e43551d024e740402f59eca0f2f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 17:32:49 -0400 Subject: [PATCH 031/391] Update main.yml --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9875e9e9..16c52bb7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,14 +17,14 @@ jobs: brew install 7zip gnu-sed - name: Resolve Swift Package Manager dependencies - run: xcodebuild -resolvePackageDependencies -project feather.xcodeproj + run: swift package resolve - name: Update Optimization Level run: | sed -i '' 's/SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";/SWIFT_OPTIMIZATION_LEVEL = "-Onone";/g' feather.xcodeproj/project.pbxproj sed -i '' 's/SWIFT_OPTIMIZATION_LEVEL = "-O";/SWIFT_OPTIMIZATION_LEVEL = "-Onone";/g' feather.xcodeproj/project.pbxproj - - name: Compile f + - name: Compile run: | mkdir upload make package SCHEME="'feather (Release)'" From 1a8d510e4cbc5bcc4d970f393f8851d998770e48 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 17:44:18 -0400 Subject: [PATCH 032/391] Update Package.swift --- Package.swift | 90 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 29 deletions(-) diff --git a/Package.swift b/Package.swift index fcaa0c49..68744a7e 100644 --- a/Package.swift +++ b/Package.swift @@ -13,41 +13,73 @@ let package = Package( ) ], dependencies: [ + .package(url: "https://github.com/huri000/SwiftUIOnboarding", from: "2.0.0"), + .package(url: "https://github.com/kean/Nuke", from: "12.0.0"), + .package(url: "https://github.com/vapor/vapor", from: "4.0.0"), + .package(url: "https://github.com/vapor/websocket-kit", from: "1.0.0"), + .package(url: "https://github.com/vapor/async-kit", from: "1.0.0"), + .package(url: "https://github.com/vapor/console-kit", from: "4.0.0"), + .package(url: "https://github.com/vapor/routing-kit", from: "4.0.0"), + .package(url: "https://github.com/vapor/multipart-kit", from: "4.0.0"), + .package(url: "https://github.com/vapor/http-types", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-algorithms", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-collections", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-numerics", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-nio", from: "2.0.0"), + .package(url: "https://github.com/apple/swift-nio-extras", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-nio-transport-services", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-nio-ssl", from: "2.0.0"), + .package(url: "https://github.com/swift-server/async-http-client", from: "1.0.0"), + .package(url: "https://github.com/swift-server/swift-backtrace", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-metrics", from: "2.0.0"), + .package(url: "https://github.com/apple/swift-log", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-crypto", from: "2.0.0"), + .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), + .package(url: "https://github.com/ZipArchive/ZipArchive", from: "2.0.0"), + .package(url: "https://github.com/marmelroy/Zip", from: "2.0.0"), + .package(url: "https://github.com/SammySmallman/BitByteData", from: "2.0.0"), + .package(url: "https://github.com/alexsteinerde/SwiftLMDB", from: "0.9.7"), + .package(url: "https://github.com/tsolomko/SWCompression", from: "4.6.0"), + .package(url: "https://github.com/Kitura/OpenSSL", from: "2.0.0"), + .package(url: "https://github.com/Flight-School/AlertKit", from: "2.0.0"), + .package(url: "https://github.com/Flight-School/AnyCodable", from: "0.6.0") + + // ... all your dependencies here ... ], targets: [ .target( name: "Backdoor", dependencies: [ - "AlertKit", - "AsyncHTTPClient", - "AsyncKit", - "BitByteData", - "ConsoleKit", - "MultipartKit", - "Nuke", - "OpenSSL", - "RoutingKit", - "SWCompression", - "Algorithms", - "Atomics", - "Collections", - "Crypto", - "HTTPTypes", - "Logging", - "Metrics", - "NIO", - "NIOExtras", - "NIOHTTP2", - "NIOSSL", - "NIOTransportServices", - "Numerics", - "System", - "UIOnboarding", - "Vapor", - "WebSocketKit", - "ZIPFoundation", - "Zip" + .product(name: "AlertKit", package: "AlertKit"), + .product(name: "AsyncHTTPClient", package: "async-http-client"), + .product(name: "AsyncKit", package: "async-kit"), + .product(name: "BitByteData", package: "BitByteData"), + .product(name: "ConsoleKit", package: "console-kit"), + .product(name: "MultipartKit", package: "multipart-kit"), + .product(name: "Nuke", package: "Nuke"), + .product(name: "OpenSSL", package: "OpenSSL"), + .product(name: "RoutingKit", package: "routing-kit"), + .product(name: "SWCompression", package: "SWCompression"), + .product(name: "Algorithms", package: "swift-algorithms"), + .product(name: "Atomics", package: "swift-backtrace"), + .product(name: "Collections", package: "swift-collections"), + .product(name: "Crypto", package: "swift-crypto"), + .product(name: "HTTPTypes", package: "http-types"), + .product(name: "Logging", package: "swift-log"), + .product(name: "Metrics", package: "swift-metrics"), + .product(name: "NIO", package: "swift-nio"), + .product(name: "NIOExtras", package: "swift-nio-extras"), + .product(name: "NIOHTTP2", package: "swift-nio"), // Likely part of swift-nio + .product(name: "NIOSSL", package: "swift-nio-ssl"), + .product(name: "NIOTransportServices", package: "swift-nio-transport-services"), + .product(name: "Numerics", package: "swift-numerics"), + .product(name: "System", package: "swift-system"), + .product(name: "SwiftUIOnboarding", package: "SwiftUIOnboarding"), + .product(name: "Vapor", package: "vapor"), + .product(name: "WebSocketKit", package: "websocket-kit"), + .product(name: "ZIPFoundation", package: "ZipArchive"), + .product(name: "Zip", package: "Zip"), ], path: "main/Backdoor" // Add this line for the custom path ), From e82333aa6bb2eab13a654fae918fa461bfb2e6da Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 17:54:05 -0400 Subject: [PATCH 033/391] Update Package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 68744a7e..7f955838 100644 --- a/Package.swift +++ b/Package.swift @@ -36,7 +36,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-crypto", from: "2.0.0"), .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), .package(url: "https://github.com/ZipArchive/ZipArchive", from: "2.0.0"), - .package(url: "https://github.com/marmelroy/Zip", from: "2.0.0"), + .package(url: "https://github.com/marmelroy/Zip", from: "2.1.2"), .package(url: "https://github.com/SammySmallman/BitByteData", from: "2.0.0"), .package(url: "https://github.com/alexsteinerde/SwiftLMDB", from: "0.9.7"), .package(url: "https://github.com/tsolomko/SWCompression", from: "4.6.0"), From 2e2ca0b6099dda5248923ad7f66bb3b3c281bd7e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 18:12:17 -0400 Subject: [PATCH 034/391] Delete package.swift --- package.swift | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 package.swift diff --git a/package.swift b/package.swift deleted file mode 100644 index b5cbf4a5..00000000 --- a/package.swift +++ /dev/null @@ -1,25 +0,0 @@ -// swift-tools-version:5.7 -import PackageDescription - -let package = Package( - name: "Backdoor", - platforms: [ - .iOS(.v15) // Adjust this to your minimum deployment target - ], - dependencies: [ - // SwiftZip - .package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.2") // Use the latest version here - ], - targets: [ - .target( - name: "Backdoor", - dependencies: [ - .product(name: "Zip", package: "Zip") - ] - ), - .testTarget( - name: "BackdoorTests", - dependencies: ["Backdoor"] - ) - ] -) \ No newline at end of file From d9afcf6933b359bf81a651e1e517bd3f04d96360 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 18:13:12 -0400 Subject: [PATCH 035/391] Update Package.swift --- Package.swift | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Package.swift b/Package.swift index 7f955838..03b4ed31 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( .package(url: "https://github.com/vapor/console-kit", from: "4.0.0"), .package(url: "https://github.com/vapor/routing-kit", from: "4.0.0"), .package(url: "https://github.com/vapor/multipart-kit", from: "4.0.0"), - .package(url: "https://github.com/vapor/http-types", from: "1.0.0"), + .package(url: "https://github.com/vapor/http", from: "1.0.0"), .package(url: "https://github.com/apple/swift-algorithms", from: "1.0.0"), .package(url: "https://github.com/apple/swift-collections", from: "1.0.0"), .package(url: "https://github.com/apple/swift-numerics", from: "1.0.0"), @@ -36,16 +36,13 @@ let package = Package( .package(url: "https://github.com/apple/swift-crypto", from: "2.0.0"), .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), .package(url: "https://github.com/ZipArchive/ZipArchive", from: "2.0.0"), - .package(url: "https://github.com/marmelroy/Zip", from: "2.1.2"), + .package(url: "https://github.com/marmelroy/Zip", from: "2.1.2"), // Updated to 2.1.2 .package(url: "https://github.com/SammySmallman/BitByteData", from: "2.0.0"), .package(url: "https://github.com/alexsteinerde/SwiftLMDB", from: "0.9.7"), .package(url: "https://github.com/tsolomko/SWCompression", from: "4.6.0"), .package(url: "https://github.com/Kitura/OpenSSL", from: "2.0.0"), - .package(url: "https://github.com/Flight-School/AlertKit", from: "2.0.0"), + .package(url: "https://github.com/sparrowcode/AlertKit", from: "2.0.0"), .package(url: "https://github.com/Flight-School/AnyCodable", from: "0.6.0") - - - // ... all your dependencies here ... ], targets: [ .target( @@ -62,15 +59,15 @@ let package = Package( .product(name: "RoutingKit", package: "routing-kit"), .product(name: "SWCompression", package: "SWCompression"), .product(name: "Algorithms", package: "swift-algorithms"), - .product(name: "Atomics", package: "swift-backtrace"), + .product(name: "Atomics", package: "swift-backtrace"), // Note: This might not be correct; 'Atomics' is typically from 'swift-atomics' .product(name: "Collections", package: "swift-collections"), .product(name: "Crypto", package: "swift-crypto"), - .product(name: "HTTPTypes", package: "http-types"), + .product(name: "HTTPTypes", package: "http"), .product(name: "Logging", package: "swift-log"), .product(name: "Metrics", package: "swift-metrics"), .product(name: "NIO", package: "swift-nio"), .product(name: "NIOExtras", package: "swift-nio-extras"), - .product(name: "NIOHTTP2", package: "swift-nio"), // Likely part of swift-nio + .product(name: "NIOHTTP2", package: "swift-nio"), .product(name: "NIOSSL", package: "swift-nio-ssl"), .product(name: "NIOTransportServices", package: "swift-nio-transport-services"), .product(name: "Numerics", package: "swift-numerics"), @@ -81,7 +78,7 @@ let package = Package( .product(name: "ZIPFoundation", package: "ZipArchive"), .product(name: "Zip", package: "Zip"), ], - path: "main/Backdoor" // Add this line for the custom path + path: "main/Backdoor" ), .testTarget( name: "BackdoorTests", From f38458102bb37e0d394f904be8eb3f2a9403c7b4 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 18:25:02 -0400 Subject: [PATCH 036/391] Update Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 399 +++++++++--------- 1 file changed, 195 insertions(+), 204 deletions(-) diff --git a/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved b/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved index c31f4cb6..49ca896e 100644 --- a/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,303 +1,294 @@ { - "originHash" : "952bcf080d88b28444dab5da5e47ebd849d15866344bba244f57e500b7e09825", - "pins" : [ + "originHash": "952bcf080d88b28444dab5da5e47ebd849d15866344bba244f57e500b7e09825", + "pins": [ { - "identity" : "alertkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/sparrowcode/AlertKit", - "state" : { - "revision" : "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", - "version" : "5.1.9" + "identity": "alertkit", + "kind": "remoteSourceControl", + "location": "https://github.com/sparrowcode/AlertKit", + "state": { + "revision": "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", + "version": "5.1.9" } }, { - "identity" : "async-http-client", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/async-http-client.git", - "state" : { - "revision" : "333f51104b75d1a5b94cb3b99e4c58a3b442c9f7", - "version" : "1.25.2" + "identity": "async-http-client", + "kind": "remoteSourceControl", + "location": "https://github.com/swift-server/async-http-client.git", + "state": { + "revision": "333f51104b75d1a5b94cb3b99e4c58a3b442c9f7", + "version": "1.25.2" } }, { - "identity" : "async-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/async-kit.git", - "state" : { - "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", - "version" : "1.20.0" + "identity": "async-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/async-kit.git", + "state": { + "revision": "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", + "version": "1.20.0" } }, { - "identity" : "bitbytedata", - "kind" : "remoteSourceControl", - "location" : "https://github.com/tsolomko/BitByteData", - "state" : { - "revision" : "cdcdc5177ad536cfb11b95c620f926a81014b7fe", - "version" : "2.0.4" + "identity": "bitbytedata", + "kind": "remoteSourceControl", + "location": "https://github.com/tsolomko/BitByteData", + "state": { + "revision": "cdcdc5177ad536cfb11b95c620f926a81014b7fe", + "version": "2.0.4" } }, { - "identity" : "console-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/console-kit.git", - "state" : { - "revision" : "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", - "version" : "4.15.2" + "identity": "console-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/console-kit.git", + "state": { + "revision": "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", + "version": "4.15.2" } }, { - "identity" : "multipart-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/multipart-kit.git", - "state" : { - "revision" : "3498e60218e6003894ff95192d756e238c01f44e", - "version" : "4.7.1" + "identity": "multipart-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/multipart-kit.git", + "state": { + "revision": "3498e60218e6003894ff95192d756e238c01f44e", + "version": "4.7.1" } }, { - "identity" : "nuke", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/Nuke", - "state" : { - "revision" : "0ead44350d2737db384908569c012fe67c421e4d", - "version" : "12.8.0" + "identity": "nuke", + "kind": "remoteSourceControl", + "location": "https://github.com/kean/Nuke", + "state": { + "revision": "0ead44350d2737db384908569c012fe67c421e4d", + "version": "12.8.0" } }, { - "identity" : "openssl-swift-package", - "kind" : "remoteSourceControl", - "location" : "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", - "state" : { - "branch" : "main", - "revision" : "309092bce7787397a183df4ad90b8291744535ae" + "identity": "openssl-swift-package", + "kind": "remoteSourceControl", + "location": "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", + "state": { + "branch": "main", + "revision": "309092bce7787397a183df4ad90b8291744535ae" } }, { - "identity" : "routing-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/routing-kit.git", - "state" : { - "revision" : "8c9a227476555c55837e569be71944e02a056b72", - "version" : "4.9.1" + "identity": "routing-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/routing-kit.git", + "state": { + "revision": "8c9a227476555c55837e569be71944e02a056b72", + "version": "4.9.1" } }, { - "identity" : "swcompression", - "kind" : "remoteSourceControl", - "location" : "https://github.com/tsolomko/SWCompression", - "state" : { - "revision" : "390e0b0af8dd19a600005a242a89e570ff482e09", - "version" : "4.8.6" + "identity": "swcompression", + "kind": "remoteSourceControl", + "location": "https://github.com/tsolomko/SWCompression", + "state": { + "revision": "390e0b0af8dd19a600005a242a89e570ff482e09", + "version": "4.8.6" } }, { - "identity" : "swift-algorithms", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-algorithms.git", - "state" : { - "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", - "version" : "1.2.1" + "identity": "swift-algorithms", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-algorithms.git", + "state": { + "revision": "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version": "1.2.1" } }, { - "identity" : "swift-asn1", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-asn1.git", - "state" : { - "revision" : "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", - "version" : "1.3.1" + "identity": "swift-asn1", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-asn1.git", + "state": { + "revision": "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", + "version": "1.3.1" } }, { - "identity" : "swift-atomics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", - "state" : { - "revision" : "cd142fd2f64be2100422d658e7411e39489da985", - "version" : "1.2.0" + "identity": "swift-atomics", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-atomics.git", + "state": { + "revision": "cd142fd2f64be2100422d658e7411e39489da985", + "version": "1.2.0" } }, { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections.git", - "state" : { - "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", - "version" : "1.1.4" + "identity": "swift-collections", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-collections.git", + "state": { + "revision": "671108c96644956dddcd89dd59c203dcdb36cec7", + "version": "1.1.4" } }, { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "45305d32cfb830faebcaa9a7aea66ad342637518", - "version" : "3.11.1" + "identity": "swift-crypto", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-crypto.git", + "state": { + "revision": "45305d32cfb830faebcaa9a7aea66ad342637518", + "version": "3.11.1" } }, { - "identity" : "swift-distributed-tracing", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-distributed-tracing.git", - "state" : { - "revision" : "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", - "version" : "1.2.0" + "identity": "swift-distributed-tracing", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-distributed-tracing.git", + "state": { + "revision": "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", + "version": "1.2.0" } }, { - "identity" : "swift-http-structured-headers", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-http-structured-headers.git", - "state" : { - "revision" : "d01361d32e14ae9b70ea5bd308a3794a198a2706", - "version" : "1.2.0" + "identity": "swift-http-structured-headers", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-http-structured-headers.git", + "state": { + "revision": "d01361d32e14ae9b70ea5bd308a3794a198a2706", + "version": "1.2.0" } }, { - "identity" : "swift-http-types", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-http-types.git", - "state" : { - "revision" : "ef18d829e8b92d731ad27bb81583edd2094d1ce3", - "version" : "1.3.1" + "identity": "swift-http-types", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-http-types.git", + "state": { + "revision": "ef18d829e8b92d731ad27bb81583edd2094d1ce3", + "version": "1.3.1" } }, { - "identity" : "swift-log", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log.git", - "state" : { - "revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91", - "version" : "1.6.2" + "identity": "swift-log", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-log.git", + "state": { + "revision": "96a2f8a0fa41e9e09af4585e2724c4e825410b91", + "version": "1.6.2" } }, { - "identity" : "swift-metrics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-metrics.git", - "state" : { - "revision" : "5e63558d12e0267782019f5dadfcae83a7d06e09", - "version" : "2.5.1" + "identity": "swift-metrics", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-metrics.git", + "state": { + "revision": "5e63558d12e0267782019f5dadfcae83a7d06e09", + "version": "2.5.1" } }, { - "identity" : "swift-nio", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio.git", - "state" : { - "revision" : "c51907a839e63ebf0ba2076bba73dd96436bd1b9", - "version" : "2.81.0" + "identity": "swift-nio", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio.git", + "state": { + "revision": "c51907a839e63ebf0ba2076bba73dd96436bd1b9", + "version": "2.81.0" } }, { - "identity" : "swift-nio-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-extras.git", - "state" : { - "revision" : "00f3f72d2f9942d0e2dc96057ab50a37ced150d4", - "version" : "1.25.0" + "identity": "swift-nio-extras", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio-extras.git", + "state": { + "revision": "00f3f72d2f9942d0e2dc96057ab50a37ced150d4", + "version": "1.25.0" } }, { - "identity" : "swift-nio-http2", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-http2.git", - "state" : { - "revision" : "170f4ca06b6a9c57b811293cebcb96e81b661310", - "version" : "1.35.0" + "identity": "swift-nio-http2", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio-http2.git", + "state": { + "revision": "170f4ca06b6a9c57b811293cebcb96e81b661310", + "version": "1.35.0" } }, { - "identity" : "swift-nio-ssl", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-ssl.git", - "state" : { - "revision" : "0cc3528ff48129d64ab9cab0b1cd621634edfc6b", - "version" : "2.29.3" + "identity": "swift-nio-ssl", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio-ssl.git", + "state": { + "revision": "0cc3528ff48129d64ab9cab0b1cd621634edfc6b", + "version": "2.29.3" } }, { - "identity" : "swift-nio-transport-services", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-transport-services.git", - "state" : { - "revision" : "3c394067c08d1225ba8442e9cffb520ded417b64", - "version" : "1.23.1" + "identity": "swift-nio-transport-services", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio-transport-services.git", + "state": { + "revision": "3c394067c08d1225ba8442e9cffb520ded417b64", + "version": "1.23.1" } }, { - "identity" : "swift-numerics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-numerics.git", - "state" : { - "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", - "version" : "1.0.3" + "identity": "swift-numerics", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-numerics.git", + "state": { + "revision": "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", + "version": "1.0.3" } }, { - "identity" : "swift-service-context", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-service-context.git", - "state" : { - "revision" : "8946c930cae601452149e45d31d8ddfac973c3c7", - "version" : "1.2.0" + "identity": "swift-service-context", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-service-context.git", + "state": { + "revision": "8946c930cae601452149e45d31d8ddfac973c3c7", + "version": "1.2.0" } }, { - "identity" : "swift-system", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-system.git", - "state" : { - "revision" : "a34201439c74b53f0fd71ef11741af7e7caf01e1", - "version" : "1.4.2" + "identity": "swift-system", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-system.git", + "state": { + "revision": "a34201439c74b53f0fd71ef11741af7e7caf01e1", + "version": "1.4.2" } }, { - "identity" : "uionboarding-18", - "kind" : "remoteSourceControl", - "location" : "https://github.com/khcrysalis/UIOnboarding-18", - "state" : { - "branch" : "main", - "revision" : "e163395903d80c032b9e47eadbbc342679b2ad77" + "identity": "uionboarding-18", + "kind": "remoteSourceControl", + "location": "https://github.com/khcrysalis/UIOnboarding-18", + "state": { + "branch": "main", + "revision": "e163395903d80c032b9e47eadbbc342679b2ad77" } }, { - "identity" : "vapor", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/vapor", - "state" : { - "revision" : "a425e32f9b9d19c0ecab952cb4484c1c15e2536f", - "version" : "4.113.2" + "identity": "vapor", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/vapor", + "state": { + "revision": "a425e32f9b9d19c0ecab952cb4484c1c15e2536f", + "version": "4.113.2" } }, { - "identity" : "websocket-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/websocket-kit.git", - "state" : { - "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740", - "version" : "2.15.0" + "identity": "websocket-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/websocket-kit.git", + "state": { + "revision": "4232d34efa49f633ba61afde365d3896fc7f8740", + "version": "2.15.0" } }, { - "identity" : "zipfoundation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/weichsel/ZIPFoundation", - "state" : { - "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", - "version" : "0.9.19" - } - }, - { - "identity" : "swift-zip", - "kind" : "remoteSourceControl", - "location" : "https://github.com/marmelroy/Zip", - "state" : { - "revision" : "a1b2c3d4e5f67890123456789abcdef012345678", - "version" : "2.1.1" + "identity": "zipfoundation", + "kind": "remoteSourceControl", + "location": "https://github.com/weichsel/ZIPFoundation", + "state": { + "revision": "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version": "0.9.19" } } ], - "version" : 3 + "version": 3 } \ No newline at end of file From 9288c3d7ff3bc8b6769a4ff14cdce886efaf440e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 18:29:26 -0400 Subject: [PATCH 037/391] Update Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 381 ++++++++++-------- 1 file changed, 204 insertions(+), 177 deletions(-) diff --git a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a2dae1c3..49ca896e 100644 --- a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,267 +1,294 @@ { - "originHash" : "952bcf080d88b28444dab5da5e47ebd849d15866344bba244f57e500b7e09825", - "pins" : [ + "originHash": "952bcf080d88b28444dab5da5e47ebd849d15866344bba244f57e500b7e09825", + "pins": [ { - "identity" : "alertkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/sparrowcode/AlertKit", - "state" : { - "revision" : "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", - "version" : "5.1.9" + "identity": "alertkit", + "kind": "remoteSourceControl", + "location": "https://github.com/sparrowcode/AlertKit", + "state": { + "revision": "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", + "version": "5.1.9" } }, { - "identity" : "async-http-client", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/async-http-client.git", - "state" : { - "revision" : "e8babad8226b9b3f956a252d5b80e36b0c9d62a9", - "version" : "1.22.0" + "identity": "async-http-client", + "kind": "remoteSourceControl", + "location": "https://github.com/swift-server/async-http-client.git", + "state": { + "revision": "333f51104b75d1a5b94cb3b99e4c58a3b442c9f7", + "version": "1.25.2" } }, { - "identity" : "async-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/async-kit.git", - "state" : { - "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", - "version" : "1.20.0" + "identity": "async-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/async-kit.git", + "state": { + "revision": "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", + "version": "1.20.0" } }, { - "identity" : "bitbytedata", - "kind" : "remoteSourceControl", - "location" : "https://github.com/tsolomko/BitByteData", - "state" : { - "revision" : "cdcdc5177ad536cfb11b95c620f926a81014b7fe", - "version" : "2.0.4" + "identity": "bitbytedata", + "kind": "remoteSourceControl", + "location": "https://github.com/tsolomko/BitByteData", + "state": { + "revision": "cdcdc5177ad536cfb11b95c620f926a81014b7fe", + "version": "2.0.4" } }, { - "identity" : "console-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/console-kit.git", - "state" : { - "revision" : "78c0dd739df8cb9ee14a8bbbf770facc4fc3402a", - "version" : "4.15.0" + "identity": "console-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/console-kit.git", + "state": { + "revision": "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", + "version": "4.15.2" } }, { - "identity" : "multipart-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/multipart-kit.git", - "state" : { - "revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68", - "version" : "4.7.0" + "identity": "multipart-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/multipart-kit.git", + "state": { + "revision": "3498e60218e6003894ff95192d756e238c01f44e", + "version": "4.7.1" } }, { - "identity" : "nuke", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/Nuke", - "state" : { - "revision" : "0ead44350d2737db384908569c012fe67c421e4d", - "version" : "12.8.0" + "identity": "nuke", + "kind": "remoteSourceControl", + "location": "https://github.com/kean/Nuke", + "state": { + "revision": "0ead44350d2737db384908569c012fe67c421e4d", + "version": "12.8.0" } }, { - "identity" : "openssl-swift-package", - "kind" : "remoteSourceControl", - "location" : "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", - "state" : { - "branch" : "main", - "revision" : "309092bce7787397a183df4ad90b8291744535ae" + "identity": "openssl-swift-package", + "kind": "remoteSourceControl", + "location": "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", + "state": { + "branch": "main", + "revision": "309092bce7787397a183df4ad90b8291744535ae" } }, { - "identity" : "routing-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/routing-kit.git", - "state" : { - "revision" : "8c9a227476555c55837e569be71944e02a056b72", - "version" : "4.9.1" + "identity": "routing-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/routing-kit.git", + "state": { + "revision": "8c9a227476555c55837e569be71944e02a056b72", + "version": "4.9.1" } }, { - "identity" : "swcompression", - "kind" : "remoteSourceControl", - "location" : "https://github.com/tsolomko/SWCompression", - "state" : { - "revision" : "390e0b0af8dd19a600005a242a89e570ff482e09", - "version" : "4.8.6" + "identity": "swcompression", + "kind": "remoteSourceControl", + "location": "https://github.com/tsolomko/SWCompression", + "state": { + "revision": "390e0b0af8dd19a600005a242a89e570ff482e09", + "version": "4.8.6" } }, { - "identity" : "swift-algorithms", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-algorithms.git", - "state" : { - "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", - "version" : "1.2.0" + "identity": "swift-algorithms", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-algorithms.git", + "state": { + "revision": "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version": "1.2.1" } }, { - "identity" : "swift-atomics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", - "state" : { - "revision" : "cd142fd2f64be2100422d658e7411e39489da985", - "version" : "1.2.0" + "identity": "swift-asn1", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-asn1.git", + "state": { + "revision": "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", + "version": "1.3.1" } }, { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections.git", - "state" : { - "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", - "version" : "1.1.2" + "identity": "swift-atomics", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-atomics.git", + "state": { + "revision": "cd142fd2f64be2100422d658e7411e39489da985", + "version": "1.2.0" } }, { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "a53a7e8f858902659d4784322bede34f4e49097e", - "version" : "3.6.1" + "identity": "swift-collections", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-collections.git", + "state": { + "revision": "671108c96644956dddcd89dd59c203dcdb36cec7", + "version": "1.1.4" } }, { - "identity" : "swift-http-types", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-http-types", - "state" : { - "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", - "version" : "1.3.0" + "identity": "swift-crypto", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-crypto.git", + "state": { + "revision": "45305d32cfb830faebcaa9a7aea66ad342637518", + "version": "3.11.1" } }, { - "identity" : "swift-log", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log.git", - "state" : { - "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", - "version" : "1.6.1" + "identity": "swift-distributed-tracing", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-distributed-tracing.git", + "state": { + "revision": "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", + "version": "1.2.0" } }, { - "identity" : "swift-metrics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-metrics.git", - "state" : { - "revision" : "e0165b53d49b413dd987526b641e05e246782685", - "version" : "2.5.0" + "identity": "swift-http-structured-headers", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-http-structured-headers.git", + "state": { + "revision": "d01361d32e14ae9b70ea5bd308a3794a198a2706", + "version": "1.2.0" } }, { - "identity" : "swift-nio", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio.git", - "state" : { - "revision" : "4c4453b489cf76e6b3b0f300aba663eb78182fad", - "version" : "2.70.0" + "identity": "swift-http-types", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-http-types.git", + "state": { + "revision": "ef18d829e8b92d731ad27bb81583edd2094d1ce3", + "version": "1.3.1" } }, { - "identity" : "swift-nio-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-extras.git", - "state" : { - "revision" : "d1ead62745cc3269e482f1c51f27608057174379", - "version" : "1.24.0" + "identity": "swift-log", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-log.git", + "state": { + "revision": "96a2f8a0fa41e9e09af4585e2724c4e825410b91", + "version": "1.6.2" } }, { - "identity" : "swift-nio-http2", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-http2.git", - "state" : { - "revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94", - "version" : "1.34.0" + "identity": "swift-metrics", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-metrics.git", + "state": { + "revision": "5e63558d12e0267782019f5dadfcae83a7d06e09", + "version": "2.5.1" } }, { - "identity" : "swift-nio-ssl", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-ssl.git", - "state" : { - "revision" : "7b84abbdcef69cc3be6573ac12440220789dcd69", - "version" : "2.27.2" + "identity": "swift-nio", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio.git", + "state": { + "revision": "c51907a839e63ebf0ba2076bba73dd96436bd1b9", + "version": "2.81.0" } }, { - "identity" : "swift-nio-transport-services", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-transport-services.git", - "state" : { - "revision" : "38ac8221dd20674682148d6451367f89c2652980", - "version" : "1.21.0" + "identity": "swift-nio-extras", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio-extras.git", + "state": { + "revision": "00f3f72d2f9942d0e2dc96057ab50a37ced150d4", + "version": "1.25.0" } }, { - "identity" : "swift-numerics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-numerics.git", - "state" : { - "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", - "version" : "1.0.2" + "identity": "swift-nio-http2", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio-http2.git", + "state": { + "revision": "170f4ca06b6a9c57b811293cebcb96e81b661310", + "version": "1.35.0" } }, { - "identity" : "swift-system", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-system.git", - "state" : { - "revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5", - "version" : "1.3.2" + "identity": "swift-nio-ssl", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio-ssl.git", + "state": { + "revision": "0cc3528ff48129d64ab9cab0b1cd621634edfc6b", + "version": "2.29.3" } }, { - "identity" : "uionboarding-18", - "kind" : "remoteSourceControl", - "location" : "https://github.com/khcrysalis/UIOnboarding-18", - "state" : { - "branch" : "main", - "revision" : "e163395903d80c032b9e47eadbbc342679b2ad77" + "identity": "swift-nio-transport-services", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-nio-transport-services.git", + "state": { + "revision": "3c394067c08d1225ba8442e9cffb520ded417b64", + "version": "1.23.1" } }, { - "identity" : "vapor", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/vapor", - "state" : { - "revision" : "236025b6a213f372f81b732ff792f95789c089d4", - "version" : "4.104.0" + "identity": "swift-numerics", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-numerics.git", + "state": { + "revision": "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", + "version": "1.0.3" } }, { - "identity" : "websocket-kit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vapor/websocket-kit.git", - "state" : { - "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740", - "version" : "2.15.0" + "identity": "swift-service-context", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-service-context.git", + "state": { + "revision": "8946c930cae601452149e45d31d8ddfac973c3c7", + "version": "1.2.0" } }, { - "identity" : "zipfoundation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/weichsel/ZIPFoundation", - "state" : { - "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", - "version" : "0.9.19" + "identity": "swift-system", + "kind": "remoteSourceControl", + "location": "https://github.com/apple/swift-system.git", + "state": { + "revision": "a34201439c74b53f0fd71ef11741af7e7caf01e1", + "version": "1.4.2" } }, { - "identity" : "swift-zip", - "kind" : "remoteSourceControl", - "location" : "https://github.com/marmelroy/Zip", - "state" : { - "revision" : "a1b2c3d4e5f67890123456789abcdef012345678", - "version" : "2.1.1" + "identity": "uionboarding-18", + "kind": "remoteSourceControl", + "location": "https://github.com/khcrysalis/UIOnboarding-18", + "state": { + "branch": "main", + "revision": "e163395903d80c032b9e47eadbbc342679b2ad77" + } + }, + { + "identity": "vapor", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/vapor", + "state": { + "revision": "a425e32f9b9d19c0ecab952cb4484c1c15e2536f", + "version": "4.113.2" + } + }, + { + "identity": "websocket-kit", + "kind": "remoteSourceControl", + "location": "https://github.com/vapor/websocket-kit.git", + "state": { + "revision": "4232d34efa49f633ba61afde365d3896fc7f8740", + "version": "2.15.0" + } + }, + { + "identity": "zipfoundation", + "kind": "remoteSourceControl", + "location": "https://github.com/weichsel/ZIPFoundation", + "state": { + "revision": "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version": "0.9.19" } } ], - "version" : 3 + "version": 3 } \ No newline at end of file From e4664801b535bbfa87c209cbcfcdc664542418ce Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 18:32:38 -0400 Subject: [PATCH 038/391] Update Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 390 +++++++++--------- 1 file changed, 195 insertions(+), 195 deletions(-) diff --git a/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved b/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved index 49ca896e..cc4548da 100644 --- a/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/feather.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,294 +1,294 @@ { - "originHash": "952bcf080d88b28444dab5da5e47ebd849d15866344bba244f57e500b7e09825", - "pins": [ + "originHash" : "952bcf080d88b28444dab5da5e47ebd849d15866344bba244f57e500b7e09825", + "pins" : [ { - "identity": "alertkit", - "kind": "remoteSourceControl", - "location": "https://github.com/sparrowcode/AlertKit", - "state": { - "revision": "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", - "version": "5.1.9" + "identity" : "alertkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparrowcode/AlertKit", + "state" : { + "revision" : "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", + "version" : "5.1.9" } }, { - "identity": "async-http-client", - "kind": "remoteSourceControl", - "location": "https://github.com/swift-server/async-http-client.git", - "state": { - "revision": "333f51104b75d1a5b94cb3b99e4c58a3b442c9f7", - "version": "1.25.2" + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "333f51104b75d1a5b94cb3b99e4c58a3b442c9f7", + "version" : "1.25.2" } }, { - "identity": "async-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/async-kit.git", - "state": { - "revision": "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", - "version": "1.20.0" + "identity" : "async-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/async-kit.git", + "state" : { + "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", + "version" : "1.20.0" } }, { - "identity": "bitbytedata", - "kind": "remoteSourceControl", - "location": "https://github.com/tsolomko/BitByteData", - "state": { - "revision": "cdcdc5177ad536cfb11b95c620f926a81014b7fe", - "version": "2.0.4" + "identity" : "bitbytedata", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tsolomko/BitByteData", + "state" : { + "revision" : "cdcdc5177ad536cfb11b95c620f926a81014b7fe", + "version" : "2.0.4" } }, { - "identity": "console-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/console-kit.git", - "state": { - "revision": "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", - "version": "4.15.2" + "identity" : "console-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/console-kit.git", + "state" : { + "revision" : "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", + "version" : "4.15.2" } }, { - "identity": "multipart-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/multipart-kit.git", - "state": { - "revision": "3498e60218e6003894ff95192d756e238c01f44e", - "version": "4.7.1" + "identity" : "multipart-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/multipart-kit.git", + "state" : { + "revision" : "3498e60218e6003894ff95192d756e238c01f44e", + "version" : "4.7.1" } }, { - "identity": "nuke", - "kind": "remoteSourceControl", - "location": "https://github.com/kean/Nuke", - "state": { - "revision": "0ead44350d2737db384908569c012fe67c421e4d", - "version": "12.8.0" + "identity" : "nuke", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke", + "state" : { + "revision" : "0ead44350d2737db384908569c012fe67c421e4d", + "version" : "12.8.0" } }, { - "identity": "openssl-swift-package", - "kind": "remoteSourceControl", - "location": "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", - "state": { - "branch": "main", - "revision": "309092bce7787397a183df4ad90b8291744535ae" + "identity" : "openssl-swift-package", + "kind" : "remoteSourceControl", + "location" : "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", + "state" : { + "branch" : "main", + "revision" : "309092bce7787397a183df4ad90b8291744535ae" } }, { - "identity": "routing-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/routing-kit.git", - "state": { - "revision": "8c9a227476555c55837e569be71944e02a056b72", - "version": "4.9.1" + "identity" : "routing-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/routing-kit.git", + "state" : { + "revision" : "8c9a227476555c55837e569be71944e02a056b72", + "version" : "4.9.1" } }, { - "identity": "swcompression", - "kind": "remoteSourceControl", - "location": "https://github.com/tsolomko/SWCompression", - "state": { - "revision": "390e0b0af8dd19a600005a242a89e570ff482e09", - "version": "4.8.6" + "identity" : "swcompression", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tsolomko/SWCompression", + "state" : { + "revision" : "390e0b0af8dd19a600005a242a89e570ff482e09", + "version" : "4.8.6" } }, { - "identity": "swift-algorithms", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-algorithms.git", - "state": { - "revision": "87e50f483c54e6efd60e885f7f5aa946cee68023", - "version": "1.2.1" + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version" : "1.2.1" } }, { - "identity": "swift-asn1", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-asn1.git", - "state": { - "revision": "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", - "version": "1.3.1" + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", + "version" : "1.3.1" } }, { - "identity": "swift-atomics", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-atomics.git", - "state": { - "revision": "cd142fd2f64be2100422d658e7411e39489da985", - "version": "1.2.0" + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" } }, { - "identity": "swift-collections", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-collections.git", - "state": { - "revision": "671108c96644956dddcd89dd59c203dcdb36cec7", - "version": "1.1.4" + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" } }, { - "identity": "swift-crypto", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-crypto.git", - "state": { - "revision": "45305d32cfb830faebcaa9a7aea66ad342637518", - "version": "3.11.1" + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "45305d32cfb830faebcaa9a7aea66ad342637518", + "version" : "3.11.1" } }, { - "identity": "swift-distributed-tracing", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-distributed-tracing.git", - "state": { - "revision": "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", - "version": "1.2.0" + "identity" : "swift-distributed-tracing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-distributed-tracing.git", + "state" : { + "revision" : "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", + "version" : "1.2.0" } }, { - "identity": "swift-http-structured-headers", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-http-structured-headers.git", - "state": { - "revision": "d01361d32e14ae9b70ea5bd308a3794a198a2706", - "version": "1.2.0" + "identity" : "swift-http-structured-headers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-structured-headers.git", + "state" : { + "revision" : "d01361d32e14ae9b70ea5bd308a3794a198a2706", + "version" : "1.2.0" } }, { - "identity": "swift-http-types", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-http-types.git", - "state": { - "revision": "ef18d829e8b92d731ad27bb81583edd2094d1ce3", - "version": "1.3.1" + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types.git", + "state" : { + "revision" : "ef18d829e8b92d731ad27bb81583edd2094d1ce3", + "version" : "1.3.1" } }, { - "identity": "swift-log", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-log.git", - "state": { - "revision": "96a2f8a0fa41e9e09af4585e2724c4e825410b91", - "version": "1.6.2" + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91", + "version" : "1.6.2" } }, { - "identity": "swift-metrics", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-metrics.git", - "state": { - "revision": "5e63558d12e0267782019f5dadfcae83a7d06e09", - "version": "2.5.1" + "identity" : "swift-metrics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-metrics.git", + "state" : { + "revision" : "5e63558d12e0267782019f5dadfcae83a7d06e09", + "version" : "2.5.1" } }, { - "identity": "swift-nio", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio.git", - "state": { - "revision": "c51907a839e63ebf0ba2076bba73dd96436bd1b9", - "version": "2.81.0" + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "c51907a839e63ebf0ba2076bba73dd96436bd1b9", + "version" : "2.81.0" } }, { - "identity": "swift-nio-extras", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio-extras.git", - "state": { - "revision": "00f3f72d2f9942d0e2dc96057ab50a37ced150d4", - "version": "1.25.0" + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "00f3f72d2f9942d0e2dc96057ab50a37ced150d4", + "version" : "1.25.0" } }, { - "identity": "swift-nio-http2", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio-http2.git", - "state": { - "revision": "170f4ca06b6a9c57b811293cebcb96e81b661310", - "version": "1.35.0" + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "170f4ca06b6a9c57b811293cebcb96e81b661310", + "version" : "1.35.0" } }, { - "identity": "swift-nio-ssl", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio-ssl.git", - "state": { - "revision": "0cc3528ff48129d64ab9cab0b1cd621634edfc6b", - "version": "2.29.3" + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "0cc3528ff48129d64ab9cab0b1cd621634edfc6b", + "version" : "2.29.3" } }, { - "identity": "swift-nio-transport-services", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio-transport-services.git", - "state": { - "revision": "3c394067c08d1225ba8442e9cffb520ded417b64", - "version": "1.23.1" + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "3c394067c08d1225ba8442e9cffb520ded417b64", + "version" : "1.23.1" } }, { - "identity": "swift-numerics", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-numerics.git", - "state": { - "revision": "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", - "version": "1.0.3" + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", + "version" : "1.0.3" } }, { - "identity": "swift-service-context", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-service-context.git", - "state": { - "revision": "8946c930cae601452149e45d31d8ddfac973c3c7", - "version": "1.2.0" + "identity" : "swift-service-context", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-service-context.git", + "state" : { + "revision" : "8946c930cae601452149e45d31d8ddfac973c3c7", + "version" : "1.2.0" } }, { - "identity": "swift-system", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-system.git", - "state": { - "revision": "a34201439c74b53f0fd71ef11741af7e7caf01e1", - "version": "1.4.2" + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "a34201439c74b53f0fd71ef11741af7e7caf01e1", + "version" : "1.4.2" } }, { - "identity": "uionboarding-18", - "kind": "remoteSourceControl", - "location": "https://github.com/khcrysalis/UIOnboarding-18", - "state": { - "branch": "main", - "revision": "e163395903d80c032b9e47eadbbc342679b2ad77" + "identity" : "uionboarding-18", + "kind" : "remoteSourceControl", + "location" : "https://github.com/khcrysalis/UIOnboarding-18", + "state" : { + "branch" : "main", + "revision" : "e163395903d80c032b9e47eadbbc342679b2ad77" } }, { - "identity": "vapor", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/vapor", - "state": { - "revision": "a425e32f9b9d19c0ecab952cb4484c1c15e2536f", - "version": "4.113.2" + "identity" : "vapor", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/vapor", + "state" : { + "revision" : "a425e32f9b9d19c0ecab952cb4484c1c15e2536f", + "version" : "4.113.2" } }, { - "identity": "websocket-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/websocket-kit.git", - "state": { - "revision": "4232d34efa49f633ba61afde365d3896fc7f8740", - "version": "2.15.0" + "identity" : "websocket-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/websocket-kit.git", + "state" : { + "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740", + "version" : "2.15.0" } }, { - "identity": "zipfoundation", - "kind": "remoteSourceControl", - "location": "https://github.com/weichsel/ZIPFoundation", - "state": { - "revision": "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", - "version": "0.9.19" + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation", + "state" : { + "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version" : "0.9.19" } } ], - "version": 3 + "version" : 3 } \ No newline at end of file From 33682ebda105a3191525b89673b5944d27d64ddf Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 18:33:46 -0400 Subject: [PATCH 039/391] Update Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 378 ++++++++---------- 1 file changed, 171 insertions(+), 207 deletions(-) diff --git a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 49ca896e..c52c4ca0 100644 --- a/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/feather.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,294 +1,258 @@ { - "originHash": "952bcf080d88b28444dab5da5e47ebd849d15866344bba244f57e500b7e09825", - "pins": [ + "originHash" : "952bcf080d88b28444dab5da5e47ebd849d15866344bba244f57e500b7e09825", + "pins" : [ { - "identity": "alertkit", - "kind": "remoteSourceControl", - "location": "https://github.com/sparrowcode/AlertKit", - "state": { - "revision": "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", - "version": "5.1.9" + "identity" : "alertkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparrowcode/AlertKit", + "state" : { + "revision" : "3b73be8db5a7e7efaf474c6ed919f5a437d843c9", + "version" : "5.1.9" } }, { - "identity": "async-http-client", - "kind": "remoteSourceControl", - "location": "https://github.com/swift-server/async-http-client.git", - "state": { - "revision": "333f51104b75d1a5b94cb3b99e4c58a3b442c9f7", - "version": "1.25.2" + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "e8babad8226b9b3f956a252d5b80e36b0c9d62a9", + "version" : "1.22.0" } }, { - "identity": "async-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/async-kit.git", - "state": { - "revision": "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", - "version": "1.20.0" + "identity" : "async-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/async-kit.git", + "state" : { + "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", + "version" : "1.20.0" } }, { - "identity": "bitbytedata", - "kind": "remoteSourceControl", - "location": "https://github.com/tsolomko/BitByteData", - "state": { - "revision": "cdcdc5177ad536cfb11b95c620f926a81014b7fe", - "version": "2.0.4" + "identity" : "bitbytedata", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tsolomko/BitByteData", + "state" : { + "revision" : "cdcdc5177ad536cfb11b95c620f926a81014b7fe", + "version" : "2.0.4" } }, { - "identity": "console-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/console-kit.git", - "state": { - "revision": "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", - "version": "4.15.2" + "identity" : "console-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/console-kit.git", + "state" : { + "revision" : "78c0dd739df8cb9ee14a8bbbf770facc4fc3402a", + "version" : "4.15.0" } }, { - "identity": "multipart-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/multipart-kit.git", - "state": { - "revision": "3498e60218e6003894ff95192d756e238c01f44e", - "version": "4.7.1" + "identity" : "multipart-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/multipart-kit.git", + "state" : { + "revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68", + "version" : "4.7.0" } }, { - "identity": "nuke", - "kind": "remoteSourceControl", - "location": "https://github.com/kean/Nuke", - "state": { - "revision": "0ead44350d2737db384908569c012fe67c421e4d", - "version": "12.8.0" + "identity" : "nuke", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke", + "state" : { + "revision" : "0ead44350d2737db384908569c012fe67c421e4d", + "version" : "12.8.0" } }, { - "identity": "openssl-swift-package", - "kind": "remoteSourceControl", - "location": "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", - "state": { - "branch": "main", - "revision": "309092bce7787397a183df4ad90b8291744535ae" + "identity" : "openssl-swift-package", + "kind" : "remoteSourceControl", + "location" : "https://github.com/HAHALOSAH/OpenSSL-Swift-Package", + "state" : { + "branch" : "main", + "revision" : "309092bce7787397a183df4ad90b8291744535ae" } }, { - "identity": "routing-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/routing-kit.git", - "state": { - "revision": "8c9a227476555c55837e569be71944e02a056b72", - "version": "4.9.1" + "identity" : "routing-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/routing-kit.git", + "state" : { + "revision" : "8c9a227476555c55837e569be71944e02a056b72", + "version" : "4.9.1" } }, { - "identity": "swcompression", - "kind": "remoteSourceControl", - "location": "https://github.com/tsolomko/SWCompression", - "state": { - "revision": "390e0b0af8dd19a600005a242a89e570ff482e09", - "version": "4.8.6" + "identity" : "swcompression", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tsolomko/SWCompression", + "state" : { + "revision" : "390e0b0af8dd19a600005a242a89e570ff482e09", + "version" : "4.8.6" } }, { - "identity": "swift-algorithms", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-algorithms.git", - "state": { - "revision": "87e50f483c54e6efd60e885f7f5aa946cee68023", - "version": "1.2.1" + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version" : "1.2.0" } }, { - "identity": "swift-asn1", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-asn1.git", - "state": { - "revision": "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", - "version": "1.3.1" + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" } }, { - "identity": "swift-atomics", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-atomics.git", - "state": { - "revision": "cd142fd2f64be2100422d658e7411e39489da985", - "version": "1.2.0" + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", + "version" : "1.1.2" } }, { - "identity": "swift-collections", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-collections.git", - "state": { - "revision": "671108c96644956dddcd89dd59c203dcdb36cec7", - "version": "1.1.4" + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "a53a7e8f858902659d4784322bede34f4e49097e", + "version" : "3.6.1" } }, { - "identity": "swift-crypto", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-crypto.git", - "state": { - "revision": "45305d32cfb830faebcaa9a7aea66ad342637518", - "version": "3.11.1" + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", + "version" : "1.3.0" } }, { - "identity": "swift-distributed-tracing", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-distributed-tracing.git", - "state": { - "revision": "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", - "version": "1.2.0" + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" } }, { - "identity": "swift-http-structured-headers", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-http-structured-headers.git", - "state": { - "revision": "d01361d32e14ae9b70ea5bd308a3794a198a2706", - "version": "1.2.0" + "identity" : "swift-metrics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-metrics.git", + "state" : { + "revision" : "e0165b53d49b413dd987526b641e05e246782685", + "version" : "2.5.0" } }, { - "identity": "swift-http-types", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-http-types.git", - "state": { - "revision": "ef18d829e8b92d731ad27bb81583edd2094d1ce3", - "version": "1.3.1" + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "4c4453b489cf76e6b3b0f300aba663eb78182fad", + "version" : "2.70.0" } }, { - "identity": "swift-log", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-log.git", - "state": { - "revision": "96a2f8a0fa41e9e09af4585e2724c4e825410b91", - "version": "1.6.2" + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "d1ead62745cc3269e482f1c51f27608057174379", + "version" : "1.24.0" } }, { - "identity": "swift-metrics", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-metrics.git", - "state": { - "revision": "5e63558d12e0267782019f5dadfcae83a7d06e09", - "version": "2.5.1" + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94", + "version" : "1.34.0" } }, { - "identity": "swift-nio", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio.git", - "state": { - "revision": "c51907a839e63ebf0ba2076bba73dd96436bd1b9", - "version": "2.81.0" + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "7b84abbdcef69cc3be6573ac12440220789dcd69", + "version" : "2.27.2" } }, { - "identity": "swift-nio-extras", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio-extras.git", - "state": { - "revision": "00f3f72d2f9942d0e2dc96057ab50a37ced150d4", - "version": "1.25.0" + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "38ac8221dd20674682148d6451367f89c2652980", + "version" : "1.21.0" } }, { - "identity": "swift-nio-http2", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio-http2.git", - "state": { - "revision": "170f4ca06b6a9c57b811293cebcb96e81b661310", - "version": "1.35.0" + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" } }, { - "identity": "swift-nio-ssl", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio-ssl.git", - "state": { - "revision": "0cc3528ff48129d64ab9cab0b1cd621634edfc6b", - "version": "2.29.3" + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5", + "version" : "1.3.2" } }, { - "identity": "swift-nio-transport-services", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-nio-transport-services.git", - "state": { - "revision": "3c394067c08d1225ba8442e9cffb520ded417b64", - "version": "1.23.1" + "identity" : "uionboarding-18", + "kind" : "remoteSourceControl", + "location" : "https://github.com/khcrysalis/UIOnboarding-18", + "state" : { + "branch" : "main", + "revision" : "e163395903d80c032b9e47eadbbc342679b2ad77" } }, { - "identity": "swift-numerics", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-numerics.git", - "state": { - "revision": "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", - "version": "1.0.3" + "identity" : "vapor", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/vapor", + "state" : { + "revision" : "236025b6a213f372f81b732ff792f95789c089d4", + "version" : "4.104.0" } }, { - "identity": "swift-service-context", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-service-context.git", - "state": { - "revision": "8946c930cae601452149e45d31d8ddfac973c3c7", - "version": "1.2.0" + "identity" : "websocket-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/websocket-kit.git", + "state" : { + "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740", + "version" : "2.15.0" } }, { - "identity": "swift-system", - "kind": "remoteSourceControl", - "location": "https://github.com/apple/swift-system.git", - "state": { - "revision": "a34201439c74b53f0fd71ef11741af7e7caf01e1", - "version": "1.4.2" - } - }, - { - "identity": "uionboarding-18", - "kind": "remoteSourceControl", - "location": "https://github.com/khcrysalis/UIOnboarding-18", - "state": { - "branch": "main", - "revision": "e163395903d80c032b9e47eadbbc342679b2ad77" - } - }, - { - "identity": "vapor", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/vapor", - "state": { - "revision": "a425e32f9b9d19c0ecab952cb4484c1c15e2536f", - "version": "4.113.2" - } - }, - { - "identity": "websocket-kit", - "kind": "remoteSourceControl", - "location": "https://github.com/vapor/websocket-kit.git", - "state": { - "revision": "4232d34efa49f633ba61afde365d3896fc7f8740", - "version": "2.15.0" - } - }, - { - "identity": "zipfoundation", - "kind": "remoteSourceControl", - "location": "https://github.com/weichsel/ZIPFoundation", - "state": { - "revision": "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", - "version": "0.9.19" + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation", + "state" : { + "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version" : "0.9.19" } } ], - "version": 3 + "version" : 3 } \ No newline at end of file From 38df15c1b8ee306dd369109936bd4b1f2c34b3d2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 19:41:17 -0400 Subject: [PATCH 040/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 32 +++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 775944f4..9ad464bd 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,5 +1,5 @@ import UIKit -import Zip +import ZIPFoundation class HomeViewController: UIViewController { @@ -7,6 +7,7 @@ class HomeViewController: UIViewController { private var ipaPath: String = "" private var fileList: [String] = [] + private let fileManager = FileManager.default // MARK: - UI Elements private let selectIPAButton: UIButton = { @@ -91,22 +92,33 @@ class HomeViewController: UIViewController { do { let zipFilePath = URL(fileURLWithPath: ipaPath) - let fileManager = FileManager.default - let destinationPath = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("extracted") + + let destinationURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("extracted") + // Check if destination directory exists, create if necessary - if !fileManager.fileExists(atPath: destinationPath.path) { - try fileManager.createDirectory(at: destinationPath, withIntermediateDirectories: true, attributes: nil) + if !fileManager.fileExists(atPath: destinationURL.path) { + try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) } - try Zip.unzipFile(zipFilePath, destination: destinationPath, overwrite: true, password: nil) { (progress) -> () in - print(String(format: "Progress: %.2f", progress*100)) + let archive = try Archive(url: zipFilePath, accessMode: .read) + + for entry in archive { + var destination = destinationURL.appendingPathComponent(entry.path) + if entry.isDirectory { + try fileManager.createDirectory(at: destination, withIntermediateDirectories: true, attributes: nil) + } else { + try archive.extract(entry, to: destination) + } + print("Extracted \(entry.path)") } + + // List files after extraction - let contents = try fileManager.contentsOfDirectory(atPath: destinationPath.path) - fileList = contents - fileListTableView.reloadData() // Reload the table view to display the files + let contents = try fileManager.contentsOfDirectory(atPath: destinationURL.path) + fileList = contents + fileListTableView.reloadData() // Reload the table view to display the files } catch { print("Extraction failed with error: \(error)") From 8594c483c837b3c72433b31e5a5fb8a2e03c4853 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 19:41:50 -0400 Subject: [PATCH 041/391] Delete Package.swift --- Package.swift | 88 --------------------------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 Package.swift diff --git a/Package.swift b/Package.swift deleted file mode 100644 index 03b4ed31..00000000 --- a/Package.swift +++ /dev/null @@ -1,88 +0,0 @@ -// swift-tools-version:5.3 -import PackageDescription - -let package = Package( - name: "Backdoor", - platforms: [ - .iOS(.v13) - ], - products: [ - .library( - name: "Backdoor", - targets: ["Backdoor"] - ) - ], - dependencies: [ - .package(url: "https://github.com/huri000/SwiftUIOnboarding", from: "2.0.0"), - .package(url: "https://github.com/kean/Nuke", from: "12.0.0"), - .package(url: "https://github.com/vapor/vapor", from: "4.0.0"), - .package(url: "https://github.com/vapor/websocket-kit", from: "1.0.0"), - .package(url: "https://github.com/vapor/async-kit", from: "1.0.0"), - .package(url: "https://github.com/vapor/console-kit", from: "4.0.0"), - .package(url: "https://github.com/vapor/routing-kit", from: "4.0.0"), - .package(url: "https://github.com/vapor/multipart-kit", from: "4.0.0"), - .package(url: "https://github.com/vapor/http", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-algorithms", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-collections", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-numerics", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-nio", from: "2.0.0"), - .package(url: "https://github.com/apple/swift-nio-extras", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-nio-transport-services", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-nio-ssl", from: "2.0.0"), - .package(url: "https://github.com/swift-server/async-http-client", from: "1.0.0"), - .package(url: "https://github.com/swift-server/swift-backtrace", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-metrics", from: "2.0.0"), - .package(url: "https://github.com/apple/swift-log", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-crypto", from: "2.0.0"), - .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), - .package(url: "https://github.com/ZipArchive/ZipArchive", from: "2.0.0"), - .package(url: "https://github.com/marmelroy/Zip", from: "2.1.2"), // Updated to 2.1.2 - .package(url: "https://github.com/SammySmallman/BitByteData", from: "2.0.0"), - .package(url: "https://github.com/alexsteinerde/SwiftLMDB", from: "0.9.7"), - .package(url: "https://github.com/tsolomko/SWCompression", from: "4.6.0"), - .package(url: "https://github.com/Kitura/OpenSSL", from: "2.0.0"), - .package(url: "https://github.com/sparrowcode/AlertKit", from: "2.0.0"), - .package(url: "https://github.com/Flight-School/AnyCodable", from: "0.6.0") - ], - targets: [ - .target( - name: "Backdoor", - dependencies: [ - .product(name: "AlertKit", package: "AlertKit"), - .product(name: "AsyncHTTPClient", package: "async-http-client"), - .product(name: "AsyncKit", package: "async-kit"), - .product(name: "BitByteData", package: "BitByteData"), - .product(name: "ConsoleKit", package: "console-kit"), - .product(name: "MultipartKit", package: "multipart-kit"), - .product(name: "Nuke", package: "Nuke"), - .product(name: "OpenSSL", package: "OpenSSL"), - .product(name: "RoutingKit", package: "routing-kit"), - .product(name: "SWCompression", package: "SWCompression"), - .product(name: "Algorithms", package: "swift-algorithms"), - .product(name: "Atomics", package: "swift-backtrace"), // Note: This might not be correct; 'Atomics' is typically from 'swift-atomics' - .product(name: "Collections", package: "swift-collections"), - .product(name: "Crypto", package: "swift-crypto"), - .product(name: "HTTPTypes", package: "http"), - .product(name: "Logging", package: "swift-log"), - .product(name: "Metrics", package: "swift-metrics"), - .product(name: "NIO", package: "swift-nio"), - .product(name: "NIOExtras", package: "swift-nio-extras"), - .product(name: "NIOHTTP2", package: "swift-nio"), - .product(name: "NIOSSL", package: "swift-nio-ssl"), - .product(name: "NIOTransportServices", package: "swift-nio-transport-services"), - .product(name: "Numerics", package: "swift-numerics"), - .product(name: "System", package: "swift-system"), - .product(name: "SwiftUIOnboarding", package: "SwiftUIOnboarding"), - .product(name: "Vapor", package: "vapor"), - .product(name: "WebSocketKit", package: "websocket-kit"), - .product(name: "ZIPFoundation", package: "ZipArchive"), - .product(name: "Zip", package: "Zip"), - ], - path: "main/Backdoor" - ), - .testTarget( - name: "BackdoorTests", - dependencies: ["Backdoor"] - ) - ] -) \ No newline at end of file From 5114fd82f7c189795ec9e6d046d676d1de05ee35 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 19:44:01 -0400 Subject: [PATCH 042/391] Update main.yml --- .github/workflows/main.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16c52bb7..9fb3c41e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,6 @@ jobs: sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid brew install 7zip gnu-sed - - name: Resolve Swift Package Manager dependencies - run: swift package resolve - - name: Update Optimization Level run: | sed -i '' 's/SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";/SWIFT_OPTIMIZATION_LEVEL = "-Onone";/g' feather.xcodeproj/project.pbxproj @@ -52,4 +49,5 @@ jobs: fail_on_unmatched_files: true draft: true env: - GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} +```​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ \ No newline at end of file From 67fa42ca68e17db6c19446421768eafa55c91c77 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 19:47:16 -0400 Subject: [PATCH 043/391] Update main.yml --- .github/workflows/main.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9fb3c41e..8c01ad98 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,7 @@ name: Create New Release on: + workflow_dispatch: jobs: @@ -16,12 +17,7 @@ jobs: sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid brew install 7zip gnu-sed - - name: Update Optimization Level - run: | - sed -i '' 's/SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";/SWIFT_OPTIMIZATION_LEVEL = "-Onone";/g' feather.xcodeproj/project.pbxproj - sed -i '' 's/SWIFT_OPTIMIZATION_LEVEL = "-O";/SWIFT_OPTIMIZATION_LEVEL = "-Onone";/g' feather.xcodeproj/project.pbxproj - - - name: Compile + - name: Compile f run: | mkdir upload make package SCHEME="'feather (Release)'" @@ -49,5 +45,4 @@ jobs: fail_on_unmatched_files: true draft: true env: - GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} -```​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ \ No newline at end of file + GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }} \ No newline at end of file From a324f920be8c71e912cc30b995b0b367ff2669ad Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 19:57:27 -0400 Subject: [PATCH 044/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 33 +++++++------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 9ad464bd..226db2b7 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,10 +1,9 @@ import UIKit import ZIPFoundation -class HomeViewController: UIViewController { - +class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerViewControllerDelegate { + // MARK: - Properties - private var ipaPath: String = "" private var fileList: [String] = [] private let fileManager = FileManager.default @@ -33,7 +32,6 @@ class HomeViewController: UIViewController { }() // MARK: - Lifecycle - override func viewDidLoad() { super.viewDidLoad() setupUI() @@ -42,7 +40,6 @@ class HomeViewController: UIViewController { } // MARK: - UI Setup - private func setupUI() { view.backgroundColor = .white @@ -70,9 +67,8 @@ class HomeViewController: UIViewController { } // MARK: - Actions - @objc private func selectIPAButtonTapped() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.ipa], asCopy: true) + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = self documentPicker.modalPresentationStyle = .formSheet present(documentPicker, animated: true, completion: nil) @@ -83,7 +79,6 @@ class HomeViewController: UIViewController { } // MARK: - ZIP Handling - private func listFiles() { guard !ipaPath.isEmpty else { print("Please select an IPA file first.") @@ -92,10 +87,8 @@ class HomeViewController: UIViewController { do { let zipFilePath = URL(fileURLWithPath: ipaPath) - let destinationURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("extracted") - // Check if destination directory exists, create if necessary if !fileManager.fileExists(atPath: destinationURL.path) { try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) @@ -105,7 +98,7 @@ class HomeViewController: UIViewController { for entry in archive { var destination = destinationURL.appendingPathComponent(entry.path) - if entry.isDirectory { + if entry.type == .directory { try fileManager.createDirectory(at: destination, withIntermediateDirectories: true, attributes: nil) } else { try archive.extract(entry, to: destination) @@ -113,22 +106,17 @@ class HomeViewController: UIViewController { print("Extracted \(entry.path)") } - - // List files after extraction - let contents = try fileManager.contentsOfDirectory(atPath: destinationURL.path) - fileList = contents - fileListTableView.reloadData() // Reload the table view to display the files + let contents = try fileManager.contentsOfDirectory(atPath: destinationURL.path) + fileList = contents + fileListTableView.reloadData() // Reload the table view to display the files } catch { print("Extraction failed with error: \(error)") } } -} - -// MARK: - UIDocumentPickerViewControllerDelegate -extension HomeViewController: UIDocumentPickerViewControllerDelegate { + // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let selectedFileURL = urls.first else { return @@ -136,11 +124,8 @@ extension HomeViewController: UIDocumentPickerViewControllerDelegate { ipaPath = selectedFileURL.path print("Selected IPA: \(ipaPath)") } -} - -// MARK: - UITableViewDelegate, UITableViewDataSource -extension HomeViewController: UITableViewDelegate, UITableViewDataSource { + // MARK: - UITableViewDelegate, UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return fileList.count } From 36ea878b3b5b9daf6819600883b5413e28299ab0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 19:58:36 -0400 Subject: [PATCH 045/391] Update TabbarView.swift --- iOS/Views/TabbarView.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/iOS/Views/TabbarView.swift b/iOS/Views/TabbarView.swift index c0ddcb72..d15b60f4 100644 --- a/iOS/Views/TabbarView.swift +++ b/iOS/Views/TabbarView.swift @@ -1,11 +1,3 @@ -// -// TabbarController.swift -// feather -// -// Created by samara on 5/17/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import SwiftUI struct TabbarView: View { @@ -93,7 +85,7 @@ struct NavigationViewController: UIViewControllerRepr func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {} } -// Assuming HomeViewController exists or will be created +// Assuming HomeViewController exists and has the necessary implementation class HomeViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() From dbd4ebf600d467b6a2f7ffee5816820c996ff9bf Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 20:13:13 -0400 Subject: [PATCH 046/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 226db2b7..54634185 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,7 +1,7 @@ import UIKit import ZIPFoundation -class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerViewControllerDelegate { +class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate { // MARK: - Properties private var ipaPath: String = "" From b6b3d2ad669908c23d4cc6fb461ae4aec478c15e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 20:13:41 -0400 Subject: [PATCH 047/391] Update TabbarView.swift --- iOS/Views/TabbarView.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/iOS/Views/TabbarView.swift b/iOS/Views/TabbarView.swift index d15b60f4..8e8678b1 100644 --- a/iOS/Views/TabbarView.swift +++ b/iOS/Views/TabbarView.swift @@ -83,13 +83,4 @@ struct NavigationViewController: UIViewControllerRepr } func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {} -} - -// Assuming HomeViewController exists and has the necessary implementation -class HomeViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = .systemBackground - // Additional setup for your home tab's view controller - } } \ No newline at end of file From c6d1848524b2768510d6f7923e3afec335c2098c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 21:04:06 -0400 Subject: [PATCH 048/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 204 +++++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 54634185..25a29064 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -31,6 +31,55 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData return tableView }() + // Additional buttons for file operations + private let shareButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Share File", for: .normal) + button.addTarget(self, action: #selector(shareFile), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let copyButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Copy File", for: .normal) + button.addTarget(self, action: #selector(copyFile), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let moveButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Move File", for: .normal) + button.addTarget(self, action: #selector(moveFile), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let compressButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Compress File", for: .normal) + button.addTarget(self, action: #selector(compressFile), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let renameButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Rename File", for: .normal) + button.addTarget(self, action: #selector(renameFile), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let deleteButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Delete File", for: .normal) + button.addTarget(self, action: #selector(deleteFile), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -47,6 +96,12 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData view.addSubview(selectIPAButton) view.addSubview(listFilesButton) view.addSubview(fileListTableView) + view.addSubview(shareButton) + view.addSubview(copyButton) + view.addSubview(moveButton) + view.addSubview(compressButton) + view.addSubview(renameButton) + view.addSubview(deleteButton) // Set up constraints NSLayoutConstraint.activate([ @@ -56,7 +111,25 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData listFilesButton.topAnchor.constraint(equalTo: selectIPAButton.bottomAnchor, constant: 20), listFilesButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - fileListTableView.topAnchor.constraint(equalTo: listFilesButton.bottomAnchor, constant: 20), + shareButton.topAnchor.constraint(equalTo: listFilesButton.bottomAnchor, constant: 20), + shareButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + copyButton.topAnchor.constraint(equalTo: shareButton.bottomAnchor, constant: 20), + copyButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + moveButton.topAnchor.constraint(equalTo: copyButton.bottomAnchor, constant: 20), + moveButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + compressButton.topAnchor.constraint(equalTo: moveButton.bottomAnchor, constant: 20), + compressButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + renameButton.topAnchor.constraint(equalTo: compressButton.bottomAnchor, constant: 20), + renameButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + deleteButton.topAnchor.constraint(equalTo: renameButton.bottomAnchor, constant: 20), + deleteButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), + + fileListTableView.topAnchor.constraint(equalTo: deleteButton.bottomAnchor, constant: 20), fileListTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), fileListTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20), fileListTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) @@ -78,6 +151,101 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData listFiles() } + @objc private func shareFile() { + guard !ipaPath.isEmpty else { + print("Please select a file first.") + return + } + let fileURL = URL(fileURLWithPath: ipaPath) + let activityVC = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityVC, animated: true, completion: nil) + } + + @objc private func copyFile() { + guard !ipaPath.isEmpty else { + print("Please select a file first.") + return + } + let fileURL = URL(fileURLWithPath: ipaPath) + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") + do { + try fileManager.copyItem(at: fileURL, to: destinationURL) + print("File copied to \(destinationURL.path)") + } catch { + print("Copy failed with error: \(error)") + } + } + + @objc private func moveFile() { + guard !ipaPath.isEmpty else { + print("Please select a file first.") + return + } + let fileURL = URL(fileURLWithPath: ipaPath) + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Moved_\(fileURL.lastPathComponent)") + do { + try fileManager.moveItem(at: fileURL, to: destinationURL) + print("File moved to \(destinationURL.path)") + } catch { + print("Move failed with error: \(error)") + } + } + + @objc private func compressFile() { + guard !ipaPath.isEmpty else { + print("Please select a file first.") + return + } + let fileURL = URL(fileURLWithPath: ipaPath) + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") + do { + let archive = Archive(url: destinationURL, accessMode: .create) + try archive?.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent()) + print("File compressed to \(destinationURL.path)") + } catch { + print("Compression failed with error: \(error)") + } + } + + @objc private func renameFile() { + guard !ipaPath.isEmpty else { + print("Please select a file first.") + return + } + let fileURL = URL(fileURLWithPath: ipaPath) + let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = fileURL.lastPathComponent + } + let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in + guard let newName = alertController.textFields?.first?.text else { return } + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + print("File renamed to \(destinationURL.path)") + } catch { + print("Rename failed with error: \(error)") + } + } + alertController.addAction(renameAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + @objc private func deleteFile() { + guard !ipaPath.isEmpty else { + print("Please select a file first.") + return + } + let fileURL = URL(fileURLWithPath: ipaPath) + do { + try fileManager.removeItem(at: fileURL) + print("File deleted") + } catch { + print("Delete failed with error: \(error)") + } + } + // MARK: - ZIP Handling private func listFiles() { guard !ipaPath.isEmpty else { @@ -135,4 +303,38 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData cell.textLabel?.text = fileList[indexPath.row] return cell } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = fileList[indexPath.row] + let fileURL = URL(fileURLWithPath: ipaPath).appendingPathComponent(fileName) + openFile(fileURL) + } + + private func openFile(_ fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + + switch fileExtension { + case "txt": + openTextEditor(fileURL) + case "plist": + openPlistEditor(fileURL) + default: + openHexEditor(fileURL) + } + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } } \ No newline at end of file From 49dae54f8a1d02b310834e406a1a39969359af61 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 21:10:59 -0400 Subject: [PATCH 049/391] Add files via upload --- iOS/Views/Home/HexEditorViewController.swift | 49 +++++++++++++++++++ .../Home/PlistEditorViewController.swift | 46 +++++++++++++++++ iOS/Views/Home/TextEditorViewController.swift | 40 +++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 iOS/Views/Home/HexEditorViewController.swift create mode 100644 iOS/Views/Home/PlistEditorViewController.swift create mode 100644 iOS/Views/Home/TextEditorViewController.swift diff --git a/iOS/Views/Home/HexEditorViewController.swift b/iOS/Views/Home/HexEditorViewController.swift new file mode 100644 index 00000000..f41ea2dc --- /dev/null +++ b/iOS/Views/Home/HexEditorViewController.swift @@ -0,0 +1,49 @@ +import UIKit + +class HexEditorViewController: UIViewController { + private var fileURL: URL + private var textView: UITextView! + + init(fileURL: URL) { + self.fileURL = fileURL + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + loadFileContent() + } + + private func setupUI() { + textView = UITextView(frame: view.bounds) + view.addSubview(textView) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveChanges)) + } + + private func loadFileContent() { + if let data = try? Data(contentsOf: fileURL) { + textView.text = data.map { String(format: "%02hhx", $0) }.joined(separator: " ") + } + } + + @objc private func saveChanges() { + let hexString = textView.text.replacingOccurrences(of: " ", with: "") + var data = Data() + var byteString = "" + for (index, char) in hexString.enumerated() { + byteString.append(char) + if index % 2 != 0 { + let num = UInt8(byteString, radix: 16)! + data.append(num) + byteString = "" + } + } + try? data.write(to: fileURL) + navigationController?.popViewController(animated: true) + } +} \ No newline at end of file diff --git a/iOS/Views/Home/PlistEditorViewController.swift b/iOS/Views/Home/PlistEditorViewController.swift new file mode 100644 index 00000000..b5b39b2a --- /dev/null +++ b/iOS/Views/Home/PlistEditorViewController.swift @@ -0,0 +1,46 @@ +import UIKit + +class PlistEditorViewController: UIViewController { + private var fileURL: URL + private var textView: UITextView! + + init(fileURL: URL) { + self.fileURL = fileURL + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + loadFileContent() + } + + private func setupUI() { + textView = UITextView(frame: view.bounds) + view.addSubview(textView) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveChanges)) + } + + private func loadFileContent() { + if let data = try? Data(contentsOf: fileURL), + let plist = try? PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil), + let plistData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0), + let plistString = String(data: plistData, encoding: .utf8) { + textView.text = plistString + } + } + + @objc private func saveChanges() { + if let newText = textView.text, + let data = newText.data(using: .utf8), + let plist = try? PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil), + let plistData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0) { + try? plistData.write(to: fileURL) + } + navigationController?.popViewController(animated: true) + } +} \ No newline at end of file diff --git a/iOS/Views/Home/TextEditorViewController.swift b/iOS/Views/Home/TextEditorViewController.swift new file mode 100644 index 00000000..e4736f97 --- /dev/null +++ b/iOS/Views/Home/TextEditorViewController.swift @@ -0,0 +1,40 @@ +import UIKit + +class TextEditorViewController: UIViewController { + private var fileURL: URL + private var textView: UITextView! + + init(fileURL: URL) { + self.fileURL = fileURL + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + loadFileContent() + } + + private func setupUI() { + textView = UITextView(frame: view.bounds) + view.addSubview(textView) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveChanges)) + } + + private func loadFileContent() { + if let fileContent = try? String(contentsOf: fileURL) { + textView.text = fileContent + } + } + + @objc private func saveChanges() { + if let newText = textView.text { + try? newText.write(to: fileURL, atomically: true, encoding: .utf8) + } + navigationController?.popViewController(animated: true) + } +} \ No newline at end of file From edb81f33989955e6a267679e2ca89a1ee64bd7ae Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 21:24:53 -0400 Subject: [PATCH 050/391] Update App-repo.json --- App-repo.json | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/App-repo.json b/App-repo.json index 08b9811c..800752be 100644 --- a/App-repo.json +++ b/App-repo.json @@ -1 +1,24 @@ -m \ No newline at end of file +{ + "name": "Backdoor Repository", + "identifier": "com.bdg.backdoor-repo", + "iconURL": "https://raw.githubusercontent.com/814bdg/App/refs/heads/main/Wing3x.png?raw=true", + "apps": [ + { + "name": "Backdoor", + "bundleIdentifier": "com.bdg.backdoor", + "developerName": "BDG", + "iconURL": "https://raw.githubusercontent.com/814bdg/App/refs/heads/main/Wing3x.png?raw=true", + "localizedDescription": "Backdoor is a free on-device iOS application manager/installer.", + "subtitle": "On-device signing application", + "tintColor": "848ef9", + "versions": [ + { + "version": "1.4.0", + "date": "2025-03-08T18:35:10Z", + "size": 12375230, + "downloadURL": "https://github.com/BDGHubNoKey/Backdoor/releases/download/v0.0.8/feather_v0.0.8.ipa" + } + ] + } + ] +} \ No newline at end of file From b9791076eb3f352327988d1e588fa515f44d8d1e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 21:27:14 -0400 Subject: [PATCH 051/391] Update AppDelegate.swift --- iOS/Delegates/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Delegates/AppDelegate.swift b/iOS/Delegates/AppDelegate.swift index ca7d76e9..45ded7fc 100644 --- a/iOS/Delegates/AppDelegate.swift +++ b/iOS/Delegates/AppDelegate.swift @@ -271,7 +271,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle name: "Backdoor Repository", id: "com.bdg.backdoor-repo", iconURL: URL(string: "https://raw.githubusercontent.com/814bdg/App/refs/heads/main/Wing3x.png?raw=true"), - url: "https://raw.githubusercontent.com/814bdg/App/c56e7beebe634db3065b8cf763c6e4a049ca73c1/App-repo.json" + url: "https://raw.githubusercontent.com/BDGHubNoKey/Backdoor/refs/heads/main/App-repo.json" ) { _ in Debug.shared.log(message: "Added default repos!") Preferences.defaultRepos = false From a7290402c43208a9a4b39e4b449601b70fecaf39 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 21:30:00 -0400 Subject: [PATCH 052/391] Update App-repo.json --- App-repo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/App-repo.json b/App-repo.json index 800752be..1a511904 100644 --- a/App-repo.json +++ b/App-repo.json @@ -13,7 +13,7 @@ "tintColor": "848ef9", "versions": [ { - "version": "1.4.0", + "version": "0.0.8", "date": "2025-03-08T18:35:10Z", "size": 12375230, "downloadURL": "https://github.com/BDGHubNoKey/Backdoor/releases/download/v0.0.8/feather_v0.0.8.ipa" From cf50a0c4066a61ca376e5ebec267553e87a3d8ce Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 21:39:07 -0400 Subject: [PATCH 053/391] Update main.yml --- .github/workflows/main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c01ad98..9f43c029 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,7 +1,6 @@ name: Create New Release on: - workflow_dispatch: jobs: @@ -20,7 +19,7 @@ jobs: - name: Compile f run: | mkdir upload - make package SCHEME="'feather (Release)'" + make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone mv packages/* upload/ - name: Get Version Number From c643e0fcfe3d13bf8eff2f0cfacbe2b2372001cf Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 21:42:51 -0400 Subject: [PATCH 054/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 25a29064..ef07e5c0 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -199,8 +199,8 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let fileURL = URL(fileURLWithPath: ipaPath) let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") do { - let archive = Archive(url: destinationURL, accessMode: .create) - try archive?.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent()) + let archive = try Archive(url: destinationURL, accessMode: .create) + try archive.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent()) print("File compressed to \(destinationURL.path)") } catch { print("Compression failed with error: \(error)") From c25b016515eed67681f83907804e0402f886167a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:28:33 -0400 Subject: [PATCH 055/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 309 +++++++++--------------- 1 file changed, 110 insertions(+), 199 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index ef07e5c0..cfdd4bd2 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -4,82 +4,22 @@ import ZIPFoundation class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate { // MARK: - Properties - private var ipaPath: String = "" private var fileList: [String] = [] private let fileManager = FileManager.default // MARK: - UI Elements - private let selectIPAButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Select IPA", for: .normal) - button.addTarget(self, action: #selector(selectIPAButtonTapped), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button + private let navigationBar: UINavigationBar = { + let navBar = UINavigationBar() + navBar.translatesAutoresizingMaskIntoConstraints = false + return navBar }() - - private let listFilesButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("List Files", for: .normal) - button.addTarget(self, action: #selector(listFilesButtonTapped), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - + private let fileListTableView: UITableView = { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false return tableView }() - - // Additional buttons for file operations - private let shareButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Share File", for: .normal) - button.addTarget(self, action: #selector(shareFile), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let copyButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Copy File", for: .normal) - button.addTarget(self, action: #selector(copyFile), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let moveButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Move File", for: .normal) - button.addTarget(self, action: #selector(moveFile), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let compressButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Compress File", for: .normal) - button.addTarget(self, action: #selector(compressFile), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let renameButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Rename File", for: .normal) - button.addTarget(self, action: #selector(renameFile), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let deleteButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Delete File", for: .normal) - button.addTarget(self, action: #selector(deleteFile), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -91,138 +31,137 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: - UI Setup private func setupUI() { view.backgroundColor = .white - + + // Setup Navigation Bar + let navItem = UINavigationItem(title: "Files") + let menuButton = UIBarButtonItem(title: "⋮", style: .plain, target: self, action: #selector(showMenu)) + navItem.rightBarButtonItem = menuButton + navigationBar.setItems([navItem], animated: false) + // Add UI elements to the view - view.addSubview(selectIPAButton) - view.addSubview(listFilesButton) + view.addSubview(navigationBar) view.addSubview(fileListTableView) - view.addSubview(shareButton) - view.addSubview(copyButton) - view.addSubview(moveButton) - view.addSubview(compressButton) - view.addSubview(renameButton) - view.addSubview(deleteButton) - + // Set up constraints NSLayoutConstraint.activate([ - selectIPAButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), - selectIPAButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - listFilesButton.topAnchor.constraint(equalTo: selectIPAButton.bottomAnchor, constant: 20), - listFilesButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - shareButton.topAnchor.constraint(equalTo: listFilesButton.bottomAnchor, constant: 20), - shareButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - copyButton.topAnchor.constraint(equalTo: shareButton.bottomAnchor, constant: 20), - copyButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - moveButton.topAnchor.constraint(equalTo: copyButton.bottomAnchor, constant: 20), - moveButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - compressButton.topAnchor.constraint(equalTo: moveButton.bottomAnchor, constant: 20), - compressButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - renameButton.topAnchor.constraint(equalTo: compressButton.bottomAnchor, constant: 20), - renameButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - deleteButton.topAnchor.constraint(equalTo: renameButton.bottomAnchor, constant: 20), - deleteButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - - fileListTableView.topAnchor.constraint(equalTo: deleteButton.bottomAnchor, constant: 20), - fileListTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - fileListTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20), - fileListTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) + navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) - + // Register the table view cell fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") + + // Add long press gesture recognizer to table view + let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) + fileListTableView.addGestureRecognizer(longPressRecognizer) } // MARK: - Actions - @objc private func selectIPAButtonTapped() { + @objc private func showMenu() { + let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + menu.addAction(UIAlertAction(title: "Select", style: .default, handler: { _ in self.selectFiles() })) + menu.addAction(UIAlertAction(title: "Import", style: .default, handler: { _ in self.importFile() })) + menu.addAction(UIAlertAction(title: "New Folder", style: .default, handler: { _ in self.createNewFolder() })) + menu.addAction(UIAlertAction(title: "New File", style: .default, handler: { _ in self.createNewFile() })) + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { + if gesture.state == .began { + let point = gesture.location(in: fileListTableView) + if let indexPath = fileListTableView.indexPathForRow(at: point) { + let fileName = fileList[indexPath.row] + let fileURL = URL(fileURLWithPath: fileName) + showFileOptions(for: fileURL) + } + } + } + + private func showFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + if fileExtension == "ipa" { + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + } else { + menu.addAction(UIAlertAction(title: "Copy", style: .default, handler: { _ in self.copyFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Move", style: .default, handler: { _ in self.moveFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Compress", style: .default, handler: { _ in self.compressFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Delete", style: .default, handler: { _ in self.deleteFile(at: fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + private func selectFiles() { + // Implement select files functionality + } + + private func importFile() { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = self documentPicker.modalPresentationStyle = .formSheet present(documentPicker, animated: true, completion: nil) } - - @objc private func listFilesButtonTapped() { - listFiles() + + private func createNewFolder() { + // Implement create new folder functionality } - - @objc private func shareFile() { - guard !ipaPath.isEmpty else { - print("Please select a file first.") - return - } - let fileURL = URL(fileURLWithPath: ipaPath) - let activityVC = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - present(activityVC, animated: true, completion: nil) + + private func createNewFile() { + // Implement create new file functionality } - - @objc private func copyFile() { - guard !ipaPath.isEmpty else { - print("Please select a file first.") - return - } - let fileURL = URL(fileURLWithPath: ipaPath) + + private func copyFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") do { - try fileManager.copyItem(at: fileURL, to: destinationURL) + try FileOperations.copyFile(at: fileURL, to: destinationURL) print("File copied to \(destinationURL.path)") } catch { print("Copy failed with error: \(error)") } } - - @objc private func moveFile() { - guard !ipaPath.isEmpty else { - print("Please select a file first.") - return - } - let fileURL = URL(fileURLWithPath: ipaPath) + + private func moveFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Moved_\(fileURL.lastPathComponent)") do { - try fileManager.moveItem(at: fileURL, to: destinationURL) + try FileOperations.moveFile(at: fileURL, to: destinationURL) print("File moved to \(destinationURL.path)") } catch { print("Move failed with error: \(error)") } } - - @objc private func compressFile() { - guard !ipaPath.isEmpty else { - print("Please select a file first.") - return - } - let fileURL = URL(fileURLWithPath: ipaPath) + + private func compressFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") do { - let archive = try Archive(url: destinationURL, accessMode: .create) - try archive.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent()) + try FileOperations.compressFile(at: fileURL, to: destinationURL) print("File compressed to \(destinationURL.path)") } catch { print("Compression failed with error: \(error)") } } - - @objc private func renameFile() { - guard !ipaPath.isEmpty else { - print("Please select a file first.") - return - } - let fileURL = URL(fileURLWithPath: ipaPath) + + private func renameFile(at fileURL: URL) { let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) alertController.addTextField { textField in textField.text = fileURL.lastPathComponent } let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in guard let newName = alertController.textFields?.first?.text else { return } - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - print("File renamed to \(destinationURL.path)") + try FileOperations.renameFile(at: fileURL, to: newName) + print("File renamed to \(newName)") } catch { print("Rename failed with error: \(error)") } @@ -231,66 +170,38 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(alertController, animated: true, completion: nil) } - - @objc private func deleteFile() { - guard !ipaPath.isEmpty else { - print("Please select a file first.") - return - } - let fileURL = URL(fileURLWithPath: ipaPath) + + private func deleteFile(at fileURL: URL) { do { - try fileManager.removeItem(at: fileURL) + try FileOperations.deleteFile(at: fileURL) print("File deleted") } catch { print("Delete failed with error: \(error)") } } - - // MARK: - ZIP Handling - private func listFiles() { - guard !ipaPath.isEmpty else { - print("Please select an IPA file first.") - return - } - + + private func unzipFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") do { - let zipFilePath = URL(fileURLWithPath: ipaPath) - let destinationURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("extracted") - - // Check if destination directory exists, create if necessary - if !fileManager.fileExists(atPath: destinationURL.path) { - try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) - } - - let archive = try Archive(url: zipFilePath, accessMode: .read) - - for entry in archive { - var destination = destinationURL.appendingPathComponent(entry.path) - if entry.type == .directory { - try fileManager.createDirectory(at: destination, withIntermediateDirectories: true, attributes: nil) - } else { - try archive.extract(entry, to: destination) - } - print("Extracted \(entry.path)") - } - - // List files after extraction - let contents = try fileManager.contentsOfDirectory(atPath: destinationURL.path) - fileList = contents - fileListTableView.reloadData() // Reload the table view to display the files - + try FileOperations.unzipFile(at: fileURL, to: destinationURL) + print("File unzipped to \(destinationURL.path)") } catch { - print("Extraction failed with error: \(error)") + print("Unzip failed with error: \(error)") } } - + + private func hexEditFile(at fileURL: URL) { + FileOperations.hexEditFile(at: fileURL) + // Implement hex edit functionality + } + // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let selectedFileURL = urls.first else { return } - ipaPath = selectedFileURL.path - print("Selected IPA: \(ipaPath)") + // Handle file import + print("Selected file: \(selectedFileURL.path)") } // MARK: - UITableViewDelegate, UITableViewDataSource @@ -306,7 +217,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let fileName = fileList[indexPath.row] - let fileURL = URL(fileURLWithPath: ipaPath).appendingPathComponent(fileName) + let fileURL = URL(fileURLWithPath: fileName) openFile(fileURL) } From 26bef879b87349ab3b9b79c46a6ed352e47c3f56 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:31:35 -0400 Subject: [PATCH 056/391] Add files via upload --- iOS/Views/Home/FileOperations.swift | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 iOS/Views/Home/FileOperations.swift diff --git a/iOS/Views/Home/FileOperations.swift b/iOS/Views/Home/FileOperations.swift new file mode 100644 index 00000000..50ac2bd4 --- /dev/null +++ b/iOS/Views/Home/FileOperations.swift @@ -0,0 +1,45 @@ +import Foundation +import ZIPFoundation + +class FileOperations { + + static let fileManager = FileManager.default + + static func copyFile(at sourceURL: URL, to destinationURL: URL) throws { + try fileManager.copyItem(at: sourceURL, to: destinationURL) + } + + static func moveFile(at sourceURL: URL, to destinationURL: URL) throws { + try fileManager.moveItem(at: sourceURL, to: destinationURL) + } + + static func compressFile(at fileURL: URL, to destinationURL: URL) throws { + let archive = try Archive(url: destinationURL, accessMode: .create) + try archive.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent()) + } + + static func renameFile(at sourceURL: URL, to newName: String) throws { + let destinationURL = sourceURL.deletingLastPathComponent().appendingPathComponent(newName) + try fileManager.moveItem(at: sourceURL, to: destinationURL) + } + + static func deleteFile(at fileURL: URL) throws { + try fileManager.removeItem(at: fileURL) + } + + static func unzipFile(at fileURL: URL, to destinationURL: URL) throws { + let archive = try Archive(url: fileURL, accessMode: .read) + for entry in archive { + let destination = destinationURL.appendingPathComponent(entry.path) + if entry.type == .directory { + try fileManager.createDirectory(at: destination, withIntermediateDirectories: true, attributes: nil) + } else { + try archive.extract(entry, to: destination) + } + } + } + + static func hexEditFile(at fileURL: URL) { + // Implement hex edit functionality + } +} \ No newline at end of file From 6de2889153a85fd8866f37bedc8acb1e748459d8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:40:57 -0400 Subject: [PATCH 057/391] Update Localizable.strings --- Shared/Localizations/en.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index 8ce0b256..9dd267aa 100644 --- a/Shared/Localizations/en.lproj/Localizable.strings +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -297,9 +297,9 @@ "DONATION_CELL_2_DESCRIPTION" = "Show your support by donating! If you're unable to donate, spreading the word about Feather works too!"; // MARK: - SettingsViewController -> LogsViewController.swift -"LOGS_VIEW_SECTION_TITLE_ERROR" = "%@ Critical Errors."; +"LOGS_VIEW_SECTION_TITLE_ERROR" = "%@ Critical Errors"; "LOGS_VIEW_SECTION_TITLE_SHARE" = "Share Logs"; "LOGS_VIEW_SECTION_TITLE_COPY" = "Copy Logs"; "LOGS_VIEW_SUCCESS_DESCRIPTION" = "Log contents have been copied to clipboard."; "LOGS_VIEW_FAIL_DESCRIPTION" = "Failed to copy log contents."; -```​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ \ No newline at end of file +"LOGS_VIEW_TITLE" = "Backdoor Logs"; \ No newline at end of file From ae3e6ee597bd7c1e843f82f0e78553da6d706b17 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:49:23 -0400 Subject: [PATCH 058/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 257 +++--------------------- 1 file changed, 23 insertions(+), 234 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index cfdd4bd2..6942226b 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,251 +1,40 @@ import UIKit -import ZIPFoundation -class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate { - - // MARK: - Properties - private var fileList: [String] = [] - private let fileManager = FileManager.default +class HexEditorViewController: UIViewController { + + private let fileURL: URL + private var textView: UITextView! + + init(fileURL: URL) { + self.fileURL = fileURL + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - // MARK: - UI Elements - private let navigationBar: UINavigationBar = { - let navBar = UINavigationBar() - navBar.translatesAutoresizingMaskIntoConstraints = false - return navBar - }() - - private let fileListTableView: UITableView = { - let tableView = UITableView() - tableView.translatesAutoresizingMaskIntoConstraints = false - return tableView - }() - - // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() setupUI() - fileListTableView.delegate = self - fileListTableView.dataSource = self + loadFileContent() } - // MARK: - UI Setup private func setupUI() { view.backgroundColor = .white - - // Setup Navigation Bar - let navItem = UINavigationItem(title: "Files") - let menuButton = UIBarButtonItem(title: "⋮", style: .plain, target: self, action: #selector(showMenu)) - navItem.rightBarButtonItem = menuButton - navigationBar.setItems([navItem], animated: false) - - // Add UI elements to the view - view.addSubview(navigationBar) - view.addSubview(fileListTableView) - - // Set up constraints - NSLayoutConstraint.activate([ - navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), - navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - - fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), - fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - - // Register the table view cell - fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") - - // Add long press gesture recognizer to table view - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - fileListTableView.addGestureRecognizer(longPressRecognizer) - } - - // MARK: - Actions - @objc private func showMenu() { - let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - menu.addAction(UIAlertAction(title: "Select", style: .default, handler: { _ in self.selectFiles() })) - menu.addAction(UIAlertAction(title: "Import", style: .default, handler: { _ in self.importFile() })) - menu.addAction(UIAlertAction(title: "New Folder", style: .default, handler: { _ in self.createNewFolder() })) - menu.addAction(UIAlertAction(title: "New File", style: .default, handler: { _ in self.createNewFile() })) - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) + textView = UITextView(frame: view.bounds) + textView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + textView.font = UIFont(name: "Courier", size: 12) + view.addSubview(textView) } - @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { - if gesture.state == .began { - let point = gesture.location(in: fileListTableView) - if let indexPath = fileListTableView.indexPathForRow(at: point) { - let fileName = fileList[indexPath.row] - let fileURL = URL(fileURLWithPath: fileName) - showFileOptions(for: fileURL) - } - } - } - - private func showFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - - if fileExtension == "ipa" { - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - } else { - menu.addAction(UIAlertAction(title: "Copy", style: .default, handler: { _ in self.copyFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Move", style: .default, handler: { _ in self.moveFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Compress", style: .default, handler: { _ in self.compressFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Delete", style: .default, handler: { _ in self.deleteFile(at: fileURL) })) - } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - - private func selectFiles() { - // Implement select files functionality - } - - private func importFile() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = self - documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) - } - - private func createNewFolder() { - // Implement create new folder functionality - } - - private func createNewFile() { - // Implement create new file functionality - } - - private func copyFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") - do { - try FileOperations.copyFile(at: fileURL, to: destinationURL) - print("File copied to \(destinationURL.path)") - } catch { - print("Copy failed with error: \(error)") - } - } - - private func moveFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Moved_\(fileURL.lastPathComponent)") - do { - try FileOperations.moveFile(at: fileURL, to: destinationURL) - print("File moved to \(destinationURL.path)") - } catch { - print("Move failed with error: \(error)") - } - } - - private func compressFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") - do { - try FileOperations.compressFile(at: fileURL, to: destinationURL) - print("File compressed to \(destinationURL.path)") - } catch { - print("Compression failed with error: \(error)") - } - } - - private func renameFile(at fileURL: URL) { - let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = fileURL.lastPathComponent - } - let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in - guard let newName = alertController.textFields?.first?.text else { return } - do { - try FileOperations.renameFile(at: fileURL, to: newName) - print("File renamed to \(newName)") - } catch { - print("Rename failed with error: \(error)") - } - } - alertController.addAction(renameAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func deleteFile(at fileURL: URL) { - do { - try FileOperations.deleteFile(at: fileURL) - print("File deleted") - } catch { - print("Delete failed with error: \(error)") - } - } - - private func unzipFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + private func loadFileContent() { do { - try FileOperations.unzipFile(at: fileURL, to: destinationURL) - print("File unzipped to \(destinationURL.path)") + let data = try Data(contentsOf: fileURL) + let hexString = data.map { String(format: "%02x", $0) }.joined(separator: " ") + textView.text = hexString } catch { - print("Unzip failed with error: \(error)") - } - } - - private func hexEditFile(at fileURL: URL) { - FileOperations.hexEditFile(at: fileURL) - // Implement hex edit functionality - } - - // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return + print("Failed to load file content: \(error)") } - // Handle file import - print("Selected file: \(selectedFileURL.path)") - } - - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) - cell.textLabel?.text = fileList[indexPath.row] - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = fileList[indexPath.row] - let fileURL = URL(fileURLWithPath: fileName) - openFile(fileURL) - } - - private func openFile(_ fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - - switch fileExtension { - case "txt": - openTextEditor(fileURL) - case "plist": - openPlistEditor(fileURL) - default: - openHexEditor(fileURL) - } - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) } } \ No newline at end of file From 6ea39e120836bbba9c7ade77b9d14b121556fd3c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:49:41 -0400 Subject: [PATCH 059/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 6942226b..d588e741 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -21,7 +21,7 @@ class HexEditorViewController: UIViewController { } private func setupUI() { - view.backgroundColor = .white + view.backgroundColor = .black textView = UITextView(frame: view.bounds) textView.autoresizingMask = [.flexibleWidth, .flexibleHeight] textView.font = UIFont(name: "Courier", size: 12) From 0b4e5b271e76a6ff4c8acc2137b121ea13bd2c1d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:52:07 -0400 Subject: [PATCH 060/391] Update HexEditorViewController.swift --- iOS/Views/Home/HexEditorViewController.swift | 103 ++++++++++++++++--- 1 file changed, 87 insertions(+), 16 deletions(-) diff --git a/iOS/Views/Home/HexEditorViewController.swift b/iOS/Views/Home/HexEditorViewController.swift index f41ea2dc..c3402601 100644 --- a/iOS/Views/Home/HexEditorViewController.swift +++ b/iOS/Views/Home/HexEditorViewController.swift @@ -1,8 +1,10 @@ import UIKit class HexEditorViewController: UIViewController { - private var fileURL: URL + + private let fileURL: URL private var textView: UITextView! + private var toolbar: UIToolbar! init(fileURL: URL) { self.fileURL = fileURL @@ -20,30 +22,99 @@ class HexEditorViewController: UIViewController { } private func setupUI() { - textView = UITextView(frame: view.bounds) + view.backgroundColor = .systemBackground + + // Setup text view + textView = UITextView() + textView.translatesAutoresizingMaskIntoConstraints = false + textView.font = UIFont(name: "Courier", size: 12) view.addSubview(textView) - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveChanges)) + + // Setup toolbar + toolbar = UIToolbar() + toolbar.translatesAutoresizingMaskIntoConstraints = false + let saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveHexContent)) + let copyButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(copyHexContent)) + let searchButton = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(promptSearch)) + toolbar.items = [saveButton, copyButton, UIBarButtonItem.flexibleSpace(), searchButton] + view.addSubview(toolbar) + + // Setup constraints + NSLayoutConstraint.activate([ + textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + textView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + textView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + textView.bottomAnchor.constraint(equalTo: toolbar.topAnchor), + + toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + toolbar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) } private func loadFileContent() { - if let data = try? Data(contentsOf: fileURL) { - textView.text = data.map { String(format: "%02hhx", $0) }.joined(separator: " ") + do { + let data = try Data(contentsOf: fileURL) + let hexString = data.map { String(format: "%02x", $0) }.joined(separator: " ") + textView.text = hexString + } catch { + presentAlert(title: "Error", message: "Failed to load file content: \(error.localizedDescription)") } } - @objc private func saveChanges() { - let hexString = textView.text.replacingOccurrences(of: " ", with: "") + @objc private func saveHexContent() { + guard let hexString = textView.text else { return } + let hexValues = hexString.split(separator: " ").map(String.init) var data = Data() - var byteString = "" - for (index, char) in hexString.enumerated() { - byteString.append(char) - if index % 2 != 0 { - let num = UInt8(byteString, radix: 16)! - data.append(num) - byteString = "" + for hex in hexValues { + if let byte = UInt8(hex, radix: 16) { + data.append(byte) + } else { + presentAlert(title: "Error", message: "Invalid hex value: \(hex)") + return } } - try? data.write(to: fileURL) - navigationController?.popViewController(animated: true) + do { + try data.write(to: fileURL) + presentAlert(title: "Success", message: "File saved successfully.") + } catch { + presentAlert(title: "Error", message: "Failed to save file: \(error.localizedDescription)") + } + } + + @objc private func copyHexContent() { + UIPasteboard.general.string = textView.text + presentAlert(title: "Copied", message: "Hex content copied to clipboard.") + } + + @objc private func promptSearch() { + let alert = UIAlertController(title: "Search Hex", message: "Enter hex value to search:", preferredStyle: .alert) + alert.addTextField { textField in + textField.placeholder = "e.g., 4a 6f 68 6e" + } + alert.addAction(UIAlertAction(title: "Search", style: .default, handler: { [weak self] _ in + if let hexValue = alert.textFields?.first?.text { + self?.searchHexValue(hexValue) + } + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func searchHexValue(_ hexValue: String) { + guard let hexContent = textView.text else { return } + if let range = hexContent.range(of: hexValue, options: .caseInsensitive) { + textView.scrollRangeToVisible(NSRange(range, in: hexContent)) + textView.becomeFirstResponder() + textView.selectedRange = NSRange(range, in: hexContent) + } else { + presentAlert(title: "Not Found", message: "Hex value not found in the file.") + } + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) } } \ No newline at end of file From 0dfb503ec23a5e24af1253852ebe6edf4eea0978 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:53:54 -0400 Subject: [PATCH 061/391] Update PlistEditorViewController.swift --- .../Home/PlistEditorViewController.swift | 88 +++++++++++++++---- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/iOS/Views/Home/PlistEditorViewController.swift b/iOS/Views/Home/PlistEditorViewController.swift index b5b39b2a..40a14aeb 100644 --- a/iOS/Views/Home/PlistEditorViewController.swift +++ b/iOS/Views/Home/PlistEditorViewController.swift @@ -1,8 +1,10 @@ import UIKit class PlistEditorViewController: UIViewController { - private var fileURL: URL + + private let fileURL: URL private var textView: UITextView! + private var toolbar: UIToolbar! init(fileURL: URL) { self.fileURL = fileURL @@ -20,27 +22,83 @@ class PlistEditorViewController: UIViewController { } private func setupUI() { - textView = UITextView(frame: view.bounds) + view.backgroundColor = .systemBackground + + // Setup text view + textView = UITextView() + textView.translatesAutoresizingMaskIntoConstraints = false + textView.font = UIFont(name: "Courier", size: 14) view.addSubview(textView) - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveChanges)) + + // Setup toolbar + toolbar = UIToolbar() + toolbar.translatesAutoresizingMaskIntoConstraints = false + let saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(savePlistContent)) + let copyButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(copyPlistContent)) + toolbar.items = [saveButton, copyButton, UIBarButtonItem.flexibleSpace()] + view.addSubview(toolbar) + + // Setup constraints + NSLayoutConstraint.activate([ + textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + textView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + textView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + textView.bottomAnchor.constraint(equalTo: toolbar.topAnchor), + + toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + toolbar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) } private func loadFileContent() { - if let data = try? Data(contentsOf: fileURL), - let plist = try? PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil), - let plistData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0), - let plistString = String(data: plistData, encoding: .utf8) { - textView.text = plistString + do { + let data = try Data(contentsOf: fileURL) + if let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] { + textView.text = convertPlistToString(plist: plist) + } + } catch { + presentAlert(title: "Error", message: "Failed to load plist content: \(error.localizedDescription)") + } + } + + @objc private func savePlistContent() { + guard let content = textView.text else { return } + do { + let plist = try convertStringToPlist(content: content) + let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) + try data.write(to: fileURL) + presentAlert(title: "Success", message: "File saved successfully.") + } catch { + presentAlert(title: "Error", message: "Failed to save plist content: \(error.localizedDescription)") } } - @objc private func saveChanges() { - if let newText = textView.text, - let data = newText.data(using: .utf8), - let plist = try? PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil), - let plistData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0) { - try? plistData.write(to: fileURL) + @objc private func copyPlistContent() { + UIPasteboard.general.string = textView.text + presentAlert(title: "Copied", message: "Plist content copied to clipboard.") + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func convertPlistToString(plist: [String: Any]) -> String { + return plist.map { "\($0.key): \($0.value)" }.joined(separator: "\n") + } + + private func convertStringToPlist(content: String) throws -> [String: Any] { + let lines = content.split(separator: "\n") + var plist = [String: Any]() + for line in lines { + let components = line.split(separator: ":") + guard components.count == 2 else { throw NSError(domain: "InvalidFormat", code: 1, userInfo: nil) } + let key = String(components[0]).trimmingCharacters(in: .whitespaces) + let value = String(components[1]).trimmingCharacters(in: .whitespaces) + plist[key] = value } - navigationController?.popViewController(animated: true) + return plist } } \ No newline at end of file From 6599bd02f7f136cbc95428cf545106c3659e114a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:55:00 -0400 Subject: [PATCH 062/391] Update TextEditorViewController.swift --- iOS/Views/Home/TextEditorViewController.swift | 86 +++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/iOS/Views/Home/TextEditorViewController.swift b/iOS/Views/Home/TextEditorViewController.swift index e4736f97..49dd43c6 100644 --- a/iOS/Views/Home/TextEditorViewController.swift +++ b/iOS/Views/Home/TextEditorViewController.swift @@ -3,6 +3,8 @@ import UIKit class TextEditorViewController: UIViewController { private var fileURL: URL private var textView: UITextView! + private var toolbar: UIToolbar! + private var hasUnsavedChanges = false init(fileURL: URL) { self.fileURL = fileURL @@ -20,21 +22,95 @@ class TextEditorViewController: UIViewController { } private func setupUI() { - textView = UITextView(frame: view.bounds) + view.backgroundColor = .systemBackground + + // Setup text view + textView = UITextView() + textView.translatesAutoresizingMaskIntoConstraints = false + textView.font = UIFont.systemFont(ofSize: 14) + textView.delegate = self view.addSubview(textView) + + // Setup toolbar + toolbar = UIToolbar() + toolbar.translatesAutoresizingMaskIntoConstraints = false + let saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveChanges)) + let copyButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(copyContent)) + toolbar.items = [saveButton, copyButton, UIBarButtonItem.flexibleSpace()] + view.addSubview(toolbar) + + // Setup navigation bar navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveChanges)) + + // Setup constraints + NSLayoutConstraint.activate([ + textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + textView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + textView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + textView.bottomAnchor.constraint(equalTo: toolbar.topAnchor), + + toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + toolbar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) } private func loadFileContent() { - if let fileContent = try? String(contentsOf: fileURL) { + do { + let fileContent = try String(contentsOf: fileURL) textView.text = fileContent + } catch { + presentAlert(title: "Error", message: "Failed to load file content: \(error.localizedDescription)") } } @objc private func saveChanges() { - if let newText = textView.text { - try? newText.write(to: fileURL, atomically: true, encoding: .utf8) + guard let newText = textView.text else { return } + do { + try newText.write(to: fileURL, atomically: true, encoding: .utf8) + hasUnsavedChanges = false + presentAlert(title: "Success", message: "File saved successfully.") + } catch { + presentAlert(title: "Error", message: "Failed to save file: \(error.localizedDescription)") + } + } + + @objc private func copyContent() { + UIPasteboard.general.string = textView.text + presentAlert(title: "Copied", message: "Content copied to clipboard.") + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func promptSaveChanges() { + let alert = UIAlertController(title: "Unsaved Changes", message: "You have unsaved changes. Do you want to save them before leaving?", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak self] _ in + self?.saveChanges() + self?.navigationController?.popViewController(animated: true) + })) + alert.addAction(UIAlertAction(title: "Discard", style: .destructive, handler: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if hasUnsavedChanges { + promptSaveChanges() + } else { + navigationController?.popViewController(animated: true) } - navigationController?.popViewController(animated: true) + } +} + +extension TextEditorViewController: UITextViewDelegate { + func textViewDidChange(_ textView: UITextView) { + hasUnsavedChanges = true } } \ No newline at end of file From aa2682c69c0a4ea336bd3e82df5f7e3fa175c867 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:56:03 -0400 Subject: [PATCH 063/391] Update TextEditorViewController.swift --- iOS/Views/Home/TextEditorViewController.swift | 64 +++++++++++++++++-- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/iOS/Views/Home/TextEditorViewController.swift b/iOS/Views/Home/TextEditorViewController.swift index 49dd43c6..27043ebe 100644 --- a/iOS/Views/Home/TextEditorViewController.swift +++ b/iOS/Views/Home/TextEditorViewController.swift @@ -5,6 +5,7 @@ class TextEditorViewController: UIViewController { private var textView: UITextView! private var toolbar: UIToolbar! private var hasUnsavedChanges = false + private var autoSaveTimer: Timer? init(fileURL: URL) { self.fileURL = fileURL @@ -19,6 +20,17 @@ class TextEditorViewController: UIViewController { super.viewDidLoad() setupUI() loadFileContent() + startAutoSaveTimer() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if hasUnsavedChanges { + promptSaveChanges() + } else { + navigationController?.popViewController(animated: true) + } + stopAutoSaveTimer() } private func setupUI() { @@ -27,7 +39,7 @@ class TextEditorViewController: UIViewController { // Setup text view textView = UITextView() textView.translatesAutoresizingMaskIntoConstraints = false - textView.font = UIFont.systemFont(ofSize: 14) + textView.font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) textView.delegate = self view.addSubview(textView) @@ -36,7 +48,10 @@ class TextEditorViewController: UIViewController { toolbar.translatesAutoresizingMaskIntoConstraints = false let saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveChanges)) let copyButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(copyContent)) - toolbar.items = [saveButton, copyButton, UIBarButtonItem.flexibleSpace()] + let findReplaceButton = UIBarButtonItem(title: "Find/Replace", style: .plain, target: self, action: #selector(promptFindReplace)) + let undoButton = UIBarButtonItem(barButtonSystemItem: .undo, target: self, action: #selector(undoAction)) + let redoButton = UIBarButtonItem(barButtonSystemItem: .redo, target: self, action: #selector(redoAction)) + toolbar.items = [saveButton, copyButton, findReplaceButton, undoButton, redoButton, UIBarButtonItem.flexibleSpace()] view.addSubview(toolbar) // Setup navigation bar @@ -80,6 +95,35 @@ class TextEditorViewController: UIViewController { presentAlert(title: "Copied", message: "Content copied to clipboard.") } + @objc private func undoAction() { + textView.undoManager?.undo() + } + + @objc private func redoAction() { + textView.undoManager?.redo() + } + + @objc private func promptFindReplace() { + let alert = UIAlertController(title: "Find and Replace", message: "Enter text to find and replace:", preferredStyle: .alert) + alert.addTextField { textField in + textField.placeholder = "Find" + } + alert.addTextField { textField in + textField.placeholder = "Replace" + } + alert.addAction(UIAlertAction(title: "Replace", style: .default, handler: { [weak self] _ in + guard let findText = alert.textFields?[0].text, let replaceText = alert.textFields?[1].text else { return } + self?.findAndReplace(findText: findText, replaceText: replaceText) + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func findAndReplace(findText: String, replaceText: String) { + textView.text = textView.text.replacingOccurrences(of: findText, with: replaceText) + hasUnsavedChanges = true + } + private func presentAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) @@ -99,12 +143,18 @@ class TextEditorViewController: UIViewController { present(alert, animated: true, completion: nil) } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) + private func startAutoSaveTimer() { + autoSaveTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(autoSaveChanges), userInfo: nil, repeats: true) + } + + private func stopAutoSaveTimer() { + autoSaveTimer?.invalidate() + autoSaveTimer = nil + } + + @objc private func autoSaveChanges() { if hasUnsavedChanges { - promptSaveChanges() - } else { - navigationController?.popViewController(animated: true) + saveChanges() } } } From f527c6efb23769a916b0b9887b08341a08149122 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:58:27 -0400 Subject: [PATCH 064/391] Update TextEditorViewController.swift --- iOS/Views/Home/TextEditorViewController.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/iOS/Views/Home/TextEditorViewController.swift b/iOS/Views/Home/TextEditorViewController.swift index 27043ebe..f9329f61 100644 --- a/iOS/Views/Home/TextEditorViewController.swift +++ b/iOS/Views/Home/TextEditorViewController.swift @@ -54,9 +54,6 @@ class TextEditorViewController: UIViewController { toolbar.items = [saveButton, copyButton, findReplaceButton, undoButton, redoButton, UIBarButtonItem.flexibleSpace()] view.addSubview(toolbar) - // Setup navigation bar - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveChanges)) - // Setup constraints NSLayoutConstraint.activate([ textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), From 99d6ff59798e9b4e76c44ae228963a9783fb5388 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:59:01 -0400 Subject: [PATCH 065/391] Update HexEditorViewController.swift --- iOS/Views/Home/HexEditorViewController.swift | 104 ++++++++++++++----- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/iOS/Views/Home/HexEditorViewController.swift b/iOS/Views/Home/HexEditorViewController.swift index c3402601..fdd0da3a 100644 --- a/iOS/Views/Home/HexEditorViewController.swift +++ b/iOS/Views/Home/HexEditorViewController.swift @@ -1,10 +1,11 @@ import UIKit class HexEditorViewController: UIViewController { - private let fileURL: URL private var textView: UITextView! private var toolbar: UIToolbar! + private var hasUnsavedChanges = false + private var autoSaveTimer: Timer? init(fileURL: URL) { self.fileURL = fileURL @@ -19,6 +20,17 @@ class HexEditorViewController: UIViewController { super.viewDidLoad() setupUI() loadFileContent() + startAutoSaveTimer() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if hasUnsavedChanges { + promptSaveChanges() + } else { + navigationController?.popViewController(animated: true) + } + stopAutoSaveTimer() } private func setupUI() { @@ -27,16 +39,19 @@ class HexEditorViewController: UIViewController { // Setup text view textView = UITextView() textView.translatesAutoresizingMaskIntoConstraints = false - textView.font = UIFont(name: "Courier", size: 12) + textView.font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) + textView.delegate = self view.addSubview(textView) // Setup toolbar toolbar = UIToolbar() toolbar.translatesAutoresizingMaskIntoConstraints = false - let saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveHexContent)) - let copyButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(copyHexContent)) - let searchButton = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(promptSearch)) - toolbar.items = [saveButton, copyButton, UIBarButtonItem.flexibleSpace(), searchButton] + let saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveChanges)) + let copyButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(copyContent)) + let findReplaceButton = UIBarButtonItem(title: "Find/Replace", style: .plain, target: self, action: #selector(promptFindReplace)) + let undoButton = UIBarButtonItem(barButtonSystemItem: .undo, target: self, action: #selector(undoAction)) + let redoButton = UIBarButtonItem(barButtonSystemItem: .redo, target: self, action: #selector(redoAction)) + toolbar.items = [saveButton, copyButton, findReplaceButton, undoButton, redoButton, UIBarButtonItem.flexibleSpace()] view.addSubview(toolbar) // Setup constraints @@ -62,7 +77,7 @@ class HexEditorViewController: UIViewController { } } - @objc private func saveHexContent() { + @objc private func saveChanges() { guard let hexString = textView.text else { return } let hexValues = hexString.split(separator: " ").map(String.init) var data = Data() @@ -76,40 +91,45 @@ class HexEditorViewController: UIViewController { } do { try data.write(to: fileURL) + hasUnsavedChanges = false presentAlert(title: "Success", message: "File saved successfully.") } catch { presentAlert(title: "Error", message: "Failed to save file: \(error.localizedDescription)") } } - @objc private func copyHexContent() { + @objc private func copyContent() { UIPasteboard.general.string = textView.text - presentAlert(title: "Copied", message: "Hex content copied to clipboard.") + presentAlert(title: "Copied", message: "Content copied to clipboard.") + } + + @objc private func undoAction() { + textView.undoManager?.undo() } - @objc private func promptSearch() { - let alert = UIAlertController(title: "Search Hex", message: "Enter hex value to search:", preferredStyle: .alert) + @objc private func redoAction() { + textView.undoManager?.redo() + } + + @objc private func promptFindReplace() { + let alert = UIAlertController(title: "Find and Replace", message: "Enter hex value to find and replace:", preferredStyle: .alert) alert.addTextField { textField in - textField.placeholder = "e.g., 4a 6f 68 6e" + textField.placeholder = "Find (e.g., 4a 6f 68)" } - alert.addAction(UIAlertAction(title: "Search", style: .default, handler: { [weak self] _ in - if let hexValue = alert.textFields?.first?.text { - self?.searchHexValue(hexValue) - } + alert.addTextField { textField in + textField.placeholder = "Replace (e.g., 4b 6c 69)" + } + alert.addAction(UIAlertAction(title: "Replace", style: .default, handler: { [weak self] _ in + guard let findText = alert.textFields?[0].text, let replaceText = alert.textFields?[1].text else { return } + self?.findAndReplace(findText: findText, replaceText: replaceText) })) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(alert, animated: true, completion: nil) } - private func searchHexValue(_ hexValue: String) { - guard let hexContent = textView.text else { return } - if let range = hexContent.range(of: hexValue, options: .caseInsensitive) { - textView.scrollRangeToVisible(NSRange(range, in: hexContent)) - textView.becomeFirstResponder() - textView.selectedRange = NSRange(range, in: hexContent) - } else { - presentAlert(title: "Not Found", message: "Hex value not found in the file.") - } + private func findAndReplace(findText: String, replaceText: String) { + textView.text = textView.text.replacingOccurrences(of: findText, with: replaceText) + hasUnsavedChanges = true } private func presentAlert(title: String, message: String) { @@ -117,4 +137,38 @@ class HexEditorViewController: UIViewController { alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) present(alert, animated: true, completion: nil) } + + private void promptSaveChanges() { + let alert = UIAlertController(title: "Unsaved Changes", message: "You have unsaved changes. Do you want to save them before leaving?", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak self] _ in + self?.saveChanges() + self?.navigationController?.popViewController(animated: true) + })) + alert.addAction(UIAlertAction(title: "Discard", style: .destructive, handler: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func startAutoSaveTimer() { + autoSaveTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(autoSaveChanges), userInfo: nil, repeats: true) + } + + private func stopAutoSaveTimer() { + autoSaveTimer?.invalidate() + autoSaveTimer = nil + } + + @objc private func autoSaveChanges() { + if hasUnsavedChanges { + saveChanges() + } + } +} + +extension HexEditorViewController: UITextViewDelegate { + func textViewDidChange(_ textView: UITextView) { + hasUnsavedChanges = true + } } \ No newline at end of file From bc0df260c6d7868f2ef0d8cedb2199207e1c4f16 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 22:59:24 -0400 Subject: [PATCH 066/391] Update PlistEditorViewController.swift --- .../Home/PlistEditorViewController.swift | 73 ++++++------------- 1 file changed, 23 insertions(+), 50 deletions(-) diff --git a/iOS/Views/Home/PlistEditorViewController.swift b/iOS/Views/Home/PlistEditorViewController.swift index 40a14aeb..caf82d95 100644 --- a/iOS/Views/Home/PlistEditorViewController.swift +++ b/iOS/Views/Home/PlistEditorViewController.swift @@ -1,10 +1,11 @@ import UIKit class PlistEditorViewController: UIViewController { - private let fileURL: URL private var textView: UITextView! private var toolbar: UIToolbar! + private var hasUnsavedChanges = false + private var autoSaveTimer: Timer? init(fileURL: URL) { self.fileURL = fileURL @@ -19,23 +20,38 @@ class PlistEditorViewController: UIViewController { super.viewDidLoad() setupUI() loadFileContent() + startAutoSaveTimer() } - private func setupUI() { + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if hasUnsavedChanges { + promptSaveChanges() + } else { + navigationController?.popViewController(animated: true) + } + stopAutoSaveTimer() + } + + private void setupUI() { view.backgroundColor = .systemBackground // Setup text view textView = UITextView() textView.translatesAutoresizingMaskIntoConstraints = false - textView.font = UIFont(name: "Courier", size: 14) + textView.font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) + textView.delegate = self view.addSubview(textView) // Setup toolbar toolbar = UIToolbar() toolbar.translatesAutoresizingMaskIntoConstraints = false - let saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(savePlistContent)) - let copyButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(copyPlistContent)) - toolbar.items = [saveButton, copyButton, UIBarButtonItem.flexibleSpace()] + let saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveChanges)) + let copyButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(copyContent)) + let findReplaceButton = UIBarButtonItem(title: "Find/Replace", style: .plain, target: self, action: #selector(promptFindReplace)) + let undoButton = UIBarButtonItem(barButtonSystemItem: .undo, target: self, action: #selector(undoAction)) + let redoButton = UIBarButtonItem(barButtonSystemItem: .redo, target: self, action: #selector(redoAction)) + toolbar.items = [saveButton, copyButton, findReplaceButton, undoButton, redoButton, UIBarButtonItem.flexibleSpace()] view.addSubview(toolbar) // Setup constraints @@ -58,47 +74,4 @@ class PlistEditorViewController: UIViewController { textView.text = convertPlistToString(plist: plist) } } catch { - presentAlert(title: "Error", message: "Failed to load plist content: \(error.localizedDescription)") - } - } - - @objc private func savePlistContent() { - guard let content = textView.text else { return } - do { - let plist = try convertStringToPlist(content: content) - let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) - try data.write(to: fileURL) - presentAlert(title: "Success", message: "File saved successfully.") - } catch { - presentAlert(title: "Error", message: "Failed to save plist content: \(error.localizedDescription)") - } - } - - @objc private func copyPlistContent() { - UIPasteboard.general.string = textView.text - presentAlert(title: "Copied", message: "Plist content copied to clipboard.") - } - - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - - private func convertPlistToString(plist: [String: Any]) -> String { - return plist.map { "\($0.key): \($0.value)" }.joined(separator: "\n") - } - - private func convertStringToPlist(content: String) throws -> [String: Any] { - let lines = content.split(separator: "\n") - var plist = [String: Any]() - for line in lines { - let components = line.split(separator: ":") - guard components.count == 2 else { throw NSError(domain: "InvalidFormat", code: 1, userInfo: nil) } - let key = String(components[0]).trimmingCharacters(in: .whitespaces) - let value = String(components[1]).trimmingCharacters(in: .whitespaces) - plist[key] = value - } - return plist - } -} \ No newline at end of file + presentAlert(title: "Error", message: "Failed to load plist content: \(error.localizedDescription)") \ No newline at end of file From 9b27ed5042633cd9bc50d5d83d53006d22c7f878 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 23:04:22 -0400 Subject: [PATCH 067/391] Update FileOperations.swift --- iOS/Views/Home/FileOperations.swift | 91 ++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 15 deletions(-) diff --git a/iOS/Views/Home/FileOperations.swift b/iOS/Views/Home/FileOperations.swift index 50ac2bd4..b771fd4c 100644 --- a/iOS/Views/Home/FileOperations.swift +++ b/iOS/Views/Home/FileOperations.swift @@ -1,45 +1,106 @@ import Foundation import ZIPFoundation +import UIKit + +enum FileOperationError: Error { + case fileNotFound(String) + case invalidDestination(String) + case unknownError(String) +} class FileOperations { static let fileManager = FileManager.default static func copyFile(at sourceURL: URL, to destinationURL: URL) throws { - try fileManager.copyItem(at: sourceURL, to: destinationURL) + guard fileManager.fileExists(atPath: sourceURL.path) else { + throw FileOperationError.fileNotFound("Source file not found at \(sourceURL.path)") + } + do { + try fileManager.copyItem(at: sourceURL, to: destinationURL) + print("File copied from \(sourceURL.path) to \(destinationURL.path)") + } catch { + throw FileOperationError.unknownError("Failed to copy file: \(error.localizedDescription)") + } } static func moveFile(at sourceURL: URL, to destinationURL: URL) throws { - try fileManager.moveItem(at: sourceURL, to: destinationURL) + guard fileManager.fileExists(atPath: sourceURL.path) else { + throw FileOperationError.fileNotFound("Source file not found at \(sourceURL.path)") + } + do { + try fileManager.moveItem(at: sourceURL, to: destinationURL) + print("File moved from \(sourceURL.path) to \(destinationURL.path)") + } catch { + throw FileOperationError.unknownError("Failed to move file: \(error.localizedDescription)") + } } static func compressFile(at fileURL: URL, to destinationURL: URL) throws { - let archive = try Archive(url: destinationURL, accessMode: .create) - try archive.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent()) + guard fileManager.fileExists(atPath: fileURL.path) else { + throw FileOperationError.fileNotFound("File not found at \(fileURL.path)") + } + do { + let archive = try Archive(url: destinationURL, accessMode: .create) + try archive.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent()) + print("File compressed from \(fileURL.path) to \(destinationURL.path)") + } catch { + throw FileOperationError.unknownError("Failed to compress file: \(error.localizedDescription)") + } } static func renameFile(at sourceURL: URL, to newName: String) throws { + guard fileManager.fileExists(atPath: sourceURL.path) else { + throw FileOperationError.fileNotFound("Source file not found at \(sourceURL.path)") + } let destinationURL = sourceURL.deletingLastPathComponent().appendingPathComponent(newName) - try fileManager.moveItem(at: sourceURL, to: destinationURL) + do { + try fileManager.moveItem(at: sourceURL, to: destinationURL) + print("File renamed from \(sourceURL.path) to \(destinationURL.path)") + } catch { + throw FileOperationError.unknownError("Failed to rename file: \(error.localizedDescription)") + } } static func deleteFile(at fileURL: URL) throws { - try fileManager.removeItem(at: fileURL) + guard fileManager.fileExists(atPath: fileURL.path) else { + throw FileOperationError.fileNotFound("File not found at \(fileURL.path)") + } + do { + try fileManager.removeItem(at: fileURL) + print("File deleted at \(fileURL.path)") + } catch { + throw FileOperationError.unknownError("Failed to delete file: \(error.localizedDescription)") + } } static func unzipFile(at fileURL: URL, to destinationURL: URL) throws { - let archive = try Archive(url: fileURL, accessMode: .read) - for entry in archive { - let destination = destinationURL.appendingPathComponent(entry.path) - if entry.type == .directory { - try fileManager.createDirectory(at: destination, withIntermediateDirectories: true, attributes: nil) - } else { - try archive.extract(entry, to: destination) + guard fileManager.fileExists(atPath: fileURL.path) else { + throw FileOperationError.fileNotFound("File not found at \(fileURL.path)") + } + do { + let archive = try Archive(url: fileURL, accessMode: .read) + for entry in archive { + let destination = destinationURL.appendingPathComponent(entry.path) + if entry.type == .directory { + try fileManager.createDirectory(at: destination, withIntermediateDirectories: true, attributes: nil) + } else { + try archive.extract(entry, to: destination) + } } + print("File unzipped from \(fileURL.path) to \(destinationURL.path)") + } catch { + throw FileOperationError.unknownError("Failed to unzip file: \(error.localizedDescription)") } } - static func hexEditFile(at fileURL: URL) { - // Implement hex edit functionality + static func hexEditFile(at fileURL: URL, in viewController: UIViewController) { + guard fileManager.fileExists(atPath: fileURL.path) else { + print("File not found at \(fileURL.path)") + return + } + + let hexEditorViewController = HexEditorViewController(fileURL: fileURL) + viewController.present(hexEditorViewController, animated: true, completion: nil) } } \ No newline at end of file From 6afdd06099aa442a9e2537ea316df07c558d493b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 23:06:55 -0400 Subject: [PATCH 068/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 318 ++++++++++++++++++++++-- 1 file changed, 295 insertions(+), 23 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index d588e741..842f1c63 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,40 +1,312 @@ import UIKit +import ZIPFoundation -class HexEditorViewController: UIViewController { - - private let fileURL: URL - private var textView: UITextView! - - init(fileURL: URL) { - self.fileURL = fileURL - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") +class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate { + + // MARK: - Properties + private var fileList: [String] = [] + private let fileManager = FileManager.default + private var documentsDirectory: URL { + return fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! } + // MARK: - UI Elements + private let navigationBar: UINavigationBar = { + let navBar = UINavigationBar() + navBar.translatesAutoresizingMaskIntoConstraints = false + navBar.barTintColor = .systemBlue // Change this to match your app's theme + navBar.titleTextAttributes = [.foregroundColor: UIColor.white] + return navBar + }() + + private let fileListTableView: UITableView = { + let tableView = UITableView() + tableView.translatesAutoresizingMaskIntoConstraints = false + return tableView + }() + + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() setupUI() - loadFileContent() + loadFiles() + fileListTableView.delegate = self + fileListTableView.dataSource = self } + // MARK: - UI Setup private func setupUI() { - view.backgroundColor = .black - textView = UITextView(frame: view.bounds) - textView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - textView.font = UIFont(name: "Courier", size: 12) - view.addSubview(textView) + view.backgroundColor = .white + + // Setup Navigation Bar + let navItem = UINavigationItem(title: "Files") + let menuButton = UIBarButtonItem(title: "⋮", style: .plain, target: self, action: #selector(showMenu)) + navItem.rightBarButtonItem = menuButton + navigationBar.setItems([navItem], animated: false) + + // Add UI elements to the view + view.addSubview(navigationBar) + view.addSubview(fileListTableView) + + // Set up constraints + NSLayoutConstraint.activate([ + navigationBar.topAnchor.constraint(equalTo: view.topAnchor), // Changed to view.topAnchor + navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + + // Register the table view cell + fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") + + // Add long press gesture recognizer to table view + let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) + fileListTableView.addGestureRecognizer(longPressRecognizer) + } + + // MARK: - Load Files + private func loadFiles() { + do { + fileList = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) + fileListTableView.reloadData() + } catch { + print("Failed to load files: \(error)") + } + } + + // MARK: - Actions + @objc private func showMenu() { + let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + menu.addAction(UIAlertAction(title: "Select", style: .default, handler: { _ in self.selectFiles() })) + menu.addAction(UIAlertAction(title: "Import", style: .default, handler: { _ in self.importFile() })) + menu.addAction(UIAlertAction(title: "New Folder", style: .default, handler: { _ in self.createNewFolder() })) + menu.addAction(UIAlertAction(title: "New File", style: .default, handler: { _ in self.createNewFile() })) + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { + if gesture.state == .began { + let point = gesture.location(in: fileListTableView) + if let indexPath = fileListTableView.indexPathForRow(at: point) { + let fileName = fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + showFileOptions(for: fileURL) + } + } + } + + private func showFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + if fileExtension == "ipa" { + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + } else { + menu.addAction(UIAlertAction(title: "Copy", style: .default, handler: { _ in self.copyFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Move", style: .default, handler: { _ in self.moveFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Compress", style: .default, handler: { _ in self.compressFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Delete", style: .default, handler: { _ in self.deleteFile(at: fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) } - private func loadFileContent() { + private func selectFiles() { + // Implement select files functionality + } + + private func importFile() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + private func createNewFolder() { + let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "Folder name" + } + let createAction = UIAlertAction(title: "Create", style: .default) { _ in + guard let folderName = alertController.textFields?.first?.text else { return } + let folderURL = self.documentsDirectory.appendingPathComponent(folderName) + do { + try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) + self.loadFiles() + } catch { + print("Failed to create folder: \(error)") + } + } + alertController.addAction(createAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func createNewFile() { + let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "File name" + } + let createAction = UIAlertAction(title: "Create", style: .default) { _ in + guard let fileName = alertController.textFields?.first?.text else { return } + let fileURL = self.documentsDirectory.appendingPathComponent(fileName) + self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + self.loadFiles() + } + alertController.addAction(createAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func copyFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") + do { + try FileOperations.copyFile(at: fileURL, to: destinationURL) + loadFiles() + } catch { + print("Copy failed with error: \(error)") + } + } + + private func moveFile(at fileURL: URL) { + let alertController = UIAlertController(title: "Move File", message: "Enter new file path", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "New file path" + } + let moveAction = UIAlertAction(title: "Move", style: .default) { _ in + guard let newPath = alertController.textFields?.first?.text else { return } + let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) + do { + try FileOperations.moveFile(at: fileURL, to: destinationURL) + self.loadFiles() + } catch { + print("Move failed with error: \(error)") + } + } + alertController.addAction(moveAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func compressFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") + do { + try FileOperations.compressFile(at: fileURL, to: destinationURL) + loadFiles() + } catch { + print("Compression failed with error: \(error)") + } + } + + private func renameFile(at fileURL: URL) { + let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = fileURL.lastPathComponent + } + let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in + guard let newName = alertController.textFields?.first?.text else { return } + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + do { + try FileOperations.renameFile(at: fileURL, to: newName) + self.loadFiles() + } catch { + print("Rename failed with error: \(error)") + } + } + alertController.addAction(renameAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func deleteFile(at fileURL: URL) { + do { + try FileOperations.deleteFile(at: fileURL) + loadFiles() + } catch { + print("Delete failed with error: \(error)") + } + } + + private func unzipFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + do { + try FileOperations.unzipFile(at: fileURL, to: destinationURL) + loadFiles() + } catch { + print("Unzip failed with error: \(error)") + } + } + + private func hexEditFile(at fileURL: URL) { + FileOperations.hexEditFile(at: fileURL) + // Implement hex edit functionality + } + + // MARK: - UIDocumentPickerViewControllerDelegate + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) do { - let data = try Data(contentsOf: fileURL) - let hexString = data.map { String(format: "%02x", $0) }.joined(separator: " ") - textView.text = hexString + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() } catch { - print("Failed to load file content: \(error)") + print("Failed to import file: \(error)") } } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) + cell.textLabel?.text = fileList[indexPath.row] + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + openFile(fileURL) + } + + private func openFile(_ fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + + switch fileExtension { + case "txt": + openTextEditor(fileURL) + case "plist": + openPlistEditor(fileURL) + default: + openHexEditor(fileURL) + } + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } } \ No newline at end of file From 2466a4568a5fe23abea039eb8cb8a5106940d8b8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 23:21:15 -0400 Subject: [PATCH 069/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 193 ++++++++++++++++++------ 1 file changed, 150 insertions(+), 43 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 842f1c63..506bc1bb 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,11 +1,13 @@ import UIKit import ZIPFoundation -class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate { +class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate, UISearchResultsUpdating { // MARK: - Properties private var fileList: [String] = [] + private var filteredFileList: [String] = [] private let fileManager = FileManager.default + private let searchController = UISearchController(searchResultsController: nil) private var documentsDirectory: URL { return fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! } @@ -32,6 +34,10 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData loadFiles() fileListTableView.delegate = self fileListTableView.dataSource = self + searchController.searchResultsUpdater = self + navigationItem.searchController = searchController + navigationItem.hidesSearchBarWhenScrolling = false + filteredFileList = fileList } // MARK: - UI Setup @@ -61,7 +67,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData ]) // Register the table view cell - fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") + fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") // Add long press gesture recognizer to table view let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) @@ -72,9 +78,10 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func loadFiles() { do { fileList = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) + filteredFileList = fileList fileListTableView.reloadData() } catch { - print("Failed to load files: \(error)") + presentAlert(title: "Error", message: "Failed to load files: \(error.localizedDescription)") } } @@ -93,7 +100,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData if gesture.state == .began { let point = gesture.location(in: fileListTableView) if let indexPath = fileListTableView.indexPathForRow(at: point) { - let fileName = fileList[indexPath.row] + let fileName = filteredFileList[indexPath.row] let fileURL = documentsDirectory.appendingPathComponent(fileName) showFileOptions(for: fileURL) } @@ -142,7 +149,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) self.loadFiles() } catch { - print("Failed to create folder: \(error)") + self.presentAlert(title: "Error", message: "Failed to create folder: \(error.localizedDescription)") } } alertController.addAction(createAction) @@ -168,11 +175,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func copyFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") - do { - try FileOperations.copyFile(at: fileURL, to: destinationURL) - loadFiles() - } catch { - print("Copy failed with error: \(error)") + DispatchQueue.global().async { + do { + try FileOperations.copyFile(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Copy failed with error: \(error.localizedDescription)") + } + } } } @@ -184,11 +197,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let moveAction = UIAlertAction(title: "Move", style: .default) { _ in guard let newPath = alertController.textFields?.first?.text else { return } let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) - do { - try FileOperations.moveFile(at: fileURL, to: destinationURL) - self.loadFiles() - } catch { - print("Move failed with error: \(error)") + DispatchQueue.global().async { + do { + try FileOperations.moveFile(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Move failed with error: \(error.localizedDescription)") + } + } } } alertController.addAction(moveAction) @@ -198,11 +217,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func compressFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") - do { - try FileOperations.compressFile(at: fileURL, to: destinationURL) - loadFiles() - } catch { - print("Compression failed with error: \(error)") + DispatchQueue.global().async { + do { + try FileOperations.compressFile(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Compression failed with error: \(error.localizedDescription)") + } + } } } @@ -214,11 +239,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in guard let newName = alertController.textFields?.first?.text else { return } let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - do { - try FileOperations.renameFile(at: fileURL, to: newName) - self.loadFiles() - } catch { - print("Rename failed with error: \(error)") + DispatchQueue.global().async { + do { + try FileOperations.renameFile(at: fileURL, to: newName) + DispatchQueue.main.async { + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Rename failed with error: \(error.localizedDescription)") + } + } } } alertController.addAction(renameAction) @@ -227,27 +258,42 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } private func deleteFile(at fileURL: URL) { - do { - try FileOperations.deleteFile(at: fileURL) - loadFiles() - } catch { - print("Delete failed with error: \(error)") + DispatchQueue.global().async { + do { + try FileOperations.deleteFile(at: fileURL) + DispatchQueue.main.async { + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") + } + } } } private func unzipFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") - do { - try FileOperations.unzipFile(at: fileURL, to: destinationURL) - loadFiles() - } catch { - print("Unzip failed with error: \(error)") + DispatchQueue.global().async { + do { + try FileOperations.unzipFile(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") + } + } } } private func hexEditFile(at fileURL: URL) { - FileOperations.hexEditFile(at: fileURL) - // Implement hex edit functionality + guard let navigationController = self.navigationController else { + presentAlert(title: "Error", message: "Navigation controller is missing") + return + } + FileOperations.hexEditFile(at: fileURL, in: navigationController) } // MARK: - UIDocumentPickerViewControllerDelegate @@ -261,27 +307,37 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData try fileManager.copyItem(at: selectedFileURL, to: destinationURL) loadFiles() } catch { - print("Failed to import file: \(error)") + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") } } // MARK: - UITableViewDelegate, UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return fileList.count + return searchController.isActive ? filteredFileList.count : fileList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) - cell.textLabel?.text = fileList[indexPath.row] + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = fileList[indexPath.row] + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] let fileURL = documentsDirectory.appendingPathComponent(fileName) openFile(fileURL) } + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + private func openFile(_ fileURL: URL) { let fileExtension = fileURL.pathExtension.lowercased() @@ -309,4 +365,55 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let hexEditorVC = HexEditorViewController(fileURL: fileURL) navigationController?.pushViewController(hexEditorVC, animated: true) } -} \ No newline at end of file + + // MARK: - Helper Methods + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } +} + +// Custom UITableViewCell +class FileTableViewCell: UITableViewCell { + let fileIconImageView = UIImageView() + let fileNameLabel = UILabel() + let fileSizeLabel = UILabel() + let fileDateLabel = UILabel() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + // Configure and add subviews to contentView + contentView.addSubview(fileIconImageView) + contentView.addSubview(fileNameLabel) + contentView.addSubview(fileSizeLabel) + contentView.addSubview(fileDateLabel) + + // Setup layout constraints + fileIconImageView.translatesAutoresizingMaskIntoConstraints = false + fileNameLabel.translatesAutoresizingMaskIntoConstraints = false + fileSizeLabel.translatesAutoresizingMaskIntoConstraints = false + fileDateLabel.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + fileIconImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + fileIconImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + fileIconImageView.widthAnchor.constraint(equalToConstant: 40), + fileIconImageView.heightAnchor.constraint(equalToConstant: 40), + + fileNameLabel.leadingAnchor.constraint(equalTo: fileIconImageView.trailingAnchor, constant: 16), + fileNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), + fileNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), + + fileSizeLabel.leadingAnchor.constraint(equalTo: fileNameLabel.leadingAnchor), + fileSizeLabel.topAnchor.constraint(equalTo: fileNameLabel.bottomAnchor, constant: 4), + + fileDateLabel.leadingAnchor.constraint(equalTo: \ No newline at end of file From 5e6f3fa4dcfb3dd24f24d19902f07fff80dc086e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sat, 15 Mar 2025 23:25:02 -0400 Subject: [PATCH 070/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 175 ++++++++++++------------ 1 file changed, 91 insertions(+), 84 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 506bc1bb..f09936ef 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,22 +1,27 @@ import UIKit import ZIPFoundation -class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate, UISearchResultsUpdating { +class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate { // MARK: - Properties private var fileList: [String] = [] private var filteredFileList: [String] = [] private let fileManager = FileManager.default private let searchController = UISearchController(searchResultsController: nil) + private var sortOrder: SortOrder = .name private var documentsDirectory: URL { return fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! } + enum SortOrder { + case name, date, size + } + // MARK: - UI Elements private let navigationBar: UINavigationBar = { let navBar = UINavigationBar() navBar.translatesAutoresizingMaskIntoConstraints = false - navBar.barTintColor = .systemBlue // Change this to match your app's theme + navBar.barTintColor = .systemBlue navBar.titleTextAttributes = [.foregroundColor: UIColor.white] return navBar }() @@ -27,13 +32,23 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData return tableView }() + private let activityIndicator: UIActivityIndicatorView = { + let indicator = UIActivityIndicatorView(style: .large) + indicator.translatesAutoresizingMaskIntoConstraints = false + indicator.hidesWhenStopped = true + return indicator + }() + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() setupUI() + setupActivityIndicator() loadFiles() fileListTableView.delegate = self fileListTableView.dataSource = self + fileListTableView.dragDelegate = self + fileListTableView.dropDelegate = self searchController.searchResultsUpdater = self navigationItem.searchController = searchController navigationItem.hidesSearchBarWhenScrolling = false @@ -47,23 +62,28 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // Setup Navigation Bar let navItem = UINavigationItem(title: "Files") let menuButton = UIBarButtonItem(title: "⋮", style: .plain, target: self, action: #selector(showMenu)) - navItem.rightBarButtonItem = menuButton + let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) + navItem.rightBarButtonItems = [menuButton, sortButton] navigationBar.setItems([navItem], animated: false) // Add UI elements to the view view.addSubview(navigationBar) view.addSubview(fileListTableView) + view.addSubview(activityIndicator) // Set up constraints NSLayoutConstraint.activate([ - navigationBar.topAnchor.constraint(equalTo: view.topAnchor), // Changed to view.topAnchor + navigationBar.topAnchor.constraint(equalTo: view.topAnchor), navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) // Register the table view cell @@ -74,10 +94,19 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData fileListTableView.addGestureRecognizer(longPressRecognizer) } + private func setupActivityIndicator() { + view.addSubview(activityIndicator) + NSLayoutConstraint.activate([ + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + } + // MARK: - Load Files private func loadFiles() { do { fileList = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) + sortFiles() filteredFileList = fileList fileListTableView.reloadData() } catch { @@ -85,6 +114,29 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } + private func sortFiles() { + switch sortOrder { + case .name: + fileList.sort(by: { $0.lowercased() < $1.lowercased() }) + case .date: + fileList.sort(by: { getFileDate($0) < getFileDate($1) }) + case .size: + fileList.sort(by: { getFileSize($0) < getFileSize($1) }) + } + } + + private func getFileDate(_ fileName: String) -> Date { + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) + return attributes?[.modificationDate] as? Date ?? Date.distantPast + } + + private func getFileSize(_ fileName: String) -> UInt64 { + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) + return attributes?[.size] as? UInt64 ?? 0 + } + // MARK: - Actions @objc private func showMenu() { let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) @@ -96,6 +148,15 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData present(menu, animated: true, completion: nil) } + @objc private func changeSortOrder() { + let sortMenu = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) + sortMenu.addAction(UIAlertAction(title: "Name", style: .default, handler: { _ in self.sortOrder = .name; self.loadFiles() })) + sortMenu.addAction(UIAlertAction(title: "Date", style: .default, handler: { _ in self.sortOrder = .date; self.loadFiles() })) + sortMenu.addAction(UIAlertAction(title: "Size", style: .default, handler: { _ in self.sortOrder = .size; self.loadFiles() })) + sortMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(sortMenu, animated: true, completion: nil) + } + @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { if gesture.state == .began { let point = gesture.location(in: fileListTableView) @@ -120,6 +181,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData menu.addAction(UIAlertAction(title: "Compress", style: .default, handler: { _ in self.compressFile(at: fileURL) })) menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) menu.addAction(UIAlertAction(title: "Delete", style: .default, handler: { _ in self.deleteFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Share", style: .default, handler: { _ in self.shareFile(at: fileURL) })) } menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) @@ -175,14 +237,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func copyFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") + activityIndicator.startAnimating() DispatchQueue.global().async { do { try FileOperations.copyFile(at: fileURL, to: destinationURL) DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.loadFiles() } } catch { DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.presentAlert(title: "Error", message: "Copy failed with error: \(error.localizedDescription)") } } @@ -197,14 +262,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let moveAction = UIAlertAction(title: "Move", style: .default) { _ in guard let newPath = alertController.textFields?.first?.text else { return } let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) + self.activityIndicator.startAnimating() DispatchQueue.global().async { do { try FileOperations.moveFile(at: fileURL, to: destinationURL) DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.loadFiles() } } catch { DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.presentAlert(title: "Error", message: "Move failed with error: \(error.localizedDescription)") } } @@ -217,14 +285,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func compressFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") + activityIndicator.startAnimating() DispatchQueue.global().async { do { try FileOperations.compressFile(at: fileURL, to: destinationURL) DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.loadFiles() } } catch { DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.presentAlert(title: "Error", message: "Compression failed with error: \(error.localizedDescription)") } } @@ -239,14 +310,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in guard let newName = alertController.textFields?.first?.text else { return } let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + self.activityIndicator.startAnimating() DispatchQueue.global().async { do { try FileOperations.renameFile(at: fileURL, to: newName) DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.loadFiles() } } catch { DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.presentAlert(title: "Error", message: "Rename failed with error: \(error.localizedDescription)") } } @@ -258,14 +332,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } private func deleteFile(at fileURL: URL) { + activityIndicator.startAnimating() DispatchQueue.global().async { do { try FileOperations.deleteFile(at: fileURL) DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.loadFiles() } } catch { DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") } } @@ -274,14 +351,17 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func unzipFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + activityIndicator.startAnimating() DispatchQueue.global().async { do { try FileOperations.unzipFile(at: fileURL, to: destinationURL) DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.loadFiles() } } catch { DispatchQueue.main.async { + self.activityIndicator.stopAnimating() self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") } } @@ -296,6 +376,11 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData FileOperations.hexEditFile(at: fileURL, in: navigationController) } + private func shareFile(at fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityController, animated: true, completion: nil) + } + // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let selectedFileURL = urls.first else { @@ -338,82 +423,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData fileListTableView.reloadData() } - private func openFile(_ fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - - switch fileExtension { - case "txt": - openTextEditor(fileURL) - case "plist": - openPlistEditor(fileURL) - default: - openHexEditor(fileURL) - } - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } - - // MARK: - Helper Methods - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } -} - -// Custom UITableViewCell -class FileTableViewCell: UITableViewCell { - let fileIconImageView = UIImageView() - let fileNameLabel = UILabel() - let fileSizeLabel = UILabel() - let fileDateLabel = UILabel() - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupUI() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupUI() { - // Configure and add subviews to contentView - contentView.addSubview(fileIconImageView) - contentView.addSubview(fileNameLabel) - contentView.addSubview(fileSizeLabel) - contentView.addSubview(fileDateLabel) - - // Setup layout constraints - fileIconImageView.translatesAutoresizingMaskIntoConstraints = false - fileNameLabel.translatesAutoresizingMaskIntoConstraints = false - fileSizeLabel.translatesAutoresizingMaskIntoConstraints = false - fileDateLabel.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - fileIconImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - fileIconImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - fileIconImageView.widthAnchor.constraint(equalToConstant: 40), - fileIconImageView.heightAnchor.constraint(equalToConstant: 40), - - fileNameLabel.leadingAnchor.constraint(equalTo: fileIconImageView.trailingAnchor, constant: 16), - fileNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), - fileNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), - - fileSizeLabel.leadingAnchor.constraint(equalTo: fileNameLabel.leadingAnchor), - fileSizeLabel.topAnchor.constraint(equalTo: fileNameLabel.bottomAnchor, constant: 4), - - fileDateLabel.leadingAnchor.constraint(equalTo: \ No newline at end of file + private func openFile(_ \ No newline at end of file From 02b5522cc12963b775650f98876db4465a253d40 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 00:05:46 -0400 Subject: [PATCH 071/391] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec25dafd..f9a4ae68 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Feather +# Backdoor [![GitHub Release](https://img.shields.io/github/v/release/khcrysalis/feather?include_prereleases)](https://github.com/khcrysalis/feather/releases) [![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/khcrysalis/feather/total)](https://github.com/khcrysalis/feather/releases) [![GitHub License](https://img.shields.io/github/license/khcrysalis/feather?color=%23C96FAD)](https://github.com/khcrysalis/feather/blob/main/LICENSE) From 514bf5a9398c30a60694a97f4f033227ae0dc36e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 00:13:07 -0400 Subject: [PATCH 072/391] Create FileTableViewCell.swift --- iOS/Views/Home/FileTableViewCell.swift | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 iOS/Views/Home/FileTableViewCell.swift diff --git a/iOS/Views/Home/FileTableViewCell.swift b/iOS/Views/Home/FileTableViewCell.swift new file mode 100644 index 00000000..32a3b5ba --- /dev/null +++ b/iOS/Views/Home/FileTableViewCell.swift @@ -0,0 +1,78 @@ +import UIKit + +class FileTableViewCell: UITableViewCell { + let fileIconImageView = UIImageView() + let fileNameLabel = UILabel() + let fileSizeLabel = UILabel() + let fileDateLabel = UILabel() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + // Configure and add subviews to contentView + contentView.addSubview(fileIconImageView) + contentView.addSubview(fileNameLabel) + contentView.addSubview(fileSizeLabel) + contentView.addSubview(fileDateLabel) + + // Setup layout constraints + fileIconImageView.translatesAutoresizingMaskIntoConstraints = false + fileNameLabel.translatesAutoresizingMaskIntoConstraints = false + fileSizeLabel.translatesAutoresizingMaskIntoConstraints = false + fileDateLabel.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + fileIconImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + fileIconImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + fileIconImageView.widthAnchor.constraint(equalToConstant: 40), + fileIconImageView.heightAnchor.constraint(equalToConstant: 40), + + fileNameLabel.leadingAnchor.constraint(equalTo: fileIconImageView.trailingAnchor, constant: 16), + fileNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), + fileNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), + + fileSizeLabel.leadingAnchor.constraint(equalTo: fileNameLabel.leadingAnchor), + fileSizeLabel.topAnchor.constraint(equalTo: fileNameLabel.bottomAnchor, constant: 4), + + fileDateLabel.leadingAnchor.constraint(equalTo: fileNameLabel.leadingAnchor), + fileDateLabel.topAnchor.constraint(equalTo: fileSizeLabel.bottomAnchor, constant: 4), + fileDateLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8) + ]) + } + + func configure(with file: File) { + // Configure the cell with file data + fileNameLabel.text = file.name + fileSizeLabel.text = "\(file.size) bytes" + fileDateLabel.text = "\(file.date)" + // Set an appropriate icon for the file type + fileIconImageView.image = UIImage(systemName: "doc.text") + } +} + +// File model class to hold file information +class File { + let url: URL + var name: String { + return url.lastPathComponent + } + var size: UInt64 { + let attributes = try? FileManager.default.attributesOfItem(atPath: url.path) + return attributes?[.size] as? UInt64 ?? 0 + } + var date: Date { + let attributes = try? FileManager.default.attributesOfItem(atPath: url.path) + return attributes?[.modificationDate] as? Date ?? Date.distantPast + } + + init(url: URL) { + self.url = url + } +} \ No newline at end of file From e5f0b354b5daa1dc32bb110ac7959184de602db9 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 00:20:00 -0400 Subject: [PATCH 073/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index f09936ef..385c4b8a 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -423,4 +423,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData fileListTableView.reloadData() } - private func openFile(_ \ No newline at end of file + private func openFile(_ \ No newline at end of file From 732863e8e048df6259f5c418da9fc5a1b87cf877 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 00:20:42 -0400 Subject: [PATCH 074/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 35 ++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 385c4b8a..86266045 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -423,4 +423,37 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData fileListTableView.reloadData() } - private func openFile(_ \ No newline at end of file + private func openFile(_ fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + + switch fileExtension { + case "txt": + openTextEditor(fileURL) + case "plist": + openPlistEditor(fileURL) + default: + openHexEditor(fileURL) + } + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } +} \ No newline at end of file From c347f4ad9bc58b475370a7038d2e43b2380c6f76 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 01:28:02 -0400 Subject: [PATCH 075/391] Update HexEditorViewController.swift --- iOS/Views/Home/HexEditorViewController.swift | 24 +++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/iOS/Views/Home/HexEditorViewController.swift b/iOS/Views/Home/HexEditorViewController.swift index fdd0da3a..047a6ccb 100644 --- a/iOS/Views/Home/HexEditorViewController.swift +++ b/iOS/Views/Home/HexEditorViewController.swift @@ -1,6 +1,6 @@ import UIKit -class HexEditorViewController: UIViewController { +class HexEditorViewController: UIViewController, UITextViewDelegate { private let fileURL: URL private var textView: UITextView! private var toolbar: UIToolbar! @@ -112,12 +112,12 @@ class HexEditorViewController: UIViewController { } @objc private func promptFindReplace() { - let alert = UIAlertController(title: "Find and Replace", message: "Enter hex value to find and replace:", preferredStyle: .alert) + let alert = UIAlertController(title: "Find and Replace", message: "Enter text to find and replace:", preferredStyle: .alert) alert.addTextField { textField in - textField.placeholder = "Find (e.g., 4a 6f 68)" + textField.placeholder = "Find" } alert.addTextField { textField in - textField.placeholder = "Replace (e.g., 4b 6c 69)" + textField.placeholder = "Replace" } alert.addAction(UIAlertAction(title: "Replace", style: .default, handler: { [weak self] _ in guard let findText = alert.textFields?[0].text, let replaceText = alert.textFields?[1].text else { return } @@ -132,13 +132,7 @@ class HexEditorViewController: UIViewController { hasUnsavedChanges = true } - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - - private void promptSaveChanges() { + private func promptSaveChanges() { let alert = UIAlertController(title: "Unsaved Changes", message: "You have unsaved changes. Do you want to save them before leaving?", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak self] _ in self?.saveChanges() @@ -165,10 +159,14 @@ class HexEditorViewController: UIViewController { saveChanges() } } -} -extension HexEditorViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { hasUnsavedChanges = true } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } } \ No newline at end of file From df6d84176a7312a3e753b1df0d942963d1eea098 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 01:29:47 -0400 Subject: [PATCH 076/391] Update PlistEditorViewController.swift --- .../Home/PlistEditorViewController.swift | 90 ++++++++++++++++++- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Home/PlistEditorViewController.swift b/iOS/Views/Home/PlistEditorViewController.swift index caf82d95..22395cf7 100644 --- a/iOS/Views/Home/PlistEditorViewController.swift +++ b/iOS/Views/Home/PlistEditorViewController.swift @@ -1,6 +1,6 @@ import UIKit -class PlistEditorViewController: UIViewController { +class PlistEditorViewController: UIViewController, UITextViewDelegate { private let fileURL: URL private var textView: UITextView! private var toolbar: UIToolbar! @@ -33,7 +33,7 @@ class PlistEditorViewController: UIViewController { stopAutoSaveTimer() } - private void setupUI() { + private func setupUI() { view.backgroundColor = .systemBackground // Setup text view @@ -74,4 +74,88 @@ class PlistEditorViewController: UIViewController { textView.text = convertPlistToString(plist: plist) } } catch { - presentAlert(title: "Error", message: "Failed to load plist content: \(error.localizedDescription)") \ No newline at end of file + presentAlert(title: "Error", message: "Failed to load plist content: \(error.localizedDescription)") + } + } + + @objc private func saveChanges() { + // Implement save changes logic + } + + @objc private func copyContent() { + UIPasteboard.general.string = textView.text + presentAlert(title: "Copied", message: "Content copied to clipboard.") + } + + @objc private func undoAction() { + textView.undoManager?.undo() + } + + @objc private func redoAction() { + textView.undoManager?.redo() + } + + @objc private func promptFindReplace() { + let alert = UIAlertController(title: "Find and Replace", message: "Enter text to find and replace:", preferredStyle: .alert) + alert.addTextField { textField in + textField.placeholder = "Find" + } + alert.addTextField { textField in + textField.placeholder = "Replace" + } + alert.addAction(UIAlertAction(title: "Replace", style: .default, handler: { [weak self] _ in + guard let findText = alert.textFields?[0].text, let replaceText = alert.textFields?[1].text else { return } + self?.findAndReplace(findText: findText, replaceText: replaceText) + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func findAndReplace(findText: String, replaceText: String) { + textView.text = textView.text.replacingOccurrences(of: findText, with: replaceText) + hasUnsavedChanges = true + } + + private func promptSaveChanges() { + let alert = UIAlertController(title: "Unsaved Changes", message: "You have unsaved changes. Do you want to save them before leaving?", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak self] _ in + self?.saveChanges() + self?.navigationController?.popViewController(animated: true) + })) + alert.addAction(UIAlertAction(title: "Discard", style: .destructive, handler: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func startAutoSaveTimer() { + autoSaveTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(autoSaveChanges), userInfo: nil, repeats: true) + } + + private func stopAutoSaveTimer() { + autoSaveTimer?.invalidate() + autoSaveTimer = nil + } + + @objc private func autoSaveChanges() { + if hasUnsavedChanges { + saveChanges() + } + } + + func textViewDidChange(_ textView: UITextView) { + hasUnsavedChanges = true + } + + private func convertPlistToString(plist: [String: Any]) -> String { + // Implement conversion logic + return "" + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } +} \ No newline at end of file From e1021cbe58b446800d0300f05d861cc36a00051a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 01:31:40 -0400 Subject: [PATCH 077/391] Update FileOperations.swift --- iOS/Views/Home/FileOperations.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Home/FileOperations.swift b/iOS/Views/Home/FileOperations.swift index b771fd4c..1f0d6256 100644 --- a/iOS/Views/Home/FileOperations.swift +++ b/iOS/Views/Home/FileOperations.swift @@ -85,7 +85,7 @@ class FileOperations { if entry.type == .directory { try fileManager.createDirectory(at: destination, withIntermediateDirectories: true, attributes: nil) } else { - try archive.extract(entry, to: destination) + _ = try archive.extract(entry, to: destination) } } print("File unzipped from \(fileURL.path) to \(destinationURL.path)") From 1e9fdc032d037357b7b4384b8eb05ba1c61b86ad Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 01:33:25 -0400 Subject: [PATCH 078/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 35 +------------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 86266045..0381701f 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -423,37 +423,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData fileListTableView.reloadData() } - private func openFile(_ fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - - switch fileExtension { - case "txt": - openTextEditor(fileURL) - case "plist": - openPlistEditor(fileURL) - default: - openHexEditor(fileURL) - } - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } - - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } -} \ No newline at end of file + private func openFile(_ fileURL: URL \ No newline at end of file From bd1c7ebdb371e3312a13c9d439c0ad048945759a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 01:34:56 -0400 Subject: [PATCH 079/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 35 ++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 0381701f..bf7d6f20 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -423,4 +423,37 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData fileListTableView.reloadData() } - private func openFile(_ fileURL: URL \ No newline at end of file + private func openFile(_ fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + + switch fileExtension { + case "txt": + openTextEditor(fileURL) + case "plist": + openPlistEditor(fileURL) + default: + openHexEditor(fileURL) + } + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } +} \ No newline at end of file From a4f45fe2cd5630a28045db97e4a5efafdb376cce Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 01:42:32 -0400 Subject: [PATCH 080/391] Update main.yml --- .github/workflows/main.yml | 50 +++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9f43c029..5efa306b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,19 +6,41 @@ on: jobs: build: runs-on: macos-15 + steps: - name: Checkout uses: actions/checkout@v3 + - name: Clean build environment + run: | + rm -rf build + rm -rf upload + mkdir -p build upload + - name: Install dependencies (packages) run: | curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid brew install 7zip gnu-sed - - name: Compile f + - name: Cache Homebrew + uses: actions/cache@v3 + with: + path: /Users/runner/Library/Caches/Homebrew + key: ${{ runner.os }}-homebrew-${{ hashFiles('**/Brewfile.lock.json') }} + restore-keys: | + ${{ runner.os }}-homebrew- + + - name: Cache Ruby gems + uses: actions/cache@v3 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + - name: Compile run: | - mkdir upload make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone mv packages/* upload/ @@ -33,6 +55,12 @@ jobs: mv upload/feather.ipa upload/feather_v${VERSION}.ipa cp upload/feather_v${VERSION}.ipa upload/feather_v${VERSION}.tipa + - name: Upload Build Artifacts + uses: actions/upload-artifact@v3 + with: + name: build-artifacts + path: upload/ + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: @@ -44,4 +72,20 @@ jobs: fail_on_unmatched_files: true draft: true env: - GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Notify Build Success + if: success() + run: echo "Build and release process completed successfully." + + - name: Notify Build Failure + if: failure() + run: echo "Build and release process failed." + +notifications: + slack: + channels: + - '#builds' + on: + - success + - failure \ No newline at end of file From 47b027d0ec5b1f95e8eab67bb8c7408a840a4f0a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 01:45:22 -0400 Subject: [PATCH 081/391] Update main.yml --- .github/workflows/main.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5efa306b..f8342084 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -80,12 +80,4 @@ jobs: - name: Notify Build Failure if: failure() - run: echo "Build and release process failed." - -notifications: - slack: - channels: - - '#builds' - on: - - success - - failure \ No newline at end of file + run: echo "Build and release process failed." \ No newline at end of file From 5067199b40baad30adea06b3bf4123d288f6ce81 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 01:50:16 -0400 Subject: [PATCH 082/391] Update main.yml --- .github/workflows/main.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f8342084..836c1528 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,12 +55,6 @@ jobs: mv upload/feather.ipa upload/feather_v${VERSION}.ipa cp upload/feather_v${VERSION}.ipa upload/feather_v${VERSION}.tipa - - name: Upload Build Artifacts - uses: actions/upload-artifact@v3 - with: - name: build-artifacts - path: upload/ - - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: From 53cb466bf4af225099e11071d9383e281c8a1934 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 01:55:52 -0400 Subject: [PATCH 083/391] Update main.yml --- .github/workflows/main.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 836c1528..a419901e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,12 +13,12 @@ jobs: - name: Clean build environment run: | - rm -rf build - rm -rf upload + rm -rf build upload mkdir -p build upload - name: Install dependencies (packages) run: | + brew update curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid brew install 7zip gnu-sed @@ -31,14 +31,6 @@ jobs: restore-keys: | ${{ runner.os }}-homebrew- - - name: Cache Ruby gems - uses: actions/cache@v3 - with: - path: vendor/bundle - key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-gems- - - name: Compile run: | make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone @@ -74,4 +66,5 @@ jobs: - name: Notify Build Failure if: failure() + continue-on-error: true run: echo "Build and release process failed." \ No newline at end of file From 48828b3231a865b2c9f07eb3fffe2a581112315f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 02:02:28 -0400 Subject: [PATCH 084/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 40 ++----------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index bf7d6f20..316e842b 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -167,7 +167,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } } - + private func showFileOptions(for fileURL: URL) { let fileExtension = fileURL.pathExtension.lowercased() let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) @@ -420,40 +420,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() - } - - private func openFile(_ fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - - switch fileExtension { - case "txt": - openTextEditor(fileURL) - case "plist": - openPlistEditor(fileURL) - default: - openHexEditor(fileURL) - } - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } - - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } -} \ No newline at end of file + \ No newline at end of file From ec1ec2f023ce8bdb56eb0004070d1dcd97d79a6c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 02:03:54 -0400 Subject: [PATCH 085/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 76 ++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 316e842b..46d31010 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -419,5 +419,79 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: - UISearchResultsUpdating func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - File Operations + private func openFile(_ fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + + switch fileExtension { + case "txt": + openTextEditor(fileURL) + case "plist": + openPlistEditor(fileURL) + default: + openHexEditor(fileURL) + } + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as! NSItemProviderWriting) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + + // MARK: - UIButton Configuration for iOS 15 and later + func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } +} \ No newline at end of file From 49975e2eb368010cb18a83df6b8e326789c1ce81 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 02:37:54 -0400 Subject: [PATCH 086/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 158 ++++++------------------ 1 file changed, 35 insertions(+), 123 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 46d31010..7f67670e 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -2,7 +2,7 @@ import UIKit import ZIPFoundation class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate { - + // MARK: - Properties private var fileList: [String] = [] private var filteredFileList: [String] = [] @@ -10,7 +10,9 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name private var documentsDirectory: URL { - return fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! + let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") + createFilesDirectoryIfNeeded(at: directory) + return directory } enum SortOrder { @@ -39,6 +41,14 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData return indicator }() + private let uploadButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Upload File", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(uploadFile), for: .touchUpInside) + return button + }() + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -70,20 +80,26 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData view.addSubview(navigationBar) view.addSubview(fileListTableView) view.addSubview(activityIndicator) + view.addSubview(uploadButton) - // Set up constraints + // Set up constraints with padding NSLayoutConstraint.activate([ - navigationBar.topAnchor.constraint(equalTo: view.topAnchor), - navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), - navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + navigationBar.topAnchor.constraint(equalTo: view.topAnchor, constant: 20), + navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), - fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), - fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 20), + fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor, constant: -20), activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), + + uploadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), + uploadButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + uploadButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + uploadButton.heightAnchor.constraint(equalToConstant: 50) ]) // Register the table view cell @@ -192,6 +208,13 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // Implement select files functionality } + @objc private func uploadFile() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + private func importFile() { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = self @@ -383,115 +406,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } - } - - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - openFile(fileURL) - } - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() - } - - // MARK: - File Operations - private func openFile(_ fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - - switch fileExtension { - case "txt": - openTextEditor(fileURL) - case "plist": - openPlistEditor(fileURL) - default: - openHexEditor(fileURL) - } - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } - - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as! NSItemProviderWriting) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } - } - - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - - // MARK: - UIButton Configuration for iOS 15 and later - func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - } -} - \ No newline at end of file + guard let selectedFileURL \ No newline at end of file From fe51178b904140932ebd921e1f96319bbf1b9067 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 02:38:42 -0400 Subject: [PATCH 087/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 121 +++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 7f67670e..77d80fb0 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -406,4 +406,123 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL \ No newline at end of file + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } + } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + openFile(fileURL) + } + + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - File Operations + private func openFile(_ fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + + switch fileExtension { + case "txt": + openTextEditor(fileURL) + case "plist": + openPlistEditor(fileURL) + default: + openHexEditor(fileURL) + } + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as! NSItemProviderWriting) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + + // MARK: - Create Files Directory + private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } + } + + // MARK: - UIButton Configuration for iOS 15 and later + func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } +} \ No newline at end of file From 913dc6edd4e35a940617571165c15713be98c754 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 02:47:06 -0400 Subject: [PATCH 088/391] Update Localizable.strings --- .../en.lproj/Localizable.strings | 97 ++++++------------- 1 file changed, 29 insertions(+), 68 deletions(-) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index 9dd267aa..31dc77e9 100644 --- a/Shared/Localizations/en.lproj/Localizable.strings +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -15,7 +15,7 @@ "DISMISS" = "Dismiss"; "LAME" = "Lame"; "SAVE" = "Save"; -"SET" = "Save"; +"SET" = "Set"; "OK" = "OK"; "CONTINUE" = "Continue"; "IMPORT" = "Import"; @@ -31,12 +31,12 @@ // MARK: - Error messages "ERROR_INSTALLER" = "Installer"; -"ERROR_INSTALLER_DESCRIPTION" = "Failed to load SSL certificates"; +"ERROR_INSTALLER_DESCRIPTION" = "Failed to load SSL certificates."; "ERROR_ZSIGN_FAILED" = "Signing failed."; -"ERROR_FAILED_TO_READ_MOBILEPROVISION" = "Failed to read mobileprovision file"; +"ERROR_FAILED_TO_READ_MOBILEPROVISION" = "Failed to read mobile provision file."; // MARK: - Success messages -"SUCCESS_SIGNED" = "Successfully signed %@"; +"SUCCESS_SIGNED" = "Successfully signed %@."; "SUCCESS_RESIGN" = "Successfully resigned!"; "SUCCESS_REQUIRES_RESTART" = "You must close the app for changes to take effect."; @@ -46,8 +46,8 @@ "ONBOARDING_CELL_1_DESCRIPTION" = "Sideload apps without a computer, all done from your device."; "ONBOARDING_CELL_2_TITLE" = "Customize Apps"; "ONBOARDING_CELL_2_DESCRIPTION" = "Manage and customize your apps for your needs."; -"ONBOARDING_CELL_3_TITLE" = "And Many More Thing’s."; -"ONBOARDING_CELL_3_DESCRIPTION" = "Altstore, Esign, Scarlet any repo’s you got work. import IPA's easily, easy certificate management, and many more thing’s."; +"ONBOARDING_CELL_3_TITLE" = "And Many More Things"; +"ONBOARDING_CELL_3_DESCRIPTION" = "Altstore, Esign, Scarlet, and any repos you have work. Import IPAs easily, manage certificates simply, and much more."; "ONBOARDING_FOOTER" = "Developed by BDG. Made for users who want to sideload & have freedom without the need of a computer. Packed with many features included inside."; "ONBOARDING_FOOTER_LINK" = "Learn more..."; "ONBOARDING_CONTINUE_BUTTON" = "Continue"; @@ -60,7 +60,7 @@ // MARK: - TransferPreview "TRANSFER_PREVIEW_PACKAGING" = "Packaging..."; -"TRANSFER_PREVIEW_READY" = "Ready To Install"; +"TRANSFER_PREVIEW_READY" = "Ready to Install"; "TRANSFER_PREVIEW_SENDING_MANIFEST" = "Sending Manifest..."; "TRANSFER_PREVIEW_SENDING_PAYLOAD" = "Sending Payload..."; "TRANSFER_PREVIEW_DONE" = "Done"; @@ -78,7 +78,7 @@ "SOURCES_VIEW_ADD_SOURCES_ALERT_DESCRIPTION" = "Add Altstore Repo URL"; "SOURCES_VIEW_ADD_SOURCES_ALERT_BUTTON_IMPORT_REPO" = "Import Repositories"; "SOURCES_VIEW_ADD_SOURCES_ALERT_BUTTON_EXPORT_REPO" = "Export Repositories"; -"SOURCES_VIEW_ADD_SOURCES_ALERT_BUTTON_EXPORT_REPO_ACTION_SUCCESS" = "Copied Repositories to Clipboard"; +"SOURCES_VIEW_ADD_SOURCES_ALERT_BUTTON_EXPORT_REPO_ACTION_SUCCESS" = "Copied repositories to clipboard."; "SOURCES_VIEW_ADD_SOURCES_FOOTER_NOTSTARTED" = "Enter a URL to start validation."; "SOURCES_VIEW_ADD_SOURCES_FOOTER_NOTVALIDJSON" = "Invalid JSON, please check your input."; "SOURCES_VIEW_ADD_SOURCES_FOOTER_VALID" = "Valid JSON entered."; @@ -103,7 +103,7 @@ "APPS_INFORMATION_SECTION_TITLE_NAME" = "Application"; "APPS_INFORMATION_TITLE_DELETED_FILE" = "Deleted File"; "APPS_INFORMATION_TITLE_DELETED_FILE_TITLE" = "File has been deleted."; -"APPS_INFORMATION_TITLE_DELETED_FILE_DESCRIPTION" = "This is a useless entry, it does not have a file and Feather will not allow you to install it. It's recommended you delete by swiping on the cell in the Apps tab."; +"APPS_INFORMATION_TITLE_DELETED_FILE_DESCRIPTION" = "This is a useless entry, it does not have a file and Feather will not allow you to install it. It's recommended you delete it by swiping on the cell in the Apps tab."; "APPS_INFORMATION_TITLE_NAME" = "Name"; "APPS_INFORMATION_TITLE_VERSION" = "Version"; "APPS_INFORMATION_TITLE_IDENTIFIER" = "Identifier"; @@ -129,7 +129,7 @@ "APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION" = "You do not have a certificate selected, please select or upload one in the signing section of the settings tab."; // MARK: - AppSigningViewController -> AppSigningAdvancedViewController -"APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_APPEARENCE" = "Appearence"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_APPEARANCE" = "Appearance"; "APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_MINIMUM_APP_VERSION" = "Minimum App Version"; "APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_PROPERTIES" = "Properties"; @@ -138,7 +138,7 @@ // MARK: - SigningsOptionsViewController "APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_PROTECTIONS" = "Enable Protection"; -"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_PROTECTIONS_DESCRIPTION" = "Enabling protection will append a random string to each bundle identifier, this is to protect the Apple ID related to your certificate from being flagged by Apple. However, if you don't care about this you can ignore it."; +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_PROTECTIONS_DESCRIPTION" = "Enabling protection will append a random string to each bundle identifier. This is to protect the Apple ID related to your certificate from being flagged by Apple. However, if you don't care about this you can ignore it."; "APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_DYNAMIC_PROTECTION" = "Dynamic Protection"; "APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_DYNAMIC_PROTECTION_DESCRIPTION" = "Dynamic protection will only apply PPQ protection if the bundle identifier exists on the App Store. This requires an internet connection during signing."; "APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_IDENTIFIERS" = "Bundle Identifiers"; @@ -157,7 +157,7 @@ "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_UISUPPORTEDDEVICES" = "Remove UISupportedDevices"; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_UISUPPORTEDDEVICES_DESCRIPTION" = "Removes device restrictions for the application."; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_URLSCHEME" = "Remove URLScheme"; -"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_URLSCHEME_DESCRIPTION" = "Removes any possible URL schemes (i.e. 'feather://')"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_URLSCHEME_DESCRIPTION" = "Removes any possible URL schemes (i.e. 'feather://')."; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_ALLOW_BROWSING_DOCUMENTS" = "Allow Browsing Documents"; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_ALLOW_BROWSING_DOCUMENTS_DESCRIPTION" = "Allows other apps to open and edit the files stored in the Documents folder. This option also lets users set the app’s default save location in Settings."; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_ALLOW_ITUNES_SHARING" = "Allow iTunes Sharing"; @@ -165,15 +165,15 @@ "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_PRO_MOTION" = "Force ProMotion"; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_PRO_MOTION_DESCRIPTION" = "Enables ProMotion capabilities within the app, however on lower versions of 15.x this may not be enough."; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_GAME_MODE" = "Force Game Mode"; -"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_GAME_MODE_DESCRIPTION" = "Enables Game Mode within the app, minimizing background activity and prioritized performance for the app."; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_GAME_MODE_DESCRIPTION" = "Enables Game Mode within the app, minimizing background activity and prioritizing performance for the app."; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_FULLSCREEN" = "Force Fullscreen"; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_FULLSCREEN_DESCRIPTION" = "Forces only fullscreen capabilities within iPad apps, disallowing sharing the screen with other apps. On an external screen, the window for an app with this setting maintains its canvas size."; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_DELETE_PLACEHOLDER_WATCH_APP" = "Delete Placeholder Watch App"; -"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_DELETE_PLACEHOLDER_WATCH_APP_DESCRIPTION" = "Removes unwanted watch placeholder which isn't supposed to be there, present in apps such as YouTube music, etc."; -"APP_SIGNING_INPUT_VIEW_CONTROLLER_FORCELOCALIZATIONS" = "Force Try To Localize"; -"APP_SIGNING_INPUT_VIEW_CONTROLLER_FORCELOCALIZATIONS_DESCRIPTION" = "Forces localization by modifying every localizable bundle within the app when trying to change a name of the app."; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_DELETE_PLACEHOLDER_WATCH_APP_DESCRIPTION" = "Removes unwanted watch placeholder present in apps such as YouTube Music."; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_FORCELOCALIZATIONS" = "Force Localization"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_FORCELOCALIZATIONS_DESCRIPTION" = "Forces localization by modifying every localizable bundle within the app when trying to change the name of the app."; "APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_PROVISIONING" = "Remove Provisioning File"; -"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_PROVISIONING_DESCRIPTION" = "Removes .mobileprovison from appearing in your app after signing."; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_PROVISIONING_DESCRIPTION" = "Removes .mobileprovision from appearing in your app after signing."; // MARK: - LibraryViewController "SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER" = "Search Library"; @@ -193,8 +193,8 @@ "LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN" = "Open %@"; "LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE" = "Update %@"; "LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE" = "Clear Update"; -"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS" = "View Details"; -"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES" = "Open in Files"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS" = "View Details"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES" = "Open in Files"; "LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM" = "Confirm Installation"; "LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION" = "Trying to install via the downloaded apps tab may not work as they are most likely not signed! It's recommended you sign that application first before installing."; @@ -202,8 +202,8 @@ "SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_GENERAL" = "General"; "SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_SIGNING" = "Signing"; "SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_SIGNING_SERVER" = "Signing Server"; -"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_ISSUES" = "If any issues occur within Backdoor please report it via my website. When submitting an issue, be sure to submit any logs."; -"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_SERVER_LIMITATIONS" = "Sadly due to limitations server certificates will need to be re-renewed every year to keep Backdoor's local features working properly, tap this button to retrieve the most up-to-date files from our repositories."; +"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_ISSUES" = "If any issues occur within Backdoor please report it via our website. When submitting an issue, be sure to submit any logs."; +"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_SERVER_LIMITATIONS" = "Sadly, due to limitations, server certificates will need to be re-renewed every year to keep Backdoor's local features working properly. Tap this button to retrieve the most up-to-date files from our repositories."; "SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_DEFAULT_SERVER" = "Default server goes to %@"; "SETTINGS_VIEW_CONTROLLER_TITLE" = "Server Options"; "SETTINGS_VIEW_CONTROLLER_TITLE_ONLINE" = "Online"; @@ -214,7 +214,7 @@ "SETTINGS_VIEW_CONTROLLER_CELL_DISPLAY" = "Display"; "SETTINGS_VIEW_CONTROLLER_CELL_APP_ICON" = "App Icon"; "SETTINGS_VIEW_CONTROLLER_CELL_LANGUAGE" = "Language"; -"SETTINGS_VIEW_CONTROLLER_CELL_CURRENT_CERTIFICATE_NOSELECTED" = "No certificates selected"; +"SETTINGS_VIEW_CONTROLLER_CELL_CURRENT_CERTIFICATE_NO_SELECTED" = "No certificates selected"; "SETTINGS_VIEW_CONTROLLER_CELL_ADD_CERTIFICATES" = "Add Certificate"; "SETTINGS_VIEW_CONTROLLER_CELL_SIGN_OPTIONS" = "Signing Options"; "SETTINGS_VIEW_CONTROLLER_CELL_SERVER_OPTIONS" = "Server Options"; @@ -222,7 +222,7 @@ "SETTINGS_VIEW_CONTROLLER_CELL_APPS_FOLDER" = "Open Apps Folder"; "SETTINGS_VIEW_CONTROLLER_CELL_CERTS_FOLDER" = "Open Certificates Folder"; "SETTINGS_VIEW_CONTROLLER_CELL_UPDATE_LOCAL_CERTIFICATE" = "Update Local Certificate"; -"SETTINGS_VIEW_CONTROLLER_CELL_UPDATE_LOCAL_CERTIFICATE_UPDATING" = "Update Local Certificate"; +"SETTINGS_VIEW_CONTROLLER_CELL_UPDATE_LOCAL_CERTIFICATE_UPDATING" = "Updating Local Certificate"; "SETTINGS_VIEW_CONTROLLER_CELL_RESET" = "Reset"; "SETTINGS_VIEW_CONTROLLER_CELL_RESET_ALL" = "Reset All"; "SETTINGS_VIEW_CONTROLLER_CELL_RESET_CONFIGURATION" = "Reset Configuration"; @@ -231,7 +231,7 @@ "SETTINGS_VIEW_CONTROLLER_CELL_EXPORT_ID" = "Export Random Identifier"; "SETTINGS_VIEW_CONTROLLER_CELL_CHANGE_ID" = "Change Random Identifier"; "SETTINGS_VIEW_CONTROLLER_PPQ_ALERT_TITLE" = "PPQCheck Protections"; -"SETTINGS_VIEW_CONTROLLER_PPQ_ALERT_DESCRIPTION" = "This setting enables the PPQCheck protections, which is designed to prepend each bundle identifier for the apps you sideload with a random string.\n\nThis is meant to avoid apple flagging your account by (trying) to make it so they're unable to associate the app you're sideloading with one from the App Store."; +"SETTINGS_VIEW_CONTROLLER_PPQ_ALERT_DESCRIPTION" = "This setting enables the PPQCheck protections, which is designed to prepend each bundle identifier for the apps you sideload with a random string.\n\nThis is meant to avoid Apple flagging your account by (trying) to make it so they're unable to associate the app you're sideloading with one from the App Store."; "SETTINGS_VIEW_CONTROLLER_URL_ALERT_TITLE" = "Change Download URL"; "SETTINGS_VIEW_CONTROLLER_CELL_CHANGE_IDENTIFIER" = "Change Random Identifier"; @@ -246,60 +246,21 @@ // MARK: - SettingsViewController -> DisplayViewController "DISPLAY_VIEW_CONTROLLER_SECTION_TITLE_TINT_COLOR" = "Tint Color"; -"DISPLAY_VIEW_CONTROLLER_SECTION_TITLE_APP_APPEARENCE" = "App Appearence"; +"DISPLAY_VIEW_CONTROLLER_SECTION_TITLE_APP_APPEARANCE" = "App Appearance"; "DISPLAY_VIEW_CONTROLLER_SECTION_TITLE_STORE" = "Store"; "DISPLAY_VIEW_CONTROLLER_CELL_DEFAULT_SUBTITLE" = "Default Subtitle"; -"DISPLAY_VIEW_CONTROLLER_CELL_DEFAULT_SUBTITLE_DESCRIPTION" = "Default style for backdoor, hides localized description and only includes subtitle."; +"DISPLAY_VIEW_CONTROLLER_CELL_DEFAULT_SUBTITLE_DESCRIPTION" = "Default style for Backdoor, hides localized description and only includes subtitle."; "DISPLAY_VIEW_CONTROLLER_CELL_LOCALIZED_SUBTITLE" = "Localized Subtitle"; "DISPLAY_VIEW_CONTROLLER_CELL_LOCALIZED_SUBTITLE_DESCRIPTION" = "Replaces subtitle with app description."; "DISPLAY_VIEW_CONTROLLER_CELL_BIG_DESCRIPTION" = "Big Description"; "DISPLAY_VIEW_CONTROLLER_CELL_BIG_DESCRIPTION_DESCRIPTION" = "Replaces screenshots with the app description."; "DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME" = "Use Team Name"; -"DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME_DESCRIPTION" = "Replaces the certificate name with your team name, could be for better to distinguish between certificates if providers happen to always use the same App ID name."; +"DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME_DESCRIPTION" = "Replaces the certificate name with your team name, could be better to distinguish between certificates if providers happen to always use the same App ID name."; -// MARK: - SettingsViewController -> LanguageViewController.swift +// MARK: - SettingsViewController -> LanguageViewController "LANGUAGE_VIEW_TITLE" = "Language"; "LANGUAGE_VIEW_CELL_USE_SYSTEM_LANGUAGE" = "Use System Language"; // MARK: - SettingsViewController -> CertificatesViewController "CERTIFICATES_VIEW_CONTROLLER_TITLE" = "Certificates"; -"CERTIFICATES_VIEW_CONTROLLER_CELL_ADD_FOOTER" = "Supported file formats:\n\n- P12 (.p12)\n- Mobile Provision (.mobileprovision)\n\nMake sure your certificates are valid and are able to sideload to your device!"; -"CERTIFICATES_VIEW_CONTROLLER_CELL_ADD" = "Add Certificates"; -"CERTIFICATES_VIEW_CONTROLLER_CELL_ADD_DESCRIPTION" = "Tap to add a certificate"; -"CERTIFICATES_VIEW_CONTROLLER_DELETE_ALERT_TITLE" = "You don't want to do this!"; -"CERTIFICATES_VIEW_CONTROLLER_DELETE_ALERT_DESCRIPTION" = "You're trying to delete a selected certificate, try again later when you have another certificate on hand."; -"CERTIFICATES_VIEW_CONTROLLER_CELL_EXPIRED" = "Expired"; -"CERTIFICATES_VIEW_CONTROLLER_CELL_DAYS_LEFT" = "%@ days left"; - -// MARK: - SettingsViewController -> CertificatesViewController -> CertImportingViewController -"CERT_IMPORTING_VIEWCONTROLLER_TITLE" = "Import"; -"CERT_IMPORTING_VIEWCONTROLLER_SECTION_PROVISIONING" = ""; -"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_TITLE" = "Bad Password"; -"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_DESCRIPTION" = "Please check the password and try again."; -"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PROV" = "Import Provisioning File"; -"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_CERT" = "Import Certificate File"; -"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_ENTER_PW" = "Enter Password"; -"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PW" = "Password"; -"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PROV" = "Import a provisioning file to be able to sideload to your device."; -"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_CERT" = "Import a file containing a valid certificate."; -"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PASS" = "Enter the password associated with the private key, leave it blank if there's no password required."; - -// MARK: - SettingsViewController -> ResetViewController -"RESET_VIEW_CONTROLLER_CLEAR_CACHE" = "Clear Network Cache"; -"RESET_VIEW_CONTROLLER_CLEAR_CACHE_ALERT_TITLE" = "This action is irreversible. Cached network requests and images will be cleared."; - -// MARK: - Donation -"DONATION_TITLE" = "Donate"; -"DONATION_DONATIONS" = "Donations"; -"DONATION_CELL_1_TITLE" = "Secret Repo"; -"DONATION_CELL_1_DESCRIPTION" = "Get access to our secret repo by donating, will provide you with beta access to Feather."; -"DONATION_CELL_2_TITLE" = "Show Your Support"; -"DONATION_CELL_2_DESCRIPTION" = "Show your support by donating! If you're unable to donate, spreading the word about Feather works too!"; - -// MARK: - SettingsViewController -> LogsViewController.swift -"LOGS_VIEW_SECTION_TITLE_ERROR" = "%@ Critical Errors"; -"LOGS_VIEW_SECTION_TITLE_SHARE" = "Share Logs"; -"LOGS_VIEW_SECTION_TITLE_COPY" = "Copy Logs"; -"LOGS_VIEW_SUCCESS_DESCRIPTION" = "Log contents have been copied to clipboard."; -"LOGS_VIEW_FAIL_DESCRIPTION" = "Failed to copy log contents."; -"LOGS_VIEW_TITLE" = "Backdoor Logs"; \ No newline at end of file +"CERTIFICATES \ No newline at end of file From 6d13602de14b5af8c44248a7c79b9cc3d862a254 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 02:49:16 -0400 Subject: [PATCH 089/391] Update Localizable.strings --- .../en.lproj/Localizable.strings | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index 31dc77e9..d851df34 100644 --- a/Shared/Localizations/en.lproj/Localizable.strings +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -261,6 +261,35 @@ "LANGUAGE_VIEW_TITLE" = "Language"; "LANGUAGE_VIEW_CELL_USE_SYSTEM_LANGUAGE" = "Use System Language"; -// MARK: - SettingsViewController -> CertificatesViewController -"CERTIFICATES_VIEW_CONTROLLER_TITLE" = "Certificates"; -"CERTIFICATES \ No newline at end of file +// MARK: - SettingsViewController -> CertificatesViewController -> CertImportingViewController +"CERT_IMPORTING_VIEWCONTROLLER_TITLE" = "Import"; +"CERT_IMPORTING_VIEWCONTROLLER_SECTION_PROVISIONING" = ""; +"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_TITLE" = "Bad Password"; +"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_DESCRIPTION" = "Please check the password and try again."; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PROV" = "Import Provisioning File"; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_CERT" = "Import Certificate File"; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_ENTER_PW" = "Enter Password"; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PW" = "Password"; +"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PROV" = "Import a provisioning file to be able to sideload to your device."; +"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_CERT" = "Import a file containing a valid certificate."; +"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PASS" = "Enter the password associated with the private key, leave it blank if there's no password required."; + +// MARK: - SettingsViewController -> ResetViewController +"RESET_VIEW_CONTROLLER_CLEAR_CACHE" = "Clear Network Cache"; +"RESET_VIEW_CONTROLLER_CLEAR_CACHE_ALERT_TITLE" = "This action is irreversible. Cached network requests and images will be cleared."; + +// MARK: - Donation +"DONATION_TITLE" = "Donate"; +"DONATION_DONATIONS" = "Donations"; +"DONATION_CELL_1_TITLE" = "Secret Repo"; +"DONATION_CELL_1_DESCRIPTION" = "Get access to our secret repo by donating, will provide you with beta access to Feather."; +"DONATION_CELL_2_TITLE" = "Show Your Support"; +"DONATION_CELL_2_DESCRIPTION" = "Show your support by donating! If you're unable to donate, spreading the word about Feather works too!"; + +// MARK: - SettingsViewController -> LogsViewController.swift +"LOGS_VIEW_SECTION_TITLE_ERROR" = "%@ Critical Errors"; +"LOGS_VIEW_SECTION_TITLE_SHARE" = "Share Logs"; +"LOGS_VIEW_SECTION_TITLE_COPY" = "Copy Logs"; +"LOGS_VIEW_SUCCESS_DESCRIPTION" = "Log contents have been copied to clipboard."; +"LOGS_VIEW_FAIL_DESCRIPTION" = "Failed to copy log contents."; +"LOGS_VIEW_TITLE" = "Backdoor Logs"; \ No newline at end of file From 86175ba282d3fd8ca6b0a6c35abe3dd1ead83b97 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 03:26:08 -0400 Subject: [PATCH 090/391] Create devcontainer 2.json --- .devcontainer/devcontainer 2.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .devcontainer/devcontainer 2.json diff --git a/.devcontainer/devcontainer 2.json b/.devcontainer/devcontainer 2.json new file mode 100644 index 00000000..08ae5ab3 --- /dev/null +++ b/.devcontainer/devcontainer 2.json @@ -0,0 +1,19 @@ +{ + "name": "iOS Development Environment", + "build": { + "dockerfile": "Dockerfile", + "args": { + "VARIANT": "18-bullseye" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-azuretools.vscode-docker" + ] + } + }, + "postCreateCommand": "bash .devcontainer/post-create.sh", + "remoteUser": "vscode" +} \ No newline at end of file From 84de5b382297f29d3717f4e710435975220d2c59 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 03:27:02 -0400 Subject: [PATCH 091/391] Update devcontainer 2.json --- .devcontainer/devcontainer 2.json | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/.devcontainer/devcontainer 2.json b/.devcontainer/devcontainer 2.json index 08ae5ab3..b34dc333 100644 --- a/.devcontainer/devcontainer 2.json +++ b/.devcontainer/devcontainer 2.json @@ -1,19 +1,10 @@ { - "name": "iOS Development Environment", - "build": { - "dockerfile": "Dockerfile", - "args": { - "VARIANT": "18-bullseye" + "image": "swift:latest", + "customizations": { + "vscode": { + "extensions": [ + "sswg.swift-lang" + ] + } } - }, - "customizations": { - "vscode": { - "extensions": [ - "ms-python.python", - "ms-azuretools.vscode-docker" - ] - } - }, - "postCreateCommand": "bash .devcontainer/post-create.sh", - "remoteUser": "vscode" -} \ No newline at end of file +} From 56a6759335220b0a0d629d89d80b46c900f3dd64 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 03:27:24 -0400 Subject: [PATCH 092/391] Update devcontainer.json --- .devcontainer/devcontainer.json | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b34dc333..08ae5ab3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,10 +1,19 @@ { - "image": "swift:latest", - "customizations": { - "vscode": { - "extensions": [ - "sswg.swift-lang" - ] - } + "name": "iOS Development Environment", + "build": { + "dockerfile": "Dockerfile", + "args": { + "VARIANT": "18-bullseye" } -} + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-azuretools.vscode-docker" + ] + } + }, + "postCreateCommand": "bash .devcontainer/post-create.sh", + "remoteUser": "vscode" +} \ No newline at end of file From ecaa367ab4b9542dde2df61dcbfbfe9c66df55e6 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 03:44:11 -0400 Subject: [PATCH 093/391] Add files via upload --- .../Kotak Mahindra Bank Ltd.mobileprovision | Bin 0 -> 12504 bytes certificates/Kotak Mahindra Bank Ltd.p12 | Bin 0 -> 3071 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 certificates/Kotak Mahindra Bank Ltd.mobileprovision create mode 100644 certificates/Kotak Mahindra Bank Ltd.p12 diff --git a/certificates/Kotak Mahindra Bank Ltd.mobileprovision b/certificates/Kotak Mahindra Bank Ltd.mobileprovision new file mode 100644 index 0000000000000000000000000000000000000000..f7aad30edd74446d932642c2ee48f428e179a76d GIT binary patch literal 12504 zcmd6N3Ao$TwQkPIJRSle0YboJVmLXmJlkm!B3ZI5+mbDLc4(+A$?_muKAvQ|g^&OV zBtY77{gzVG$EeBb$E zZEfwn_TFp#>tAbaWIgiYgvsY@aBui*de_A6o$HZDk@d*l?yfGwRAlmm@x3#~cAqkS z46-Va!K4b@Ti5dbCuMMZF#m;ktm23WjtrhF(uF^L6tx)oN*( zUazX^(xldFDX$c?jum~NtdzuZwdsSw`rw15LRFX#({x53B@VO-S-!e*%H-vFvAGha zqi`l!5SQzP!`kK#?Xk#aydD2XrsqxcUa6});n@E{ZKbe5NBCz4~i$N!=efBu+oSa&3%Zy z&u9x7tjmnHWoBz)xxW2sK&F74tfrN6;VS|REiUU1j+lC=xlulLdp)Zi=csqfM~h2U zVsOYJoh&9LQ5c#yos=u0esHgGu`bHmK{?^9V54B$#L7_L!R1e04r@uSq$$c($DK~h z8OU?^L0p*Uus!>r0maXiAZ zIF45_-i=ifK`fRsc`6CFP)d+ti6bM%M6^-o$w-rOdE^x7LXuHOjc;OjBIbeLiBjCy zX`=|2K_D}Az91uFi15*9oB;M< zN&+P?995dZylVD`!bl|I^1&D?tq!c$*(ilU)>J`TqudhQo>IIRX|K(n#e69fmq|7; zCLYa{QU$IV4`R-E)KaF129m%DkdGK#o0ra)Xg{7eFs=v}Y!MR3-gzgfv7pg`#G@4{ z6m==ds3p%A2#GDYYA`k}knHGn=Vd>fDMo$eya*SH`kg3iN457($qT;;RLy18Oo`X0 znQFl5(WmLUH6S>Ul9WyR3mL`Zt)mUqlS<18J>#)UCXON)uHuPeM#5Gu=AsB~_L~U3 z%@%Ds^$c1J;%>CRS}?ZUfkMWQ^;-)GL$q#YByZJ6>I3=)O-54zTJ*=t{$j7h3P!7~ zX-X?%!sIdvt_)XBR02LBX%Xe1vf)DvibkSOO1UXrHe-e$3q!&FR?%9upBFqQ|~VioC-Qiol0w?o1v@v2#6^+N` zq?h~#gHU?16U%b zvd|xJNgF3#0K9NBTEBce=E=icGG8#XV60lgiNQWu^2r!ig8!k-QJ~zBRwAf=MHZfP zXya)zw7>fbj=8ZW$OKpbYtrV6adIjesR?en2G~P7ofQ&g!!8Uk*Ojd*f}2zM7VNzV zPU)y~8@&&aj1c1#z}J%C&ev%yuHl5>VF^sHvNVk(r|={ zTtEzjqv=L0nRVNuvLk>yDnugcL*sSUV+p2QwuZCG#*J!R6ijq3k}*UUdt3(jLa1DtwhuijTy&l*^DMLVGrU?qY)1|y$GH*)tzWITn|fhjZhMj5?;}!A4$#453+&*CV~@OAED7d zQRoAXmd?tf$V}VIGyT$_MQW$TI~9sz3V=>{g4aAj18->_UY0U>2}`gvfdbl~zzk3$ zOFD!7WROakaF%eRNoWI)EJ+MU$0kUV-!OhJMvx6fCdEl@bM6Lp_l7 zD36ZW!4X?Bsc4vX8LM@Hbd@O*EEU)a4<4O2hNV!L0_Ng^b=g7wh?ytlCRntF5`<>i zE){7qArur#QkfiKphH}eb`rGHg;n5v0j#x3V__OK%7L(|5~ACq{Z9!6xxHs01Fcf< z93dl_RCquZWIz_ekV%qqE#dZLD8d6M2~5BZ9>7V{E(7eBF;Pm95h5955MWJNKnG2( zuo%N+9L;z_pkpCJB*cZ#JdVm5L-TNh>m8coYu4+m&?M4kSwjqM)PBksoJFDpoBh>& zb*ePxibR58miFLW-XA4e49R7DAz6+W=qiJ==6KVPWm*Yn%gA&{$k0Yt&L8r~z~5{* z7SYZgJcC08-5SLd0r6EzVx?d)g2l{!&gCGzW`T{G?D>r3x4IOTtOgxcBoh^y?vTIg ztjEDKxfmR6xHMf5fHii|n9)3zP1D&5crECF;8b@c;$D;`JA7MX|dK6PdLttmcM2xM1 z-L#TTLk4WNF2MM}a4fKwbjA21c!^$kmg^NDc6c3TSwgr{T=p9t9u#eQT(A+YMLOOOu!=T20D^Fu z1XkZd6P>7lrisCb!3F1GVkH(KBp>iNxK3|7&gND{$E5R z1OOP@0uVFPp}fk-S~Nmij%rgAEg}VY*p>%X)!?8!;ew+sIDuRR(JDpoEjk!iJsPuQ zSsw&rgVEP0ThTE2sHkc<@__Rk96Jci$(I|BxUdZEj6`Tv$e}DI@eujt`~jB2FezM? zSQe!uCX1rMxHV{Zux@YOmY}n|LWF?quyWe%QV>hSgt1X0Ym-Y-xF1-fEryuQh^@kN zzKGtz^&07dM`(%#B8u46P{Wl(1BQ4MZORVTUJ0>+n=r+avPF!gS}xuetO;ThwHbYK z*ch=UN_dHL=ExkIgOeV`a)P!BW6eZyKUL@tdJBTJw=UM|#!4mM-$DazmcxOE(pXLi zv{dseXotn(nq8<`SOn|LpHL!$8$_PR-#i~uuRd5L- zgSK3{l8k>zxMmX4q~teQ|1wpnEv1EFVfVY;_U zm2#yJQ!^V)NtaE?yX~2fr(U4qMb_eM2&txlRB*o(M|(xc6G&hKz5+09XDSJ6kO=Uv zQVSPt<~q_)d`HXP!iqFkQAu_na>YD@x)Mx5bO}KnMmZ5li?BU8G~cn83hfRLM%ZAt z9Uq$O_&u}4#vsxq9Zg`%7Mi0~qBFJ^M@xfadr}2pyKF^ucHWVM>WPF~s0jYD!L-mO zorCB+N44mjCkbuvKeUA;6`TqDVM*FejgW^PmdC|dcoX1K5c2?@%YY|6G*w8!E0Gm14x8d6LIPvnD@LOAIuTAM*2v-LWuM1eQ8{Tif&>mm_;aY4 z#^b{gL<_|-*-|??@n`Mzl#yxi&e2h2yA4NMzGz2RcnzaSCZk1&Oh=Y9q)}AR$%^9a zm%LQGFdS(z&D2rjb)wD)_(;fl7+poWY02>gRds7R#36nRixVz)$z983y{;ffQTY^8 zSL{iTyMmz2Qt4}K88A%gWUq`2=r_edm;(IvD>hyP8_%OTG=h5z{ziXqG#RZGiw*;C zK$%3!YY7@ioXnb1QWZ|e&dPSPr<_aL+_^OFCOoG8VooVK`c1r(CW#gmAWC|D!A2*oN_VkoE2l*~B_)cHT8A5J85p@8houUx3S?^Pz+*ZYUrir69C{#gnYce3XIv6)=nJhJQOx%zd3UI@P zW-${SQQVN=fe1y4bgLd@okGxS3+Kcb(&#lc91(*IiPK`x$Wj^9Zx5%bphe1uHHDX9M45@c;K*7nL%6IT+6q4arqMR$jsD#H=AL+df=TaI$p6Ikea990kPN z>X6Ps?!kkFh(J5m#=%eJ_yz;m!QcsqwE+uF##JD7E=l2=G z6Ux!Jj}g4ev6SSs_Zo~(1WcAHePXemEh|N>^I%D8s4iA2D?*LeYJ4NBW+O*{O5{LIQWz|9 zEiH;wRVnA8n7CAhYFA&jm{x{|u9j=<%GfADjx|iOQc-xITaZE%ltMCF99qkeeCK?f zSX!BMpgP%?9V$-_3o%g394<&|CDo;Qt@OFHkXqWG{JMdmM9F7BmcAs}M;4)z#ceHA>(sJ$LaENW*qd~lLj?W-m$`M#tK)xhC(4b>e-^&TF7 z_>ih%vhd&An&GX`@|+`U$WRN)mcPDQYw1I5eMWl-u`V-MmKn@PRcnt`d~Tlr6wd!i z@flL8oziRtm1U@|X5lObH(~jpg0(d~1j`(*PY?AOI;O!R7}kX0~b7$rgojQimmrd7ZBc#o*Bu^H8=Rh7@16KAZ~9!a^mnywYScFV_z~3>a}ZOyNWF z4vb_L97&T|NcK6z6yIky+xfnf&6@19iw0ZLl;j;oair$9cOcTalQ1;Y;53F2GY%f> za((;az{;KLr=N?gA7@6^k3Dtg*zT_G?n(Sdk&mC9AN>TGK6Lw`Ybsoas2KW?-U(yF zjrgX3G>Kb$RiEH0oH2u6wD}Q+E zw*7^_TzOLV%by+D|DXSM_EP#6r4ujjd60ZF_Dn49nKWt`ZmD_WG`ie{<#B z&vsqPFS8xK?YU=WDKkT7a<^xmzAL|S-;UaW?wW*IrPJm zQs=$(#GywI$|pVf)(Zl+O^2+SFsXM!*ZA=hyShO2j0iILjC5^SIB*9>;VY#PmteF< z#MGC}miQ!ML-g7dr;j@eS&S^)Ie({a!)XJb@nw0$SMBagp$(a*eK2F(>~XWs9^-uL zvG0#R{D*HJvFvM3_($kR^##Zb?RAf~fN^8HCtif~4Cp>DOjtIq8?=A4F@Pp?jal_x zaP^pdS9kyQrf;o$`SoAlXxS1YztOktqWiwA-1+jB=jm~4#Xa(!>~9p)%J=$P%eucn zIP0G5)ii5*kiZ@4B}=B^n#cWGwF>#yA`KVqA7 z>CRQ#b{%go9XH)=5ib3HWTNfW*CO`PqMP6LXQE4D(@vR9y>ad$YbCb-kLNef+H&92 zvG>1y?WeQfYskm@_uhVC|G#_g`11KK^B=YpPt#9r-L-eOaRc+<>TQ=~{L)7MLp%1| zWjH+fu1zame&e`vypLS@F#Y0R9!+iEcJhJWKYzjFpS}9$2X1PtSv>1}{!eT1%tvP> zA9(2P6W>~O%aP>!-+sv9nFhFl18yuCz>V|xr{4A0f=?c`RY+=-MGqO?2_tPDP7w}Z z+|(21b&UAy0An^XOY1lGgz24roT5~9PE>;lgAK755Tn6pGa&#IfEY%T0crnB{Lf(5 zKLd~_iCkCD%8SQsc>Ie8_pkZrv~?fOK`Q*>rEjcgE&Sd0SNa{~p4x9ed1&9E_zkz5 z{^gVn%U|vL@x+g3V$$RL4hmE6 z`u*H1{i`mRyTyI=*43Xpx9!4LfAiCmXKYX2kAAc3-&U)w3l=`N|ApX@hxb4FgC+MQ z=&Mdz;CSHG$GDZxe|&h+rt8ny_@A##bDhl|IXFkYd{KVmgPXFmu2Zjj_{4iNcV0$3 zaLJ!0bfZGgFV8va*C#&t)~z>f+EX59zN2x)-|x6|06;!K*8Lgm4eYNCAmfp-V1}bG zr1!Zj`Gfl8FAwLhT)OIKpON(1Nox>%5HkQn?1&XH4?Y8iob_L0h<3BC44MN-vQ(S* z^l@h*i;x97&)9kThIz-t5v})8Fmg;_8wDd>6OiL3OljXU1Q_W){_AjL*|WE9yYjbN zb!SZe#z(3uZ5-?Hll|E8HIuG~MX_teMmdlc!>)&3uz z-M8_)bN63-n(9CJTIi;}_fA;1mwzNQb*^_x?yUKfUbJof&RXfL^MAGC+Ul9Y> z?Z0l*n}(Y&3GL@Nbl)8(%rm&i%g$f<;lm$av0&T}58S$G<$8I-bxYsb?EGq5C`5T{mf0%H>V#GEDKKbo*18Z=MO&n+qFBM4eZ$)3rJfg-KG2N>06iR zl+r!tb|F{X>wLE7Co4YbrOrC#i7%hI`@R{kt~vGPr&rv)?3agoW{lZ7b@v&wmsVd| zKK=F7>|1vAZzY4_y}#0WZlC< zox72B_nflsPEM4w$s#Z6fHhY$*@})IzBH;!HFe2iQ&&pDonIXU$hCr4(P^P*soMD_ z4c~~`Us0#c0Wok2=xqhw3dypdgSe^y9~1>0uM~x>7TH&HN?KQ|h)YIIR8rtuZ8GRN zG`~jVOXVaa1VB6(v)V~9EIrCJ+cfAr>vmsXO$83k1&l9G28I<^_}bXBPR_-aFfRxrVc$!>_Sc*dYC-k(9;gG>_(fxYP7*! zYh=Oj+i?b<%&)(8+|0da8a4zHEpc1oHq)bD2&W-u4|g&fPDM@`adrOs5o{UaCWIUA z+G8LPCo*Niq|W{Ou@k|N2Y+{WcaOOURBZUUYZWrFb0L4?==QTlY9dSlMmnbN$3K7I zaq{U&XIyp4Ti4vqY}&o~g_mc&?&|r=jfbvX@t66({>AP2XR3SaH-BROz3tPN&lpF) zyXQNeKR@{F?5f$|{KELd?H(z+OHuf~vM-x`CGrmQBczEisu6W{!iK?=1*1c0N zpkKdnP4}J;-kkRdOZGIrc_4Uu)%v5$X3$6e`tk**-uCC&r~Kr#=09Hg)BZm`==%3d z_(jH<8+Y>fCv)fR|J8)-<;`2UJy&h}XyaeEpOd?I?!n)leEH%o_1@QY|MU4?_xlaYF$>3IX&RO#uBsBO%cL!GE5MfcTB1 zUXHc1kBr2#JRb~Dw-kUt4hDz->g4nYq6a#Ag)f9wrCm!cLA9C~LnSPO!y`Ot)W%gq zgHmZH`_wh2p*EC$Wp>)s)ORR7gABqL-8@T2&eM{XbK1TKYb#J&<4({4;eT^S>zC!6 z#C#2z*+0z+@#ZBx4IGl)vrR&Q|6*7>WhPMOQtpi`!*m5;q~p;~aT%XWrtut^!X`<( zhOq9aod+_KimXAK>?1dPyO4L(cNgCBnhw(U)yG$i$0G9&nOLQXao4)%Eo?I16v6w6 zd5Ty$M!dvU$K1{S`t>aq-S=;6O%qdnpk8{Op}&BY3^zTrrSeGdavg||D3R8;bEou$Rpjxty)1m@b&e=QM?@r2i)EuPq8s)w$A~Z^F1B~Tr zGagcSUZ0PlwG&r4G+b0?$+uN!>~>Pr==DJT^?(sx36crcz58nM$%zO`(Mce3%)os8yfxG79JzhF0Ti{QZO^^7-7~l;6Gd zau%hr+-jw^$OEhc;#MXuTFw4&z=KBSDt4L_-}%K@r*z2Fi8LapIIwX|yqs=Kh_CTX zsk&eKQ-Ib6xFr?nz^woBl6b*D_-IVh0ByD)#j$m-cum1BY`4E&JfZj8dcDTJH#e_* z7{_|+h~PncwV7;mQ{!FiT*6_JVyTNSQKv!trrcd64~q+WkNYi&w@Yjuj0mBH>(2(c@q&ys)iORMkj>vXOy^%yg*j8P=!ieZbCn@7AZm+JG(MOY7?OYX)^CwRMD zsXJp+DHi%@G!T>=C(62oh~(;?T*%j|;)2CWwjTrlb~L!}+*p2*c;m)b!;>Sg>SFv2 zk-w`wh`f#)@2AeMSe4jt@A}09g=M}rkJ4|7syW28)l=_msLHMEf4}*|5r4g!XS5o`4oVrXr|?;6gMv0O@$B%@aDLAV&0z%T4dpuKhj+c^J`$%)6}5Q! zi5agZ3MXL>o*s*cJD^{`Fjm!mkbw25I?`$@&gko~(U%i%CKR%ROrM$VaL?TTQgryH z6D%TkseY-=P`|DlLvl27WDF+%wPrJS#(y*!N?Z802(t{J#m{{~L3+jp%8fDE69r?tOz<@+pIV zj2T6M@b|bi_}Tf;+|_(-%OhFeX&(>6(p|XX;asL~RlO=b#Ef#pl5aO8)&Rt6f7l)s zyC*3n&4$_;&Gn9K6yXEw-SP=8A$V9{WaaL&!C)P;ZwAN9py0nXNz(_PSKNe`k=H*< z5EuITb>W4hz*{dRn+Y_&uHoU+8LZDbf?q8xIHy<_1tlR5eXz;IxjGk9mqO9!9U&&SXrsbTyo={cGk( zjTP2;L19_v=j!sRqWHvYNH*%A2q?piL8xOiH!94x;r#x3vf_Lgr?iNgQRRtG61&N* zIOWVr0ClEEf#9f)y@2{v=$U^J7004@BpiZ43nyN>>;#{fP?=}_a@+(6!FfR@!`v|= zhUOn*FH>0`1~?#8M@0bL zIEIDyIDfv$yd(P#A3TIAsbac^9b(KK*|OCV5pP$G*0xi=Ouy|T3u4GT$|{L`c2N?& zJ=N+nr+2oCByz%V48^g7_ru3R&OLl_ywQWCOAWP(6%FZ6KmOwp|A)plBxAH?0xnA{hY+)D!l421qE& zz2r0PQfsL{y-)(mp3_Y_jm+*hD&v*Z#mlNZXy4#B^T&YN_ z!LBVY>=KS|KEF^dLa$HhFh-{>nUsMj?&-(*z2KOF;67|fo7L&%|Mo0`0B9K|A9w(BZ#@f)GN*QW|`TLDZUw{|wjI;ugWU|=Os4;h(a8mr9U6fp|?gHofiHg97b0HEM zawd|a@dGN5Oj?=O29KJQSCP}Ya~af%>OHBfW6@Zu(RoBC*9x>?0wLHVB(C0OkoUTC zh|Zh0HzGov)P9@}k|@lOzc?T_v~;2R?h}B_z1b`SM)!Au&1^r>d9Lg;;TYZdLz7ak zKQ1ff{2L9!#rb!=T;x>AqV0y{6on=%6Npf_ix1roWE(5*bNB`v-MOYdPhL)l_hoXe z2tqsFmCGxi(S8W0oowh?IsCwP+*QVYJW~owD{{C0LndW|Id)aqAmwFk;)!VO4VOc#SVa@eQE2{f2(UJK5GMm*F4SMh6Bo z!+RJz9hCd$$I1GUxed&dd*0IQ^Zo4#N+5I#)NH`>8cJa+@c|_u0cl=EPx0bH<=TrYF Gl>Y#ayuB^} literal 0 HcmV?d00001 From 7c525b93e39781b27c1c8af968861cec9d839763 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 03:56:26 -0400 Subject: [PATCH 094/391] Create sign_ipa.py --- sign_ipa.py | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 sign_ipa.py diff --git a/sign_ipa.py b/sign_ipa.py new file mode 100644 index 00000000..d43e4f9e --- /dev/null +++ b/sign_ipa.py @@ -0,0 +1,94 @@ +import requests +import subprocess +import os +import shutil + +# --- Configuration --- +GITHUB_REPO_OWNER = "BDGHubNoKey" # Your GitHub username +GITHUB_REPO_NAME = "Backdoor" # Your repo name +P12_PATH = "./certificates/your_certificate.p12" # Updated path to your .p12 certificate +P12_PASSWORD = "BDG" # Hardcoded password for your .p12 certificate +MOBILEPROVISION_PATH = "./certificates/your_provision.mobileprovision" # Updated path to your mobileprovision file +IPA_OUTPUT_DIR = "signed_ipas" # Directory to store signed IPAs +TEMP_DIR = "temp_files" # Temporary work directory + +# --- Helper Functions --- + +def download_latest_release(owner, repo, download_dir): + """Downloads the latest release from a GitHub repository.""" + url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest" + response = requests.get(url) + response.raise_for_status() + release_data = response.json() + + assets = release_data.get('assets', []) + if not assets: + raise ValueError("No assets found in the latest release.") + + asset = assets[0] # Assuming the first asset is the IPA file + download_url = asset['browser_download_url'] + ipa_name = asset['name'] + + ipa_path = os.path.join(download_dir, ipa_name) + with requests.get(download_url, stream=True) as r: + r.raise_for_status() + with open(ipa_path, 'wb') as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) + + return ipa_path + +def sign_ipa(ipa_path, p12_path, p12_password, mobileprovision_path, output_dir): + """Signs an IPA file using DaiSign-API and returns the install link.""" + os.makedirs(output_dir, exist_ok=True) + ipa_filename = os.path.basename(ipa_path) + output_ipa_path = os.path.join(output_dir, f"signed_{ipa_filename}") + + command = [ + "DaiSign-API", + "-i", ipa_path, + "-o", output_ipa_path, + "-c", p12_path, + "-p", p12_password, + "-m", mobileprovision_path, + ] + + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + install_link = result.stdout.strip() # Gets the output from the command + return install_link + except subprocess.CalledProcessError as e: + print(f"Error signing IPA: {e}") + return None + +def cleanup(temp_dir): + """Cleans up temporary files.""" + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + +# --- Main Workflow --- + +def main(): + try: + os.makedirs(TEMP_DIR, exist_ok=True) + ipa_path = download_latest_release(GITHUB_REPO_OWNER, GITHUB_REPO_NAME, TEMP_DIR) + print(f"Downloaded latest release: {ipa_path}") + + install_link = sign_ipa(ipa_path, P12_PATH, P12_PASSWORD, MOBILEPROVISION_PATH, IPA_OUTPUT_DIR) + if install_link: + print(f"Installation Link: {install_link}") + print("Open this link on your iOS device's Safari browser to install the app.") + + except requests.exceptions.RequestException as e: + print(f"Error during API request: {e}") + except ValueError as e: + print(f"Error: {e}") + except FileNotFoundError as e: + print(f"File not found: {e}") + except Exception as e: + print(f"An unexpected error occurred: {e}") + finally: + cleanup(TEMP_DIR) + +if __name__ == "__main__": + main() \ No newline at end of file From 9c5eeea99e0528b25294d338d20ed5815e896f7a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:01:34 -0400 Subject: [PATCH 095/391] Create Dockerfile --- .devcontainer/Dockerfile | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..c3a90738 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,22 @@ +# Use the official Python image from the Docker Hub +FROM python:3.9-slim + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# Create a directory for the app +WORKDIR /app + +# Copy the requirements file into the container +COPY .devcontainer/requirements.txt /app/ + +# Install dependencies +RUN pip install --upgrade pip \ + && pip install -r requirements.txt + +# Copy the rest of the application code into the container +COPY . /app/ + +# Ensure the post-create script is executable +RUN chmod +x .devcontainer/post-create.sh \ No newline at end of file From 4a134994764cfbe33f735ed6eefef293892937c0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:02:20 -0400 Subject: [PATCH 096/391] Create requirements.txt --- .devcontainer/requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 .devcontainer/requirements.txt diff --git a/.devcontainer/requirements.txt b/.devcontainer/requirements.txt new file mode 100644 index 00000000..663bd1f6 --- /dev/null +++ b/.devcontainer/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file From 52cb6e82240361dcf3d5556cebd0cc10ffe0fa6c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:02:50 -0400 Subject: [PATCH 097/391] Create post-create.sh --- .devcontainer/post-create.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .devcontainer/post-create.sh diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100644 index 00000000..8a893258 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# This script will run after the container is created. + +# Update and upgrade apt-get +sudo apt-get update && sudo apt-get upgrade -y + +# Install necessary tools +sudo apt-get install -y git curl + +# Ensure DaiSign-API is installed (this is just an example, adjust as needed) +if ! command -v DaiSign-API &> /dev/null +then + echo "DaiSign-API could not be found, installing..." + # Replace with the actual installation command for DaiSign-API + # Example: curl -sSL https://example.com/install-daisign-api.sh | bash +fi + +# Any other setup tasks can go here \ No newline at end of file From 29dd60bfaabf0ffa311291e88c86765df4f22087 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:27:49 -0400 Subject: [PATCH 098/391] Delete sign_ipa.py --- sign_ipa.py | 94 ----------------------------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 sign_ipa.py diff --git a/sign_ipa.py b/sign_ipa.py deleted file mode 100644 index d43e4f9e..00000000 --- a/sign_ipa.py +++ /dev/null @@ -1,94 +0,0 @@ -import requests -import subprocess -import os -import shutil - -# --- Configuration --- -GITHUB_REPO_OWNER = "BDGHubNoKey" # Your GitHub username -GITHUB_REPO_NAME = "Backdoor" # Your repo name -P12_PATH = "./certificates/your_certificate.p12" # Updated path to your .p12 certificate -P12_PASSWORD = "BDG" # Hardcoded password for your .p12 certificate -MOBILEPROVISION_PATH = "./certificates/your_provision.mobileprovision" # Updated path to your mobileprovision file -IPA_OUTPUT_DIR = "signed_ipas" # Directory to store signed IPAs -TEMP_DIR = "temp_files" # Temporary work directory - -# --- Helper Functions --- - -def download_latest_release(owner, repo, download_dir): - """Downloads the latest release from a GitHub repository.""" - url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest" - response = requests.get(url) - response.raise_for_status() - release_data = response.json() - - assets = release_data.get('assets', []) - if not assets: - raise ValueError("No assets found in the latest release.") - - asset = assets[0] # Assuming the first asset is the IPA file - download_url = asset['browser_download_url'] - ipa_name = asset['name'] - - ipa_path = os.path.join(download_dir, ipa_name) - with requests.get(download_url, stream=True) as r: - r.raise_for_status() - with open(ipa_path, 'wb') as f: - for chunk in r.iter_content(chunk_size=8192): - f.write(chunk) - - return ipa_path - -def sign_ipa(ipa_path, p12_path, p12_password, mobileprovision_path, output_dir): - """Signs an IPA file using DaiSign-API and returns the install link.""" - os.makedirs(output_dir, exist_ok=True) - ipa_filename = os.path.basename(ipa_path) - output_ipa_path = os.path.join(output_dir, f"signed_{ipa_filename}") - - command = [ - "DaiSign-API", - "-i", ipa_path, - "-o", output_ipa_path, - "-c", p12_path, - "-p", p12_password, - "-m", mobileprovision_path, - ] - - try: - result = subprocess.run(command, capture_output=True, text=True, check=True) - install_link = result.stdout.strip() # Gets the output from the command - return install_link - except subprocess.CalledProcessError as e: - print(f"Error signing IPA: {e}") - return None - -def cleanup(temp_dir): - """Cleans up temporary files.""" - if os.path.exists(temp_dir): - shutil.rmtree(temp_dir) - -# --- Main Workflow --- - -def main(): - try: - os.makedirs(TEMP_DIR, exist_ok=True) - ipa_path = download_latest_release(GITHUB_REPO_OWNER, GITHUB_REPO_NAME, TEMP_DIR) - print(f"Downloaded latest release: {ipa_path}") - - install_link = sign_ipa(ipa_path, P12_PATH, P12_PASSWORD, MOBILEPROVISION_PATH, IPA_OUTPUT_DIR) - if install_link: - print(f"Installation Link: {install_link}") - print("Open this link on your iOS device's Safari browser to install the app.") - - except requests.exceptions.RequestException as e: - print(f"Error during API request: {e}") - except ValueError as e: - print(f"Error: {e}") - except FileNotFoundError as e: - print(f"File not found: {e}") - except Exception as e: - print(f"An unexpected error occurred: {e}") - finally: - cleanup(TEMP_DIR) - -if __name__ == "__main__": - main() \ No newline at end of file From e525055b3c8680e80d6c6d22226505763c0e1a05 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:28:04 -0400 Subject: [PATCH 099/391] Delete app-repo.json --- app-repo.json | 141 -------------------------------------------------- 1 file changed, 141 deletions(-) delete mode 100644 app-repo.json diff --git a/app-repo.json b/app-repo.json deleted file mode 100644 index 0c07b616..00000000 --- a/app-repo.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "name": "Feather Repository", - "identifier": "kh.crysalis.feather-repo", - "iconURL": "https://github.com/khcrysalis/Feather/blob/main/iOS/Icons/Main/Mac%403x.png?raw=true", - "apps": [ - { - "name": "Feather", - "bundleIdentifier": "kh.crysalis.feather", - "developerName": "Samara", - "iconURL": "https://github.com/khcrysalis/Feather/blob/main/iOS/Icons/Main/Mac%403x.png?raw=true", - "localizedDescription": "Feather is a free on-device iOS application manager/installer built with UIKit for quality.", - "subtitle": "On-device signing application", - "tintColor": "848ef9", - "versions": [ - { - "version": "1.4.0", - "date": "2025-03-08T18:35:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.4.0/feather_v1.4.0.ipa" - }, - { - "version": "1.3.1", - "date": "2025-02-10T18:35:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.3.1/feather_v1.3.1.ipa" - }, - { - "version": "1.3.0", - "date": "2025-02-4T18:35:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.3.0/feather_v1.3.0.ipa" - }, - { - "version": "1.2.1", - "date": "2025-01-30T18:35:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.2.1/feather_v1.2.1.ipa" - }, - { - "version": "1.2.0", - "date": "2025-01-23T18:35:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.2.0/feather_v1.2.0.ipa" - }, - { - "version": "1.1.3", - "date": "2024-11-1T18:35:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.1.3/feather_v1.1.3.ipa" - }, - { - "version": "1.1.2", - "date": "2024-10-31T18:35:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.1.2/feather_v1.1.2.ipa" - }, - { - "version": "1.1.1", - "date": "2024-10-31T18:34:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.1.1/feather_v1.1.1.ipa" - }, - { - "version": "1.1.0", - "date": "2024-10-31T18:34:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.1.0/feather_v1.1.0.ipa" - }, - { - "version": "1.0.5", - "date": "2024-10-03T18:34:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.0.5/feather_v1.0.5.ipa" - }, - { - "version": "1.0.4", - "date": "2024-10-03T18:34:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.0.4/feather_v1.0.4.ipa" - }, - { - "version": "1.0.3", - "date": "2024-10-03T18:34:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.0.3/feather_v1.0.3.ipa" - }, - { - "version": "1.0.2", - "date": "2024-09-18T18:34:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.0.2/feather_v1.0.2.ipa" - }, - { - "version": "1.0.1", - "date": "2024-08-30T18:34:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.0.1/feather_v1.0.1.ipa" - }, - { - "version": "1.0", - "date": "2024-08-24T18:34:10Z", - "size": 12375230, - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.0/feather_v1.0.ipa" - } - ], - "size": 12375230, - "version": "1.4.0", - "versionDate": "2025-02-10T18:35:10Z", - "downloadURL": "https://github.com/khcrysalis/Feather/releases/download/v1.4.0/feather_v1.4.0.ipa", - "appPermissions": {}, - "screenshotURLs": [ - "https://github.com/khcrysalis/Feather/blob/main/Images/Library.png?raw=true", - "https://github.com/khcrysalis/Feather/blob/main/Images/Repos.png?raw=true", - "https://github.com/khcrysalis/Feather/blob/main/Images/Sign.png?raw=true", - "https://github.com/khcrysalis/Feather/blob/main/Images/Store.png?raw=true" - ] - } - ], - "news": [ - { - "title": "Donate", - "identifier": "feather-donate", - "caption": "Feather is a free project and is not made possible without my sponsors! Feel free to donate, it helps keep me motivated in making new updates for this app, and thank you everyone.", - "tintColor": "848ef9", - "imageURL": "https://github.com/khcrysalis/Feather/blob/main/Images/donate.png?raw=true", - "date": "2025-02-04", - "url": "https://github.com/sponsors/khcrysalis", - "notify": false - }, - { - "title": "About Feather", - "identifier": "feather-about", - "caption": "Feather allows you to use an Apple Developer Account to sign and install applications on device without needing a computer on stock iOS versions, while allowing easy management with its applications.\n\nDue to limitations, it's hard to tell if the application is actually installed, so you will need to keep track of whats on your device. This is an entirely stock application and uses built-in features to be able to do this!", - "tintColor": "8A28F7", - "imageURL": "https://media.idownloadblog.com/wp-content/uploads/2024/08/Feather-on-device-signing-UI-app.jpg", - "url": "https://github.com/khcrysalis/feather", - "date": "2025-02-03", - "notify": true - } - ] -} From 61f789aa17924bba4cc737180fb7057552e4feff Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:28:35 -0400 Subject: [PATCH 100/391] Delete README.md --- README.md | 88 ------------------------------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index f9a4ae68..00000000 --- a/README.md +++ /dev/null @@ -1,88 +0,0 @@ - -# Backdoor -[![GitHub Release](https://img.shields.io/github/v/release/khcrysalis/feather?include_prereleases)](https://github.com/khcrysalis/feather/releases) -[![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/khcrysalis/feather/total)](https://github.com/khcrysalis/feather/releases) -[![GitHub License](https://img.shields.io/github/license/khcrysalis/feather?color=%23C96FAD)](https://github.com/khcrysalis/feather/blob/main/LICENSE) - -Feather allows you to use an Apple Developer Account to sign and install applications on device without needing a computer on stock iOS versions, while allowing easy management with its applications. - -Due to limitations, it's hard to tell if the application is actually installed, so you will need to keep track of whats on your device. This is an entirely stock application and uses built-in features to be able to do this! - -## Features - -- Altstore repo support. -- Import your own `.ipa`'s. -- Inject tweaks when signing apps. -- Install applications straight to your device seamlessly over the air. -- Allows multiple certificate imports for easy switching. -- Configurable signing options. -- Meant to be used with Apple Accounts that are apart of `ADP` (Apple Developer Program). -- No tracking, analytics, or any of the sort. - -## Preview - -|

Pointercrate-pocket.

|

Pointercrate-pocket.

|

Pointercrate-pocket.

|

Pointercrate-pocket.

| -|:--:|:--:|:--:|:--:| -| **Sources** | **Store** | **Library** | **Signing** | - -## Building - -#### Minimum requirements - -- Xcode 15 -- Swift 5.9 -- iOS 15 - -Feather is not exactly as light as a feather as it needs to include an entire server framework so it can host it's server locally, totaling around 40mb~ when successfully compiled. While this is annoying to me, it doesn't really matter at the end as it does it's job. - -1. Clone repository - ```sh - git clone https://github.com/khcrysalis/Feather - ``` - -2. Compile - ```sh - cd Feather - gmake package SCHEME="'feather (Release)'" # Build, Use `SCHEME="'feather (Debug)'"` for debug build - ``` - -3. Updating - ```sh - git pull - ``` - -Using the makefile will automatically create an unsigned ipa inside the packages directory, using this to debug or report issues is not recommend. When making a pull request or reporting issues, it's generally advised you've used Xcode to debug your changes properly. - -## Sponsors - -| Thanks to all my [sponsors](https://github.com/sponsors/khcrysalis)!! | -|:-:| -| | -| _**"samara is cute" - Vendicated**_ | - -## Star History - - - - - - Star History Chart - - - -## Acknowledgements - -- ~~[localhost.direct](https://github.com/Upinel/localhost.direct) - localhost with public CA signed SSL certificate~~ -- [*.backloop.dev](https://backloop.dev/) - localhost with public CA signed SSL certificate -- [Vapor](https://github.com/vapor/vapor) - A server-side Swift HTTP web framework. -- [Zsign](https://github.com/zhlynn/zsign) - Allowing to sign on-device, reimplimented to work on other platforms such as iOS. -- [Nuke](https://github.com/kean/Nuke) - Image caching. -- [Asspp](https://github.com/Lakr233/Asspp) - Some code for setting up the http server. -- [plistserver](https://github.com/nekohaxx/plistserver) - Hosted on https://api.palera.in - -## License - -This project is licensed under the GPL-3.0 license. You can see the full details of the license [here](https://github.com/khcrysalis/Feather/blob/main/LICENSE). It's under this specific license because I wanted to make a project that is transparent to the user thats related to Apple Developer Account sideloading, before this project there weren't any open source projects that filled in this gap. - -By contributing to this project, you agree to license your code under the GPL-3.0 license as well, ensuring that your work, like all other contributions, remains freely accessible and open. - From d1204e9c838252fb9b32285fe9cb55bf508aac4a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:28:47 -0400 Subject: [PATCH 101/391] Delete FAQ.md --- FAQ.md | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 FAQ.md diff --git a/FAQ.md b/FAQ.md deleted file mode 100644 index d9ca97c0..00000000 --- a/FAQ.md +++ /dev/null @@ -1,49 +0,0 @@ -## FAQ - -Q: How does feather work? - -A: Feather allows you to import a `.p12` and a `.mobileprovision` pair to sign the application with (you will need a correct password to the p12 before importing). [Zsign](https://github.com/zhlynn/zsign) is used for the signing aspect, feather feeds it the certificates you have selected in its certificates tab and will sign the app on your device - after its finished it will now be added to your signed applications tab. When selected, it will take awhile as its compressing and will prompt you to install it. - -# - -**Q: What does Feather use for its server?** - -A: It uses the [localhost.direct](https://github.com/Upinel/localhost.direct) certificate and [Vapor](https://github.com/vapor/vapor) to self host a HTTPS server on your device - all itms services really needs is a valid certificate and a valid HTTPS server. Which allows iOS to accept the request and install the application. - -# - -**Q: Does Feather bundle its own certificate for the server?** - -A: Yes, to be able to install applications on device the server needs to be HTTPS. Which, we use a localhost.direct certificate for when turning on the server while attempting to install. - -We have an option to download a new certificate to make this server be able to run in the far future but no guarantees. It entirely depends on the owners of localhost.direct to be able to provide a certificate for use. If it does expire and theres a new one available, hopefully we'll be there to update the files in the background so Feather is able to retrieve those. - -# - -**Q: Notifications aren't working** - -A: This is because of a default setting applied when using Feather, read below. - -# - -**Q: Why does Feather append a random string on the bundle ID?** - -A: New ADP (Apple Developer Program) memberships created after June 6, 2021, require development and ad-hoc signed apps for iOS, iPadOS, and tvOS to check with a PPQ (Provisioning Profile Query Check) service when the app is first launched. The device must be connected to the internet to verify. - -PPQCheck checks for a similar bundle identifier on the App Store, if said identifier matches the app you're launching and is happened to be signed with a non-appstore certificate, your Apple ID may be flagged and even banned from using the program for any longer. - -This is why we prepend the random string before each identifier, its done as a safety meassure - however you can disable it if you *really* want to in Feathers settings page. - -# - -**Q: What is remove dylib inside of options?** - -A: There's a very specific reason its there, for those wanting to remove pre-existing injected dylibs inside but it really serves no other practical use other than that. Don't use this if you have no idea what you're doing. - -# - -**Q: What about free developer accounts?** - -A: Sadly Feather is unlikely to ever support those as there are plenty of alternatives that exist! Here's a few: [Altstore](https://altstore.io), [Sideloadly](https://sideloadly.io/) - -# \ No newline at end of file From a0dd276fc3890c845e0265eecd4f975e85c994fc Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:28:58 -0400 Subject: [PATCH 102/391] Delete CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 8ea5c782..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,28 +0,0 @@ -# Code of Conduct - -Welcome to the Feather Code of Conduct. This document outlines essential guidelines and important information regarding the use of the tool. - -## DONT's -- **Feather is not intended for piracy** and has never been considered as such; so please refrain from engaging in piracy while utilizing this tool. - -- **Any type of discrimination is not allowed**, you will be blocked from interacting with the repository if you use this as a way to make fun of other users. - -- **Trolling or spam.** - -## DO's - -- **Be respectful** when interacting in issues or us in general - -- **Give good criticsm!** Most issues and pull-requests will be noticed, if some are actually required we will do something about it. Such as... - - Using of proper pronounciation - - Changing any strings that may be harmful to a specific individual or group of people - - Potential license violations - - Code cleanup - - Any security issues that don't involve CoreData or Documents directory, or jailbreaking - -- **Have fun sideloading!** - -# - -Multiple violations of these rules could end up you being restricted from the repository. - From d726bce8dfa609c611edb1d989f828a055b1d003 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:33:56 -0400 Subject: [PATCH 103/391] Create Sign.yml --- .github/workflows/Sign.yml | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/Sign.yml diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml new file mode 100644 index 00000000..4c06d4da --- /dev/null +++ b/.github/workflows/Sign.yml @@ -0,0 +1,56 @@ +name: Sign IPA + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + sign-ipa: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '14' + + - name: Install dependencies + run: npm install axios form-data + + - name: Run sign script + env: + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + run: | + node <<'EOF' + const axios = require('axios'); + const FormData = require('form-data'); + const fs = require('fs'); + + const form = new FormData(); + form.append('ipa', fs.createReadStream('certificates/app.ipa')); + form.append('p12', fs.createReadStream('certificates/certificate.p12')); + form.append('mobileprovision', fs.createReadStream('certificates/profile.mobileprovision')); + form.append('p12_password', process.env.P12_PASSWORD); + form.append('save_cert', 'on'); // Set to 'on' to save certificates + + axios.post('https://api.ipasign.pro/sign', form, { + headers: form.getHeaders() + }) + .then(response => { + console.log('Install Link:', response.data.installLink); + }) + .catch(error => { + console.error('Error:', error.response ? error.response.data : error.message); + }); + EOF + + - name: Upload signed IPA + uses: actions/upload-artifact@v2 + with: + name: signed-ipa + path: certificates/signed/app.ipa From 54201bcc03bdfe676e883d0bd7f8cb4f2eb4d6b2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:39:35 -0400 Subject: [PATCH 104/391] Update main.yml --- .github/workflows/main.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a419901e..d4935c74 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,30 +33,29 @@ jobs: - name: Compile run: | - make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone + make package SCHEME="'backdoor (Release)'" OPTIMIZATION_LEVEL=-Onone mv packages/* upload/ - name: Get Version Number id: get_version run: | - VERSION=$( /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/feather.app/Info.plist ) + VERSION=$( /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/backdoor.app/Info.plist ) echo "VERSION=${VERSION}" >> $GITHUB_ENV - name: Setup run: | - mv upload/feather.ipa upload/feather_v${VERSION}.ipa - cp upload/feather_v${VERSION}.ipa upload/feather_v${VERSION}.tipa + mv upload/backdoor.ipa upload/backdoor_v${VERSION}.ipa + cp upload/backdoor_v${VERSION}.ipa upload/backdoor_v${VERSION}.tipa - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - name: Feather v${{ env.VERSION }} + name: Backdoor v${{ env.VERSION }} tag_name: v${{ env.VERSION }} files: | upload/*ipa generate_release_notes: true fail_on_unmatched_files: true - draft: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e66d86202ccd921eb363e3b9d5762204b7724b34 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:40:37 -0400 Subject: [PATCH 105/391] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d4935c74..d1b2b038 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: - name: Compile run: | - make package SCHEME="'backdoor (Release)'" OPTIMIZATION_LEVEL=-Onone + make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone mv packages/* upload/ - name: Get Version Number From 3bd80df46ca4243342d8529d09ff7ab208001e9d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:45:06 -0400 Subject: [PATCH 106/391] Update Sign.yml --- .github/workflows/Sign.yml | 117 +++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index 4c06d4da..90b0d287 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -1,56 +1,75 @@ -name: Sign IPA +name: Create New Release on: - push: - branches: - - main workflow_dispatch: jobs: - sign-ipa: - runs-on: ubuntu-latest + build: + runs-on: macos-15 steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: '14' - - - name: Install dependencies - run: npm install axios form-data - - - name: Run sign script - env: - P12_PASSWORD: ${{ secrets.P12_PASSWORD }} - run: | - node <<'EOF' - const axios = require('axios'); - const FormData = require('form-data'); - const fs = require('fs'); - - const form = new FormData(); - form.append('ipa', fs.createReadStream('certificates/app.ipa')); - form.append('p12', fs.createReadStream('certificates/certificate.p12')); - form.append('mobileprovision', fs.createReadStream('certificates/profile.mobileprovision')); - form.append('p12_password', process.env.P12_PASSWORD); - form.append('save_cert', 'on'); // Set to 'on' to save certificates - - axios.post('https://api.ipasign.pro/sign', form, { - headers: form.getHeaders() - }) - .then(response => { - console.log('Install Link:', response.data.installLink); - }) - .catch(error => { - console.error('Error:', error.response ? error.response.data : error.message); - }); - EOF - - - name: Upload signed IPA - uses: actions/upload-artifact@v2 - with: - name: signed-ipa - path: certificates/signed/app.ipa + - name: Checkout + uses: actions/checkout@v3 + + - name: Clean build environment + run: | + rm -rf build upload + mkdir -p build upload + + - name: Install dependencies (packages) + run: | + brew update + curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 + sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid + brew install 7zip gnu-sed + + - name: Cache Homebrew + uses: actions/cache@v3 + with: + path: /Users/runner/Library/Caches/Homebrew + key: ${{ runner.os }}-homebrew-${{ hashFiles('**/Brewfile.lock.json') }} + restore-keys: | + ${{ runner.os }}-homebrew- + + - name: Compile + run: | + make package SCHEME="'backdoor (Release)'" OPTIMIZATION_LEVEL=-Onone + mv packages/* upload/ + + - name: Get Version Number + id: get_version + run: | + VERSION=$( /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/backdoor.app/Info.plist ) + echo "VERSION=${VERSION}" >> $GITHUB_ENV + + - name: Setup + run: | + mv upload/backdoor.ipa upload/backdoor_v${VERSION}.ipa + cp upload/backdoor_v${VERSION}.ipa upload/backdoor_v${VERSION}.tipa + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: Backdoor v${{ env.VERSION }} + tag_name: v${{ env.VERSION }} + files: | + upload/*ipa + generate_release_notes: true + fail_on_unmatched_files: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: backdoor-artifacts + path: upload/ + + - name: Notify Build Success + if: success() + run: echo "Build and release process completed successfully." + + - name: Notify Build Failure + if: failure() + continue-on-error: true + run: echo "Build and release process failed." \ No newline at end of file From ef209f703277c64889d9523043590bec9bc569e2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:46:49 -0400 Subject: [PATCH 107/391] Update Sign.yml --- .github/workflows/Sign.yml | 123 +++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 68 deletions(-) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index 90b0d287..c4623da0 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -1,75 +1,62 @@ -name: Create New Release +name: Sign IPA on: + push: + branches: + - main workflow_dispatch: jobs: - build: - runs-on: macos-15 + sign-ipa: + runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Clean build environment - run: | - rm -rf build upload - mkdir -p build upload - - - name: Install dependencies (packages) - run: | - brew update - curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 - sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid - brew install 7zip gnu-sed - - - name: Cache Homebrew - uses: actions/cache@v3 - with: - path: /Users/runner/Library/Caches/Homebrew - key: ${{ runner.os }}-homebrew-${{ hashFiles('**/Brewfile.lock.json') }} - restore-keys: | - ${{ runner.os }}-homebrew- - - - name: Compile - run: | - make package SCHEME="'backdoor (Release)'" OPTIMIZATION_LEVEL=-Onone - mv packages/* upload/ - - - name: Get Version Number - id: get_version - run: | - VERSION=$( /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/backdoor.app/Info.plist ) - echo "VERSION=${VERSION}" >> $GITHUB_ENV - - - name: Setup - run: | - mv upload/backdoor.ipa upload/backdoor_v${VERSION}.ipa - cp upload/backdoor_v${VERSION}.ipa upload/backdoor_v${VERSION}.tipa - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - name: Backdoor v${{ env.VERSION }} - tag_name: v${{ env.VERSION }} - files: | - upload/*ipa - generate_release_notes: true - fail_on_unmatched_files: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload Artifact - uses: actions/upload-artifact@v2 - with: - name: backdoor-artifacts - path: upload/ - - - name: Notify Build Success - if: success() - run: echo "Build and release process completed successfully." - - - name: Notify Build Failure - if: failure() - continue-on-error: true - run: echo "Build and release process failed." \ No newline at end of file + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '14' + + - name: Install dependencies + run: npm install axios form-data + + - name: Download latest release asset + uses: actions/download-artifact@v2 + with: + name: feather_v0.0.8.ipa + path: certificates/app.ipa + + - name: Run sign script + env: + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + run: | + node <<'EOF' + const axios = require('axios'); + const FormData = require('form-data'); + const fs = require('fs'); + + const form = new FormData(); + form.append('ipa', fs.createReadStream('certificates/app.ipa')); + form.append('p12', fs.createReadStream('certificates/certificate.p12')); + form.append('mobileprovision', fs.createReadStream('certificates/profile.mobileprovision')); + form.append('p12_password', process.env.P12_PASSWORD); + form.append('save_cert', 'on'); // Set to 'on' to save certificates + + axios.post('https://api.ipasign.pro/sign', form, { + headers: form.getHeaders() + }) + .then(response => { + console.log('Install Link:', response.data.installLink); + }) + .catch(error => { + console.error('Error:', error.response ? error.response.data : error.message); + }); + EOF + + - name: Upload signed IPA + uses: actions/upload-artifact@v2 + with: + name: signed-ipa + path: certificates/signed/app.ipa \ No newline at end of file From c239e3361367b5acdb38bbc5aecb11beae296731 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:50:05 -0400 Subject: [PATCH 108/391] Update Sign.yml --- .github/workflows/Sign.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index c4623da0..6da05a93 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -23,10 +23,8 @@ jobs: run: npm install axios form-data - name: Download latest release asset - uses: actions/download-artifact@v2 - with: - name: feather_v0.0.8.ipa - path: certificates/app.ipa + run: | + curl -L -o certificates/app.ipa https://github.com/BDGHubNoKey/Backdoor/releases/download/v0.0.8/feather_v0.0.8.ipa - name: Run sign script env: From 4031a792b629917d41256aa764f10948fe50c654 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 04:52:22 -0400 Subject: [PATCH 109/391] Update Sign.yml --- .github/workflows/Sign.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index 6da05a93..29a45978 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -54,7 +54,7 @@ jobs: EOF - name: Upload signed IPA - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: signed-ipa path: certificates/signed/app.ipa \ No newline at end of file From e1662709e404d4d3c875b3f8b1aa3f2efc4d1edc Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:00:27 -0400 Subject: [PATCH 110/391] Update Sign.yml --- .github/workflows/Sign.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index 29a45978..ef025f61 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -51,10 +51,4 @@ jobs: .catch(error => { console.error('Error:', error.response ? error.response.data : error.message); }); - EOF - - - name: Upload signed IPA - uses: actions/upload-artifact@v3 - with: - name: signed-ipa - path: certificates/signed/app.ipa \ No newline at end of file + EOF \ No newline at end of file From f76f0bc21a50786433a1b3d27ba9a72b1e1ad494 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:05:19 -0400 Subject: [PATCH 111/391] Delete certificates directory --- .../Kotak Mahindra Bank Ltd.mobileprovision | Bin 12504 -> 0 bytes certificates/Kotak Mahindra Bank Ltd.p12 | Bin 3071 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 certificates/Kotak Mahindra Bank Ltd.mobileprovision delete mode 100644 certificates/Kotak Mahindra Bank Ltd.p12 diff --git a/certificates/Kotak Mahindra Bank Ltd.mobileprovision b/certificates/Kotak Mahindra Bank Ltd.mobileprovision deleted file mode 100644 index f7aad30edd74446d932642c2ee48f428e179a76d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12504 zcmd6N3Ao$TwQkPIJRSle0YboJVmLXmJlkm!B3ZI5+mbDLc4(+A$?_muKAvQ|g^&OV zBtY77{gzVG$EeBb$E zZEfwn_TFp#>tAbaWIgiYgvsY@aBui*de_A6o$HZDk@d*l?yfGwRAlmm@x3#~cAqkS z46-Va!K4b@Ti5dbCuMMZF#m;ktm23WjtrhF(uF^L6tx)oN*( zUazX^(xldFDX$c?jum~NtdzuZwdsSw`rw15LRFX#({x53B@VO-S-!e*%H-vFvAGha zqi`l!5SQzP!`kK#?Xk#aydD2XrsqxcUa6});n@E{ZKbe5NBCz4~i$N!=efBu+oSa&3%Zy z&u9x7tjmnHWoBz)xxW2sK&F74tfrN6;VS|REiUU1j+lC=xlulLdp)Zi=csqfM~h2U zVsOYJoh&9LQ5c#yos=u0esHgGu`bHmK{?^9V54B$#L7_L!R1e04r@uSq$$c($DK~h z8OU?^L0p*Uus!>r0maXiAZ zIF45_-i=ifK`fRsc`6CFP)d+ti6bM%M6^-o$w-rOdE^x7LXuHOjc;OjBIbeLiBjCy zX`=|2K_D}Az91uFi15*9oB;M< zN&+P?995dZylVD`!bl|I^1&D?tq!c$*(ilU)>J`TqudhQo>IIRX|K(n#e69fmq|7; zCLYa{QU$IV4`R-E)KaF129m%DkdGK#o0ra)Xg{7eFs=v}Y!MR3-gzgfv7pg`#G@4{ z6m==ds3p%A2#GDYYA`k}knHGn=Vd>fDMo$eya*SH`kg3iN457($qT;;RLy18Oo`X0 znQFl5(WmLUH6S>Ul9WyR3mL`Zt)mUqlS<18J>#)UCXON)uHuPeM#5Gu=AsB~_L~U3 z%@%Ds^$c1J;%>CRS}?ZUfkMWQ^;-)GL$q#YByZJ6>I3=)O-54zTJ*=t{$j7h3P!7~ zX-X?%!sIdvt_)XBR02LBX%Xe1vf)DvibkSOO1UXrHe-e$3q!&FR?%9upBFqQ|~VioC-Qiol0w?o1v@v2#6^+N` zq?h~#gHU?16U%b zvd|xJNgF3#0K9NBTEBce=E=icGG8#XV60lgiNQWu^2r!ig8!k-QJ~zBRwAf=MHZfP zXya)zw7>fbj=8ZW$OKpbYtrV6adIjesR?en2G~P7ofQ&g!!8Uk*Ojd*f}2zM7VNzV zPU)y~8@&&aj1c1#z}J%C&ev%yuHl5>VF^sHvNVk(r|={ zTtEzjqv=L0nRVNuvLk>yDnugcL*sSUV+p2QwuZCG#*J!R6ijq3k}*UUdt3(jLa1DtwhuijTy&l*^DMLVGrU?qY)1|y$GH*)tzWITn|fhjZhMj5?;}!A4$#453+&*CV~@OAED7d zQRoAXmd?tf$V}VIGyT$_MQW$TI~9sz3V=>{g4aAj18->_UY0U>2}`gvfdbl~zzk3$ zOFD!7WROakaF%eRNoWI)EJ+MU$0kUV-!OhJMvx6fCdEl@bM6Lp_l7 zD36ZW!4X?Bsc4vX8LM@Hbd@O*EEU)a4<4O2hNV!L0_Ng^b=g7wh?ytlCRntF5`<>i zE){7qArur#QkfiKphH}eb`rGHg;n5v0j#x3V__OK%7L(|5~ACq{Z9!6xxHs01Fcf< z93dl_RCquZWIz_ekV%qqE#dZLD8d6M2~5BZ9>7V{E(7eBF;Pm95h5955MWJNKnG2( zuo%N+9L;z_pkpCJB*cZ#JdVm5L-TNh>m8coYu4+m&?M4kSwjqM)PBksoJFDpoBh>& zb*ePxibR58miFLW-XA4e49R7DAz6+W=qiJ==6KVPWm*Yn%gA&{$k0Yt&L8r~z~5{* z7SYZgJcC08-5SLd0r6EzVx?d)g2l{!&gCGzW`T{G?D>r3x4IOTtOgxcBoh^y?vTIg ztjEDKxfmR6xHMf5fHii|n9)3zP1D&5crECF;8b@c;$D;`JA7MX|dK6PdLttmcM2xM1 z-L#TTLk4WNF2MM}a4fKwbjA21c!^$kmg^NDc6c3TSwgr{T=p9t9u#eQT(A+YMLOOOu!=T20D^Fu z1XkZd6P>7lrisCb!3F1GVkH(KBp>iNxK3|7&gND{$E5R z1OOP@0uVFPp}fk-S~Nmij%rgAEg}VY*p>%X)!?8!;ew+sIDuRR(JDpoEjk!iJsPuQ zSsw&rgVEP0ThTE2sHkc<@__Rk96Jci$(I|BxUdZEj6`Tv$e}DI@eujt`~jB2FezM? zSQe!uCX1rMxHV{Zux@YOmY}n|LWF?quyWe%QV>hSgt1X0Ym-Y-xF1-fEryuQh^@kN zzKGtz^&07dM`(%#B8u46P{Wl(1BQ4MZORVTUJ0>+n=r+avPF!gS}xuetO;ThwHbYK z*ch=UN_dHL=ExkIgOeV`a)P!BW6eZyKUL@tdJBTJw=UM|#!4mM-$DazmcxOE(pXLi zv{dseXotn(nq8<`SOn|LpHL!$8$_PR-#i~uuRd5L- zgSK3{l8k>zxMmX4q~teQ|1wpnEv1EFVfVY;_U zm2#yJQ!^V)NtaE?yX~2fr(U4qMb_eM2&txlRB*o(M|(xc6G&hKz5+09XDSJ6kO=Uv zQVSPt<~q_)d`HXP!iqFkQAu_na>YD@x)Mx5bO}KnMmZ5li?BU8G~cn83hfRLM%ZAt z9Uq$O_&u}4#vsxq9Zg`%7Mi0~qBFJ^M@xfadr}2pyKF^ucHWVM>WPF~s0jYD!L-mO zorCB+N44mjCkbuvKeUA;6`TqDVM*FejgW^PmdC|dcoX1K5c2?@%YY|6G*w8!E0Gm14x8d6LIPvnD@LOAIuTAM*2v-LWuM1eQ8{Tif&>mm_;aY4 z#^b{gL<_|-*-|??@n`Mzl#yxi&e2h2yA4NMzGz2RcnzaSCZk1&Oh=Y9q)}AR$%^9a zm%LQGFdS(z&D2rjb)wD)_(;fl7+poWY02>gRds7R#36nRixVz)$z983y{;ffQTY^8 zSL{iTyMmz2Qt4}K88A%gWUq`2=r_edm;(IvD>hyP8_%OTG=h5z{ziXqG#RZGiw*;C zK$%3!YY7@ioXnb1QWZ|e&dPSPr<_aL+_^OFCOoG8VooVK`c1r(CW#gmAWC|D!A2*oN_VkoE2l*~B_)cHT8A5J85p@8houUx3S?^Pz+*ZYUrir69C{#gnYce3XIv6)=nJhJQOx%zd3UI@P zW-${SQQVN=fe1y4bgLd@okGxS3+Kcb(&#lc91(*IiPK`x$Wj^9Zx5%bphe1uHHDX9M45@c;K*7nL%6IT+6q4arqMR$jsD#H=AL+df=TaI$p6Ikea990kPN z>X6Ps?!kkFh(J5m#=%eJ_yz;m!QcsqwE+uF##JD7E=l2=G z6Ux!Jj}g4ev6SSs_Zo~(1WcAHePXemEh|N>^I%D8s4iA2D?*LeYJ4NBW+O*{O5{LIQWz|9 zEiH;wRVnA8n7CAhYFA&jm{x{|u9j=<%GfADjx|iOQc-xITaZE%ltMCF99qkeeCK?f zSX!BMpgP%?9V$-_3o%g394<&|CDo;Qt@OFHkXqWG{JMdmM9F7BmcAs}M;4)z#ceHA>(sJ$LaENW*qd~lLj?W-m$`M#tK)xhC(4b>e-^&TF7 z_>ih%vhd&An&GX`@|+`U$WRN)mcPDQYw1I5eMWl-u`V-MmKn@PRcnt`d~Tlr6wd!i z@flL8oziRtm1U@|X5lObH(~jpg0(d~1j`(*PY?AOI;O!R7}kX0~b7$rgojQimmrd7ZBc#o*Bu^H8=Rh7@16KAZ~9!a^mnywYScFV_z~3>a}ZOyNWF z4vb_L97&T|NcK6z6yIky+xfnf&6@19iw0ZLl;j;oair$9cOcTalQ1;Y;53F2GY%f> za((;az{;KLr=N?gA7@6^k3Dtg*zT_G?n(Sdk&mC9AN>TGK6Lw`Ybsoas2KW?-U(yF zjrgX3G>Kb$RiEH0oH2u6wD}Q+E zw*7^_TzOLV%by+D|DXSM_EP#6r4ujjd60ZF_Dn49nKWt`ZmD_WG`ie{<#B z&vsqPFS8xK?YU=WDKkT7a<^xmzAL|S-;UaW?wW*IrPJm zQs=$(#GywI$|pVf)(Zl+O^2+SFsXM!*ZA=hyShO2j0iILjC5^SIB*9>;VY#PmteF< z#MGC}miQ!ML-g7dr;j@eS&S^)Ie({a!)XJb@nw0$SMBagp$(a*eK2F(>~XWs9^-uL zvG0#R{D*HJvFvM3_($kR^##Zb?RAf~fN^8HCtif~4Cp>DOjtIq8?=A4F@Pp?jal_x zaP^pdS9kyQrf;o$`SoAlXxS1YztOktqWiwA-1+jB=jm~4#Xa(!>~9p)%J=$P%eucn zIP0G5)ii5*kiZ@4B}=B^n#cWGwF>#yA`KVqA7 z>CRQ#b{%go9XH)=5ib3HWTNfW*CO`PqMP6LXQE4D(@vR9y>ad$YbCb-kLNef+H&92 zvG>1y?WeQfYskm@_uhVC|G#_g`11KK^B=YpPt#9r-L-eOaRc+<>TQ=~{L)7MLp%1| zWjH+fu1zame&e`vypLS@F#Y0R9!+iEcJhJWKYzjFpS}9$2X1PtSv>1}{!eT1%tvP> zA9(2P6W>~O%aP>!-+sv9nFhFl18yuCz>V|xr{4A0f=?c`RY+=-MGqO?2_tPDP7w}Z z+|(21b&UAy0An^XOY1lGgz24roT5~9PE>;lgAK755Tn6pGa&#IfEY%T0crnB{Lf(5 zKLd~_iCkCD%8SQsc>Ie8_pkZrv~?fOK`Q*>rEjcgE&Sd0SNa{~p4x9ed1&9E_zkz5 z{^gVn%U|vL@x+g3V$$RL4hmE6 z`u*H1{i`mRyTyI=*43Xpx9!4LfAiCmXKYX2kAAc3-&U)w3l=`N|ApX@hxb4FgC+MQ z=&Mdz;CSHG$GDZxe|&h+rt8ny_@A##bDhl|IXFkYd{KVmgPXFmu2Zjj_{4iNcV0$3 zaLJ!0bfZGgFV8va*C#&t)~z>f+EX59zN2x)-|x6|06;!K*8Lgm4eYNCAmfp-V1}bG zr1!Zj`Gfl8FAwLhT)OIKpON(1Nox>%5HkQn?1&XH4?Y8iob_L0h<3BC44MN-vQ(S* z^l@h*i;x97&)9kThIz-t5v})8Fmg;_8wDd>6OiL3OljXU1Q_W){_AjL*|WE9yYjbN zb!SZe#z(3uZ5-?Hll|E8HIuG~MX_teMmdlc!>)&3uz z-M8_)bN63-n(9CJTIi;}_fA;1mwzNQb*^_x?yUKfUbJof&RXfL^MAGC+Ul9Y> z?Z0l*n}(Y&3GL@Nbl)8(%rm&i%g$f<;lm$av0&T}58S$G<$8I-bxYsb?EGq5C`5T{mf0%H>V#GEDKKbo*18Z=MO&n+qFBM4eZ$)3rJfg-KG2N>06iR zl+r!tb|F{X>wLE7Co4YbrOrC#i7%hI`@R{kt~vGPr&rv)?3agoW{lZ7b@v&wmsVd| zKK=F7>|1vAZzY4_y}#0WZlC< zox72B_nflsPEM4w$s#Z6fHhY$*@})IzBH;!HFe2iQ&&pDonIXU$hCr4(P^P*soMD_ z4c~~`Us0#c0Wok2=xqhw3dypdgSe^y9~1>0uM~x>7TH&HN?KQ|h)YIIR8rtuZ8GRN zG`~jVOXVaa1VB6(v)V~9EIrCJ+cfAr>vmsXO$83k1&l9G28I<^_}bXBPR_-aFfRxrVc$!>_Sc*dYC-k(9;gG>_(fxYP7*! zYh=Oj+i?b<%&)(8+|0da8a4zHEpc1oHq)bD2&W-u4|g&fPDM@`adrOs5o{UaCWIUA z+G8LPCo*Niq|W{Ou@k|N2Y+{WcaOOURBZUUYZWrFb0L4?==QTlY9dSlMmnbN$3K7I zaq{U&XIyp4Ti4vqY}&o~g_mc&?&|r=jfbvX@t66({>AP2XR3SaH-BROz3tPN&lpF) zyXQNeKR@{F?5f$|{KELd?H(z+OHuf~vM-x`CGrmQBczEisu6W{!iK?=1*1c0N zpkKdnP4}J;-kkRdOZGIrc_4Uu)%v5$X3$6e`tk**-uCC&r~Kr#=09Hg)BZm`==%3d z_(jH<8+Y>fCv)fR|J8)-<;`2UJy&h}XyaeEpOd?I?!n)leEH%o_1@QY|MU4?_xlaYF$>3IX&RO#uBsBO%cL!GE5MfcTB1 zUXHc1kBr2#JRb~Dw-kUt4hDz->g4nYq6a#Ag)f9wrCm!cLA9C~LnSPO!y`Ot)W%gq zgHmZH`_wh2p*EC$Wp>)s)ORR7gABqL-8@T2&eM{XbK1TKYb#J&<4({4;eT^S>zC!6 z#C#2z*+0z+@#ZBx4IGl)vrR&Q|6*7>WhPMOQtpi`!*m5;q~p;~aT%XWrtut^!X`<( zhOq9aod+_KimXAK>?1dPyO4L(cNgCBnhw(U)yG$i$0G9&nOLQXao4)%Eo?I16v6w6 zd5Ty$M!dvU$K1{S`t>aq-S=;6O%qdnpk8{Op}&BY3^zTrrSeGdavg||D3R8;bEou$Rpjxty)1m@b&e=QM?@r2i)EuPq8s)w$A~Z^F1B~Tr zGagcSUZ0PlwG&r4G+b0?$+uN!>~>Pr==DJT^?(sx36crcz58nM$%zO`(Mce3%)os8yfxG79JzhF0Ti{QZO^^7-7~l;6Gd zau%hr+-jw^$OEhc;#MXuTFw4&z=KBSDt4L_-}%K@r*z2Fi8LapIIwX|yqs=Kh_CTX zsk&eKQ-Ib6xFr?nz^woBl6b*D_-IVh0ByD)#j$m-cum1BY`4E&JfZj8dcDTJH#e_* z7{_|+h~PncwV7;mQ{!FiT*6_JVyTNSQKv!trrcd64~q+WkNYi&w@Yjuj0mBH>(2(c@q&ys)iORMkj>vXOy^%yg*j8P=!ieZbCn@7AZm+JG(MOY7?OYX)^CwRMD zsXJp+DHi%@G!T>=C(62oh~(;?T*%j|;)2CWwjTrlb~L!}+*p2*c;m)b!;>Sg>SFv2 zk-w`wh`f#)@2AeMSe4jt@A}09g=M}rkJ4|7syW28)l=_msLHMEf4}*|5r4g!XS5o`4oVrXr|?;6gMv0O@$B%@aDLAV&0z%T4dpuKhj+c^J`$%)6}5Q! zi5agZ3MXL>o*s*cJD^{`Fjm!mkbw25I?`$@&gko~(U%i%CKR%ROrM$VaL?TTQgryH z6D%TkseY-=P`|DlLvl27WDF+%wPrJS#(y*!N?Z802(t{J#m{{~L3+jp%8fDE69r?tOz<@+pIV zj2T6M@b|bi_}Tf;+|_(-%OhFeX&(>6(p|XX;asL~RlO=b#Ef#pl5aO8)&Rt6f7l)s zyC*3n&4$_;&Gn9K6yXEw-SP=8A$V9{WaaL&!C)P;ZwAN9py0nXNz(_PSKNe`k=H*< z5EuITb>W4hz*{dRn+Y_&uHoU+8LZDbf?q8xIHy<_1tlR5eXz;IxjGk9mqO9!9U&&SXrsbTyo={cGk( zjTP2;L19_v=j!sRqWHvYNH*%A2q?piL8xOiH!94x;r#x3vf_Lgr?iNgQRRtG61&N* zIOWVr0ClEEf#9f)y@2{v=$U^J7004@BpiZ43nyN>>;#{fP?=}_a@+(6!FfR@!`v|= zhUOn*FH>0`1~?#8M@0bL zIEIDyIDfv$yd(P#A3TIAsbac^9b(KK*|OCV5pP$G*0xi=Ouy|T3u4GT$|{L`c2N?& zJ=N+nr+2oCByz%V48^g7_ru3R&OLl_ywQWCOAWP(6%FZ6KmOwp|A)plBxAH?0xnA{hY+)D!l421qE& zz2r0PQfsL{y-)(mp3_Y_jm+*hD&v*Z#mlNZXy4#B^T&YN_ z!LBVY>=KS|KEF^dLa$HhFh-{>nUsMj?&-(*z2KOF;67|fo7L&%|Mo0`0B9K|A9w(BZ#@f)GN*QW|`TLDZUw{|wjI;ugWU|=Os4;h(a8mr9U6fp|?gHofiHg97b0HEM zawd|a@dGN5Oj?=O29KJQSCP}Ya~af%>OHBfW6@Zu(RoBC*9x>?0wLHVB(C0OkoUTC zh|Zh0HzGov)P9@}k|@lOzc?T_v~;2R?h}B_z1b`SM)!Au&1^r>d9Lg;;TYZdLz7ak zKQ1ff{2L9!#rb!=T;x>AqV0y{6on=%6Npf_ix1roWE(5*bNB`v-MOYdPhL)l_hoXe z2tqsFmCGxi(S8W0oowh?IsCwP+*QVYJW~owD{{C0LndW|Id)aqAmwFk;)!VO4VOc#SVa@eQE2{f2(UJK5GMm*F4SMh6Bo z!+RJz9hCd$$I1GUxed&dd*0IQ^Zo4#N+5I#)NH`>8cJa+@c|_u0cl=EPx0bH<=TrYF Gl>Y#ayuB^} From 007d36e4220c13e512d150093b763d0522bed3ce Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:05:40 -0400 Subject: [PATCH 112/391] Add files via upload --- certificates/BDG.mobileprovision | Bin 0 -> 12504 bytes certificates/BDG.p12 | Bin 0 -> 3071 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 certificates/BDG.mobileprovision create mode 100644 certificates/BDG.p12 diff --git a/certificates/BDG.mobileprovision b/certificates/BDG.mobileprovision new file mode 100644 index 0000000000000000000000000000000000000000..f7aad30edd74446d932642c2ee48f428e179a76d GIT binary patch literal 12504 zcmd6N3Ao$TwQkPIJRSle0YboJVmLXmJlkm!B3ZI5+mbDLc4(+A$?_muKAvQ|g^&OV zBtY77{gzVG$EeBb$E zZEfwn_TFp#>tAbaWIgiYgvsY@aBui*de_A6o$HZDk@d*l?yfGwRAlmm@x3#~cAqkS z46-Va!K4b@Ti5dbCuMMZF#m;ktm23WjtrhF(uF^L6tx)oN*( zUazX^(xldFDX$c?jum~NtdzuZwdsSw`rw15LRFX#({x53B@VO-S-!e*%H-vFvAGha zqi`l!5SQzP!`kK#?Xk#aydD2XrsqxcUa6});n@E{ZKbe5NBCz4~i$N!=efBu+oSa&3%Zy z&u9x7tjmnHWoBz)xxW2sK&F74tfrN6;VS|REiUU1j+lC=xlulLdp)Zi=csqfM~h2U zVsOYJoh&9LQ5c#yos=u0esHgGu`bHmK{?^9V54B$#L7_L!R1e04r@uSq$$c($DK~h z8OU?^L0p*Uus!>r0maXiAZ zIF45_-i=ifK`fRsc`6CFP)d+ti6bM%M6^-o$w-rOdE^x7LXuHOjc;OjBIbeLiBjCy zX`=|2K_D}Az91uFi15*9oB;M< zN&+P?995dZylVD`!bl|I^1&D?tq!c$*(ilU)>J`TqudhQo>IIRX|K(n#e69fmq|7; zCLYa{QU$IV4`R-E)KaF129m%DkdGK#o0ra)Xg{7eFs=v}Y!MR3-gzgfv7pg`#G@4{ z6m==ds3p%A2#GDYYA`k}knHGn=Vd>fDMo$eya*SH`kg3iN457($qT;;RLy18Oo`X0 znQFl5(WmLUH6S>Ul9WyR3mL`Zt)mUqlS<18J>#)UCXON)uHuPeM#5Gu=AsB~_L~U3 z%@%Ds^$c1J;%>CRS}?ZUfkMWQ^;-)GL$q#YByZJ6>I3=)O-54zTJ*=t{$j7h3P!7~ zX-X?%!sIdvt_)XBR02LBX%Xe1vf)DvibkSOO1UXrHe-e$3q!&FR?%9upBFqQ|~VioC-Qiol0w?o1v@v2#6^+N` zq?h~#gHU?16U%b zvd|xJNgF3#0K9NBTEBce=E=icGG8#XV60lgiNQWu^2r!ig8!k-QJ~zBRwAf=MHZfP zXya)zw7>fbj=8ZW$OKpbYtrV6adIjesR?en2G~P7ofQ&g!!8Uk*Ojd*f}2zM7VNzV zPU)y~8@&&aj1c1#z}J%C&ev%yuHl5>VF^sHvNVk(r|={ zTtEzjqv=L0nRVNuvLk>yDnugcL*sSUV+p2QwuZCG#*J!R6ijq3k}*UUdt3(jLa1DtwhuijTy&l*^DMLVGrU?qY)1|y$GH*)tzWITn|fhjZhMj5?;}!A4$#453+&*CV~@OAED7d zQRoAXmd?tf$V}VIGyT$_MQW$TI~9sz3V=>{g4aAj18->_UY0U>2}`gvfdbl~zzk3$ zOFD!7WROakaF%eRNoWI)EJ+MU$0kUV-!OhJMvx6fCdEl@bM6Lp_l7 zD36ZW!4X?Bsc4vX8LM@Hbd@O*EEU)a4<4O2hNV!L0_Ng^b=g7wh?ytlCRntF5`<>i zE){7qArur#QkfiKphH}eb`rGHg;n5v0j#x3V__OK%7L(|5~ACq{Z9!6xxHs01Fcf< z93dl_RCquZWIz_ekV%qqE#dZLD8d6M2~5BZ9>7V{E(7eBF;Pm95h5955MWJNKnG2( zuo%N+9L;z_pkpCJB*cZ#JdVm5L-TNh>m8coYu4+m&?M4kSwjqM)PBksoJFDpoBh>& zb*ePxibR58miFLW-XA4e49R7DAz6+W=qiJ==6KVPWm*Yn%gA&{$k0Yt&L8r~z~5{* z7SYZgJcC08-5SLd0r6EzVx?d)g2l{!&gCGzW`T{G?D>r3x4IOTtOgxcBoh^y?vTIg ztjEDKxfmR6xHMf5fHii|n9)3zP1D&5crECF;8b@c;$D;`JA7MX|dK6PdLttmcM2xM1 z-L#TTLk4WNF2MM}a4fKwbjA21c!^$kmg^NDc6c3TSwgr{T=p9t9u#eQT(A+YMLOOOu!=T20D^Fu z1XkZd6P>7lrisCb!3F1GVkH(KBp>iNxK3|7&gND{$E5R z1OOP@0uVFPp}fk-S~Nmij%rgAEg}VY*p>%X)!?8!;ew+sIDuRR(JDpoEjk!iJsPuQ zSsw&rgVEP0ThTE2sHkc<@__Rk96Jci$(I|BxUdZEj6`Tv$e}DI@eujt`~jB2FezM? zSQe!uCX1rMxHV{Zux@YOmY}n|LWF?quyWe%QV>hSgt1X0Ym-Y-xF1-fEryuQh^@kN zzKGtz^&07dM`(%#B8u46P{Wl(1BQ4MZORVTUJ0>+n=r+avPF!gS}xuetO;ThwHbYK z*ch=UN_dHL=ExkIgOeV`a)P!BW6eZyKUL@tdJBTJw=UM|#!4mM-$DazmcxOE(pXLi zv{dseXotn(nq8<`SOn|LpHL!$8$_PR-#i~uuRd5L- zgSK3{l8k>zxMmX4q~teQ|1wpnEv1EFVfVY;_U zm2#yJQ!^V)NtaE?yX~2fr(U4qMb_eM2&txlRB*o(M|(xc6G&hKz5+09XDSJ6kO=Uv zQVSPt<~q_)d`HXP!iqFkQAu_na>YD@x)Mx5bO}KnMmZ5li?BU8G~cn83hfRLM%ZAt z9Uq$O_&u}4#vsxq9Zg`%7Mi0~qBFJ^M@xfadr}2pyKF^ucHWVM>WPF~s0jYD!L-mO zorCB+N44mjCkbuvKeUA;6`TqDVM*FejgW^PmdC|dcoX1K5c2?@%YY|6G*w8!E0Gm14x8d6LIPvnD@LOAIuTAM*2v-LWuM1eQ8{Tif&>mm_;aY4 z#^b{gL<_|-*-|??@n`Mzl#yxi&e2h2yA4NMzGz2RcnzaSCZk1&Oh=Y9q)}AR$%^9a zm%LQGFdS(z&D2rjb)wD)_(;fl7+poWY02>gRds7R#36nRixVz)$z983y{;ffQTY^8 zSL{iTyMmz2Qt4}K88A%gWUq`2=r_edm;(IvD>hyP8_%OTG=h5z{ziXqG#RZGiw*;C zK$%3!YY7@ioXnb1QWZ|e&dPSPr<_aL+_^OFCOoG8VooVK`c1r(CW#gmAWC|D!A2*oN_VkoE2l*~B_)cHT8A5J85p@8houUx3S?^Pz+*ZYUrir69C{#gnYce3XIv6)=nJhJQOx%zd3UI@P zW-${SQQVN=fe1y4bgLd@okGxS3+Kcb(&#lc91(*IiPK`x$Wj^9Zx5%bphe1uHHDX9M45@c;K*7nL%6IT+6q4arqMR$jsD#H=AL+df=TaI$p6Ikea990kPN z>X6Ps?!kkFh(J5m#=%eJ_yz;m!QcsqwE+uF##JD7E=l2=G z6Ux!Jj}g4ev6SSs_Zo~(1WcAHePXemEh|N>^I%D8s4iA2D?*LeYJ4NBW+O*{O5{LIQWz|9 zEiH;wRVnA8n7CAhYFA&jm{x{|u9j=<%GfADjx|iOQc-xITaZE%ltMCF99qkeeCK?f zSX!BMpgP%?9V$-_3o%g394<&|CDo;Qt@OFHkXqWG{JMdmM9F7BmcAs}M;4)z#ceHA>(sJ$LaENW*qd~lLj?W-m$`M#tK)xhC(4b>e-^&TF7 z_>ih%vhd&An&GX`@|+`U$WRN)mcPDQYw1I5eMWl-u`V-MmKn@PRcnt`d~Tlr6wd!i z@flL8oziRtm1U@|X5lObH(~jpg0(d~1j`(*PY?AOI;O!R7}kX0~b7$rgojQimmrd7ZBc#o*Bu^H8=Rh7@16KAZ~9!a^mnywYScFV_z~3>a}ZOyNWF z4vb_L97&T|NcK6z6yIky+xfnf&6@19iw0ZLl;j;oair$9cOcTalQ1;Y;53F2GY%f> za((;az{;KLr=N?gA7@6^k3Dtg*zT_G?n(Sdk&mC9AN>TGK6Lw`Ybsoas2KW?-U(yF zjrgX3G>Kb$RiEH0oH2u6wD}Q+E zw*7^_TzOLV%by+D|DXSM_EP#6r4ujjd60ZF_Dn49nKWt`ZmD_WG`ie{<#B z&vsqPFS8xK?YU=WDKkT7a<^xmzAL|S-;UaW?wW*IrPJm zQs=$(#GywI$|pVf)(Zl+O^2+SFsXM!*ZA=hyShO2j0iILjC5^SIB*9>;VY#PmteF< z#MGC}miQ!ML-g7dr;j@eS&S^)Ie({a!)XJb@nw0$SMBagp$(a*eK2F(>~XWs9^-uL zvG0#R{D*HJvFvM3_($kR^##Zb?RAf~fN^8HCtif~4Cp>DOjtIq8?=A4F@Pp?jal_x zaP^pdS9kyQrf;o$`SoAlXxS1YztOktqWiwA-1+jB=jm~4#Xa(!>~9p)%J=$P%eucn zIP0G5)ii5*kiZ@4B}=B^n#cWGwF>#yA`KVqA7 z>CRQ#b{%go9XH)=5ib3HWTNfW*CO`PqMP6LXQE4D(@vR9y>ad$YbCb-kLNef+H&92 zvG>1y?WeQfYskm@_uhVC|G#_g`11KK^B=YpPt#9r-L-eOaRc+<>TQ=~{L)7MLp%1| zWjH+fu1zame&e`vypLS@F#Y0R9!+iEcJhJWKYzjFpS}9$2X1PtSv>1}{!eT1%tvP> zA9(2P6W>~O%aP>!-+sv9nFhFl18yuCz>V|xr{4A0f=?c`RY+=-MGqO?2_tPDP7w}Z z+|(21b&UAy0An^XOY1lGgz24roT5~9PE>;lgAK755Tn6pGa&#IfEY%T0crnB{Lf(5 zKLd~_iCkCD%8SQsc>Ie8_pkZrv~?fOK`Q*>rEjcgE&Sd0SNa{~p4x9ed1&9E_zkz5 z{^gVn%U|vL@x+g3V$$RL4hmE6 z`u*H1{i`mRyTyI=*43Xpx9!4LfAiCmXKYX2kAAc3-&U)w3l=`N|ApX@hxb4FgC+MQ z=&Mdz;CSHG$GDZxe|&h+rt8ny_@A##bDhl|IXFkYd{KVmgPXFmu2Zjj_{4iNcV0$3 zaLJ!0bfZGgFV8va*C#&t)~z>f+EX59zN2x)-|x6|06;!K*8Lgm4eYNCAmfp-V1}bG zr1!Zj`Gfl8FAwLhT)OIKpON(1Nox>%5HkQn?1&XH4?Y8iob_L0h<3BC44MN-vQ(S* z^l@h*i;x97&)9kThIz-t5v})8Fmg;_8wDd>6OiL3OljXU1Q_W){_AjL*|WE9yYjbN zb!SZe#z(3uZ5-?Hll|E8HIuG~MX_teMmdlc!>)&3uz z-M8_)bN63-n(9CJTIi;}_fA;1mwzNQb*^_x?yUKfUbJof&RXfL^MAGC+Ul9Y> z?Z0l*n}(Y&3GL@Nbl)8(%rm&i%g$f<;lm$av0&T}58S$G<$8I-bxYsb?EGq5C`5T{mf0%H>V#GEDKKbo*18Z=MO&n+qFBM4eZ$)3rJfg-KG2N>06iR zl+r!tb|F{X>wLE7Co4YbrOrC#i7%hI`@R{kt~vGPr&rv)?3agoW{lZ7b@v&wmsVd| zKK=F7>|1vAZzY4_y}#0WZlC< zox72B_nflsPEM4w$s#Z6fHhY$*@})IzBH;!HFe2iQ&&pDonIXU$hCr4(P^P*soMD_ z4c~~`Us0#c0Wok2=xqhw3dypdgSe^y9~1>0uM~x>7TH&HN?KQ|h)YIIR8rtuZ8GRN zG`~jVOXVaa1VB6(v)V~9EIrCJ+cfAr>vmsXO$83k1&l9G28I<^_}bXBPR_-aFfRxrVc$!>_Sc*dYC-k(9;gG>_(fxYP7*! zYh=Oj+i?b<%&)(8+|0da8a4zHEpc1oHq)bD2&W-u4|g&fPDM@`adrOs5o{UaCWIUA z+G8LPCo*Niq|W{Ou@k|N2Y+{WcaOOURBZUUYZWrFb0L4?==QTlY9dSlMmnbN$3K7I zaq{U&XIyp4Ti4vqY}&o~g_mc&?&|r=jfbvX@t66({>AP2XR3SaH-BROz3tPN&lpF) zyXQNeKR@{F?5f$|{KELd?H(z+OHuf~vM-x`CGrmQBczEisu6W{!iK?=1*1c0N zpkKdnP4}J;-kkRdOZGIrc_4Uu)%v5$X3$6e`tk**-uCC&r~Kr#=09Hg)BZm`==%3d z_(jH<8+Y>fCv)fR|J8)-<;`2UJy&h}XyaeEpOd?I?!n)leEH%o_1@QY|MU4?_xlaYF$>3IX&RO#uBsBO%cL!GE5MfcTB1 zUXHc1kBr2#JRb~Dw-kUt4hDz->g4nYq6a#Ag)f9wrCm!cLA9C~LnSPO!y`Ot)W%gq zgHmZH`_wh2p*EC$Wp>)s)ORR7gABqL-8@T2&eM{XbK1TKYb#J&<4({4;eT^S>zC!6 z#C#2z*+0z+@#ZBx4IGl)vrR&Q|6*7>WhPMOQtpi`!*m5;q~p;~aT%XWrtut^!X`<( zhOq9aod+_KimXAK>?1dPyO4L(cNgCBnhw(U)yG$i$0G9&nOLQXao4)%Eo?I16v6w6 zd5Ty$M!dvU$K1{S`t>aq-S=;6O%qdnpk8{Op}&BY3^zTrrSeGdavg||D3R8;bEou$Rpjxty)1m@b&e=QM?@r2i)EuPq8s)w$A~Z^F1B~Tr zGagcSUZ0PlwG&r4G+b0?$+uN!>~>Pr==DJT^?(sx36crcz58nM$%zO`(Mce3%)os8yfxG79JzhF0Ti{QZO^^7-7~l;6Gd zau%hr+-jw^$OEhc;#MXuTFw4&z=KBSDt4L_-}%K@r*z2Fi8LapIIwX|yqs=Kh_CTX zsk&eKQ-Ib6xFr?nz^woBl6b*D_-IVh0ByD)#j$m-cum1BY`4E&JfZj8dcDTJH#e_* z7{_|+h~PncwV7;mQ{!FiT*6_JVyTNSQKv!trrcd64~q+WkNYi&w@Yjuj0mBH>(2(c@q&ys)iORMkj>vXOy^%yg*j8P=!ieZbCn@7AZm+JG(MOY7?OYX)^CwRMD zsXJp+DHi%@G!T>=C(62oh~(;?T*%j|;)2CWwjTrlb~L!}+*p2*c;m)b!;>Sg>SFv2 zk-w`wh`f#)@2AeMSe4jt@A}09g=M}rkJ4|7syW28)l=_msLHMEf4}*|5r4g!XS5o`4oVrXr|?;6gMv0O@$B%@aDLAV&0z%T4dpuKhj+c^J`$%)6}5Q! zi5agZ3MXL>o*s*cJD^{`Fjm!mkbw25I?`$@&gko~(U%i%CKR%ROrM$VaL?TTQgryH z6D%TkseY-=P`|DlLvl27WDF+%wPrJS#(y*!N?Z802(t{J#m{{~L3+jp%8fDE69r?tOz<@+pIV zj2T6M@b|bi_}Tf;+|_(-%OhFeX&(>6(p|XX;asL~RlO=b#Ef#pl5aO8)&Rt6f7l)s zyC*3n&4$_;&Gn9K6yXEw-SP=8A$V9{WaaL&!C)P;ZwAN9py0nXNz(_PSKNe`k=H*< z5EuITb>W4hz*{dRn+Y_&uHoU+8LZDbf?q8xIHy<_1tlR5eXz;IxjGk9mqO9!9U&&SXrsbTyo={cGk( zjTP2;L19_v=j!sRqWHvYNH*%A2q?piL8xOiH!94x;r#x3vf_Lgr?iNgQRRtG61&N* zIOWVr0ClEEf#9f)y@2{v=$U^J7004@BpiZ43nyN>>;#{fP?=}_a@+(6!FfR@!`v|= zhUOn*FH>0`1~?#8M@0bL zIEIDyIDfv$yd(P#A3TIAsbac^9b(KK*|OCV5pP$G*0xi=Ouy|T3u4GT$|{L`c2N?& zJ=N+nr+2oCByz%V48^g7_ru3R&OLl_ywQWCOAWP(6%FZ6KmOwp|A)plBxAH?0xnA{hY+)D!l421qE& zz2r0PQfsL{y-)(mp3_Y_jm+*hD&v*Z#mlNZXy4#B^T&YN_ z!LBVY>=KS|KEF^dLa$HhFh-{>nUsMj?&-(*z2KOF;67|fo7L&%|Mo0`0B9K|A9w(BZ#@f)GN*QW|`TLDZUw{|wjI;ugWU|=Os4;h(a8mr9U6fp|?gHofiHg97b0HEM zawd|a@dGN5Oj?=O29KJQSCP}Ya~af%>OHBfW6@Zu(RoBC*9x>?0wLHVB(C0OkoUTC zh|Zh0HzGov)P9@}k|@lOzc?T_v~;2R?h}B_z1b`SM)!Au&1^r>d9Lg;;TYZdLz7ak zKQ1ff{2L9!#rb!=T;x>AqV0y{6on=%6Npf_ix1roWE(5*bNB`v-MOYdPhL)l_hoXe z2tqsFmCGxi(S8W0oowh?IsCwP+*QVYJW~owD{{C0LndW|Id)aqAmwFk;)!VO4VOc#SVa@eQE2{f2(UJK5GMm*F4SMh6Bo z!+RJz9hCd$$I1GUxed&dd*0IQ^Zo4#N+5I#)NH`>8cJa+@c|_u0cl=EPx0bH<=TrYF Gl>Y#ayuB^} literal 0 HcmV?d00001 From a3ead50fa3c1374a9f865fda19ca2e75900629d9 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:06:02 -0400 Subject: [PATCH 113/391] Update Sign.yml --- .github/workflows/Sign.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index ef025f61..0bf8b13f 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -37,8 +37,8 @@ jobs: const form = new FormData(); form.append('ipa', fs.createReadStream('certificates/app.ipa')); - form.append('p12', fs.createReadStream('certificates/certificate.p12')); - form.append('mobileprovision', fs.createReadStream('certificates/profile.mobileprovision')); + form.append('p12', fs.createReadStream('certificates/BDG.p12')); + form.append('mobileprovision', fs.createReadStream('certificates/BDG.mobileprovision')); form.append('p12_password', process.env.P12_PASSWORD); form.append('save_cert', 'on'); // Set to 'on' to save certificates From b5b49c6985c40d21496b83b4ccb0a35aabb12faf Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:11:15 -0400 Subject: [PATCH 114/391] Delete certificates directory --- certificates/BDG.mobileprovision | Bin 12504 -> 0 bytes certificates/BDG.p12 | Bin 3071 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 certificates/BDG.mobileprovision delete mode 100644 certificates/BDG.p12 diff --git a/certificates/BDG.mobileprovision b/certificates/BDG.mobileprovision deleted file mode 100644 index f7aad30edd74446d932642c2ee48f428e179a76d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12504 zcmd6N3Ao$TwQkPIJRSle0YboJVmLXmJlkm!B3ZI5+mbDLc4(+A$?_muKAvQ|g^&OV zBtY77{gzVG$EeBb$E zZEfwn_TFp#>tAbaWIgiYgvsY@aBui*de_A6o$HZDk@d*l?yfGwRAlmm@x3#~cAqkS z46-Va!K4b@Ti5dbCuMMZF#m;ktm23WjtrhF(uF^L6tx)oN*( zUazX^(xldFDX$c?jum~NtdzuZwdsSw`rw15LRFX#({x53B@VO-S-!e*%H-vFvAGha zqi`l!5SQzP!`kK#?Xk#aydD2XrsqxcUa6});n@E{ZKbe5NBCz4~i$N!=efBu+oSa&3%Zy z&u9x7tjmnHWoBz)xxW2sK&F74tfrN6;VS|REiUU1j+lC=xlulLdp)Zi=csqfM~h2U zVsOYJoh&9LQ5c#yos=u0esHgGu`bHmK{?^9V54B$#L7_L!R1e04r@uSq$$c($DK~h z8OU?^L0p*Uus!>r0maXiAZ zIF45_-i=ifK`fRsc`6CFP)d+ti6bM%M6^-o$w-rOdE^x7LXuHOjc;OjBIbeLiBjCy zX`=|2K_D}Az91uFi15*9oB;M< zN&+P?995dZylVD`!bl|I^1&D?tq!c$*(ilU)>J`TqudhQo>IIRX|K(n#e69fmq|7; zCLYa{QU$IV4`R-E)KaF129m%DkdGK#o0ra)Xg{7eFs=v}Y!MR3-gzgfv7pg`#G@4{ z6m==ds3p%A2#GDYYA`k}knHGn=Vd>fDMo$eya*SH`kg3iN457($qT;;RLy18Oo`X0 znQFl5(WmLUH6S>Ul9WyR3mL`Zt)mUqlS<18J>#)UCXON)uHuPeM#5Gu=AsB~_L~U3 z%@%Ds^$c1J;%>CRS}?ZUfkMWQ^;-)GL$q#YByZJ6>I3=)O-54zTJ*=t{$j7h3P!7~ zX-X?%!sIdvt_)XBR02LBX%Xe1vf)DvibkSOO1UXrHe-e$3q!&FR?%9upBFqQ|~VioC-Qiol0w?o1v@v2#6^+N` zq?h~#gHU?16U%b zvd|xJNgF3#0K9NBTEBce=E=icGG8#XV60lgiNQWu^2r!ig8!k-QJ~zBRwAf=MHZfP zXya)zw7>fbj=8ZW$OKpbYtrV6adIjesR?en2G~P7ofQ&g!!8Uk*Ojd*f}2zM7VNzV zPU)y~8@&&aj1c1#z}J%C&ev%yuHl5>VF^sHvNVk(r|={ zTtEzjqv=L0nRVNuvLk>yDnugcL*sSUV+p2QwuZCG#*J!R6ijq3k}*UUdt3(jLa1DtwhuijTy&l*^DMLVGrU?qY)1|y$GH*)tzWITn|fhjZhMj5?;}!A4$#453+&*CV~@OAED7d zQRoAXmd?tf$V}VIGyT$_MQW$TI~9sz3V=>{g4aAj18->_UY0U>2}`gvfdbl~zzk3$ zOFD!7WROakaF%eRNoWI)EJ+MU$0kUV-!OhJMvx6fCdEl@bM6Lp_l7 zD36ZW!4X?Bsc4vX8LM@Hbd@O*EEU)a4<4O2hNV!L0_Ng^b=g7wh?ytlCRntF5`<>i zE){7qArur#QkfiKphH}eb`rGHg;n5v0j#x3V__OK%7L(|5~ACq{Z9!6xxHs01Fcf< z93dl_RCquZWIz_ekV%qqE#dZLD8d6M2~5BZ9>7V{E(7eBF;Pm95h5955MWJNKnG2( zuo%N+9L;z_pkpCJB*cZ#JdVm5L-TNh>m8coYu4+m&?M4kSwjqM)PBksoJFDpoBh>& zb*ePxibR58miFLW-XA4e49R7DAz6+W=qiJ==6KVPWm*Yn%gA&{$k0Yt&L8r~z~5{* z7SYZgJcC08-5SLd0r6EzVx?d)g2l{!&gCGzW`T{G?D>r3x4IOTtOgxcBoh^y?vTIg ztjEDKxfmR6xHMf5fHii|n9)3zP1D&5crECF;8b@c;$D;`JA7MX|dK6PdLttmcM2xM1 z-L#TTLk4WNF2MM}a4fKwbjA21c!^$kmg^NDc6c3TSwgr{T=p9t9u#eQT(A+YMLOOOu!=T20D^Fu z1XkZd6P>7lrisCb!3F1GVkH(KBp>iNxK3|7&gND{$E5R z1OOP@0uVFPp}fk-S~Nmij%rgAEg}VY*p>%X)!?8!;ew+sIDuRR(JDpoEjk!iJsPuQ zSsw&rgVEP0ThTE2sHkc<@__Rk96Jci$(I|BxUdZEj6`Tv$e}DI@eujt`~jB2FezM? zSQe!uCX1rMxHV{Zux@YOmY}n|LWF?quyWe%QV>hSgt1X0Ym-Y-xF1-fEryuQh^@kN zzKGtz^&07dM`(%#B8u46P{Wl(1BQ4MZORVTUJ0>+n=r+avPF!gS}xuetO;ThwHbYK z*ch=UN_dHL=ExkIgOeV`a)P!BW6eZyKUL@tdJBTJw=UM|#!4mM-$DazmcxOE(pXLi zv{dseXotn(nq8<`SOn|LpHL!$8$_PR-#i~uuRd5L- zgSK3{l8k>zxMmX4q~teQ|1wpnEv1EFVfVY;_U zm2#yJQ!^V)NtaE?yX~2fr(U4qMb_eM2&txlRB*o(M|(xc6G&hKz5+09XDSJ6kO=Uv zQVSPt<~q_)d`HXP!iqFkQAu_na>YD@x)Mx5bO}KnMmZ5li?BU8G~cn83hfRLM%ZAt z9Uq$O_&u}4#vsxq9Zg`%7Mi0~qBFJ^M@xfadr}2pyKF^ucHWVM>WPF~s0jYD!L-mO zorCB+N44mjCkbuvKeUA;6`TqDVM*FejgW^PmdC|dcoX1K5c2?@%YY|6G*w8!E0Gm14x8d6LIPvnD@LOAIuTAM*2v-LWuM1eQ8{Tif&>mm_;aY4 z#^b{gL<_|-*-|??@n`Mzl#yxi&e2h2yA4NMzGz2RcnzaSCZk1&Oh=Y9q)}AR$%^9a zm%LQGFdS(z&D2rjb)wD)_(;fl7+poWY02>gRds7R#36nRixVz)$z983y{;ffQTY^8 zSL{iTyMmz2Qt4}K88A%gWUq`2=r_edm;(IvD>hyP8_%OTG=h5z{ziXqG#RZGiw*;C zK$%3!YY7@ioXnb1QWZ|e&dPSPr<_aL+_^OFCOoG8VooVK`c1r(CW#gmAWC|D!A2*oN_VkoE2l*~B_)cHT8A5J85p@8houUx3S?^Pz+*ZYUrir69C{#gnYce3XIv6)=nJhJQOx%zd3UI@P zW-${SQQVN=fe1y4bgLd@okGxS3+Kcb(&#lc91(*IiPK`x$Wj^9Zx5%bphe1uHHDX9M45@c;K*7nL%6IT+6q4arqMR$jsD#H=AL+df=TaI$p6Ikea990kPN z>X6Ps?!kkFh(J5m#=%eJ_yz;m!QcsqwE+uF##JD7E=l2=G z6Ux!Jj}g4ev6SSs_Zo~(1WcAHePXemEh|N>^I%D8s4iA2D?*LeYJ4NBW+O*{O5{LIQWz|9 zEiH;wRVnA8n7CAhYFA&jm{x{|u9j=<%GfADjx|iOQc-xITaZE%ltMCF99qkeeCK?f zSX!BMpgP%?9V$-_3o%g394<&|CDo;Qt@OFHkXqWG{JMdmM9F7BmcAs}M;4)z#ceHA>(sJ$LaENW*qd~lLj?W-m$`M#tK)xhC(4b>e-^&TF7 z_>ih%vhd&An&GX`@|+`U$WRN)mcPDQYw1I5eMWl-u`V-MmKn@PRcnt`d~Tlr6wd!i z@flL8oziRtm1U@|X5lObH(~jpg0(d~1j`(*PY?AOI;O!R7}kX0~b7$rgojQimmrd7ZBc#o*Bu^H8=Rh7@16KAZ~9!a^mnywYScFV_z~3>a}ZOyNWF z4vb_L97&T|NcK6z6yIky+xfnf&6@19iw0ZLl;j;oair$9cOcTalQ1;Y;53F2GY%f> za((;az{;KLr=N?gA7@6^k3Dtg*zT_G?n(Sdk&mC9AN>TGK6Lw`Ybsoas2KW?-U(yF zjrgX3G>Kb$RiEH0oH2u6wD}Q+E zw*7^_TzOLV%by+D|DXSM_EP#6r4ujjd60ZF_Dn49nKWt`ZmD_WG`ie{<#B z&vsqPFS8xK?YU=WDKkT7a<^xmzAL|S-;UaW?wW*IrPJm zQs=$(#GywI$|pVf)(Zl+O^2+SFsXM!*ZA=hyShO2j0iILjC5^SIB*9>;VY#PmteF< z#MGC}miQ!ML-g7dr;j@eS&S^)Ie({a!)XJb@nw0$SMBagp$(a*eK2F(>~XWs9^-uL zvG0#R{D*HJvFvM3_($kR^##Zb?RAf~fN^8HCtif~4Cp>DOjtIq8?=A4F@Pp?jal_x zaP^pdS9kyQrf;o$`SoAlXxS1YztOktqWiwA-1+jB=jm~4#Xa(!>~9p)%J=$P%eucn zIP0G5)ii5*kiZ@4B}=B^n#cWGwF>#yA`KVqA7 z>CRQ#b{%go9XH)=5ib3HWTNfW*CO`PqMP6LXQE4D(@vR9y>ad$YbCb-kLNef+H&92 zvG>1y?WeQfYskm@_uhVC|G#_g`11KK^B=YpPt#9r-L-eOaRc+<>TQ=~{L)7MLp%1| zWjH+fu1zame&e`vypLS@F#Y0R9!+iEcJhJWKYzjFpS}9$2X1PtSv>1}{!eT1%tvP> zA9(2P6W>~O%aP>!-+sv9nFhFl18yuCz>V|xr{4A0f=?c`RY+=-MGqO?2_tPDP7w}Z z+|(21b&UAy0An^XOY1lGgz24roT5~9PE>;lgAK755Tn6pGa&#IfEY%T0crnB{Lf(5 zKLd~_iCkCD%8SQsc>Ie8_pkZrv~?fOK`Q*>rEjcgE&Sd0SNa{~p4x9ed1&9E_zkz5 z{^gVn%U|vL@x+g3V$$RL4hmE6 z`u*H1{i`mRyTyI=*43Xpx9!4LfAiCmXKYX2kAAc3-&U)w3l=`N|ApX@hxb4FgC+MQ z=&Mdz;CSHG$GDZxe|&h+rt8ny_@A##bDhl|IXFkYd{KVmgPXFmu2Zjj_{4iNcV0$3 zaLJ!0bfZGgFV8va*C#&t)~z>f+EX59zN2x)-|x6|06;!K*8Lgm4eYNCAmfp-V1}bG zr1!Zj`Gfl8FAwLhT)OIKpON(1Nox>%5HkQn?1&XH4?Y8iob_L0h<3BC44MN-vQ(S* z^l@h*i;x97&)9kThIz-t5v})8Fmg;_8wDd>6OiL3OljXU1Q_W){_AjL*|WE9yYjbN zb!SZe#z(3uZ5-?Hll|E8HIuG~MX_teMmdlc!>)&3uz z-M8_)bN63-n(9CJTIi;}_fA;1mwzNQb*^_x?yUKfUbJof&RXfL^MAGC+Ul9Y> z?Z0l*n}(Y&3GL@Nbl)8(%rm&i%g$f<;lm$av0&T}58S$G<$8I-bxYsb?EGq5C`5T{mf0%H>V#GEDKKbo*18Z=MO&n+qFBM4eZ$)3rJfg-KG2N>06iR zl+r!tb|F{X>wLE7Co4YbrOrC#i7%hI`@R{kt~vGPr&rv)?3agoW{lZ7b@v&wmsVd| zKK=F7>|1vAZzY4_y}#0WZlC< zox72B_nflsPEM4w$s#Z6fHhY$*@})IzBH;!HFe2iQ&&pDonIXU$hCr4(P^P*soMD_ z4c~~`Us0#c0Wok2=xqhw3dypdgSe^y9~1>0uM~x>7TH&HN?KQ|h)YIIR8rtuZ8GRN zG`~jVOXVaa1VB6(v)V~9EIrCJ+cfAr>vmsXO$83k1&l9G28I<^_}bXBPR_-aFfRxrVc$!>_Sc*dYC-k(9;gG>_(fxYP7*! zYh=Oj+i?b<%&)(8+|0da8a4zHEpc1oHq)bD2&W-u4|g&fPDM@`adrOs5o{UaCWIUA z+G8LPCo*Niq|W{Ou@k|N2Y+{WcaOOURBZUUYZWrFb0L4?==QTlY9dSlMmnbN$3K7I zaq{U&XIyp4Ti4vqY}&o~g_mc&?&|r=jfbvX@t66({>AP2XR3SaH-BROz3tPN&lpF) zyXQNeKR@{F?5f$|{KELd?H(z+OHuf~vM-x`CGrmQBczEisu6W{!iK?=1*1c0N zpkKdnP4}J;-kkRdOZGIrc_4Uu)%v5$X3$6e`tk**-uCC&r~Kr#=09Hg)BZm`==%3d z_(jH<8+Y>fCv)fR|J8)-<;`2UJy&h}XyaeEpOd?I?!n)leEH%o_1@QY|MU4?_xlaYF$>3IX&RO#uBsBO%cL!GE5MfcTB1 zUXHc1kBr2#JRb~Dw-kUt4hDz->g4nYq6a#Ag)f9wrCm!cLA9C~LnSPO!y`Ot)W%gq zgHmZH`_wh2p*EC$Wp>)s)ORR7gABqL-8@T2&eM{XbK1TKYb#J&<4({4;eT^S>zC!6 z#C#2z*+0z+@#ZBx4IGl)vrR&Q|6*7>WhPMOQtpi`!*m5;q~p;~aT%XWrtut^!X`<( zhOq9aod+_KimXAK>?1dPyO4L(cNgCBnhw(U)yG$i$0G9&nOLQXao4)%Eo?I16v6w6 zd5Ty$M!dvU$K1{S`t>aq-S=;6O%qdnpk8{Op}&BY3^zTrrSeGdavg||D3R8;bEou$Rpjxty)1m@b&e=QM?@r2i)EuPq8s)w$A~Z^F1B~Tr zGagcSUZ0PlwG&r4G+b0?$+uN!>~>Pr==DJT^?(sx36crcz58nM$%zO`(Mce3%)os8yfxG79JzhF0Ti{QZO^^7-7~l;6Gd zau%hr+-jw^$OEhc;#MXuTFw4&z=KBSDt4L_-}%K@r*z2Fi8LapIIwX|yqs=Kh_CTX zsk&eKQ-Ib6xFr?nz^woBl6b*D_-IVh0ByD)#j$m-cum1BY`4E&JfZj8dcDTJH#e_* z7{_|+h~PncwV7;mQ{!FiT*6_JVyTNSQKv!trrcd64~q+WkNYi&w@Yjuj0mBH>(2(c@q&ys)iORMkj>vXOy^%yg*j8P=!ieZbCn@7AZm+JG(MOY7?OYX)^CwRMD zsXJp+DHi%@G!T>=C(62oh~(;?T*%j|;)2CWwjTrlb~L!}+*p2*c;m)b!;>Sg>SFv2 zk-w`wh`f#)@2AeMSe4jt@A}09g=M}rkJ4|7syW28)l=_msLHMEf4}*|5r4g!XS5o`4oVrXr|?;6gMv0O@$B%@aDLAV&0z%T4dpuKhj+c^J`$%)6}5Q! zi5agZ3MXL>o*s*cJD^{`Fjm!mkbw25I?`$@&gko~(U%i%CKR%ROrM$VaL?TTQgryH z6D%TkseY-=P`|DlLvl27WDF+%wPrJS#(y*!N?Z802(t{J#m{{~L3+jp%8fDE69r?tOz<@+pIV zj2T6M@b|bi_}Tf;+|_(-%OhFeX&(>6(p|XX;asL~RlO=b#Ef#pl5aO8)&Rt6f7l)s zyC*3n&4$_;&Gn9K6yXEw-SP=8A$V9{WaaL&!C)P;ZwAN9py0nXNz(_PSKNe`k=H*< z5EuITb>W4hz*{dRn+Y_&uHoU+8LZDbf?q8xIHy<_1tlR5eXz;IxjGk9mqO9!9U&&SXrsbTyo={cGk( zjTP2;L19_v=j!sRqWHvYNH*%A2q?piL8xOiH!94x;r#x3vf_Lgr?iNgQRRtG61&N* zIOWVr0ClEEf#9f)y@2{v=$U^J7004@BpiZ43nyN>>;#{fP?=}_a@+(6!FfR@!`v|= zhUOn*FH>0`1~?#8M@0bL zIEIDyIDfv$yd(P#A3TIAsbac^9b(KK*|OCV5pP$G*0xi=Ouy|T3u4GT$|{L`c2N?& zJ=N+nr+2oCByz%V48^g7_ru3R&OLl_ywQWCOAWP(6%FZ6KmOwp|A)plBxAH?0xnA{hY+)D!l421qE& zz2r0PQfsL{y-)(mp3_Y_jm+*hD&v*Z#mlNZXy4#B^T&YN_ z!LBVY>=KS|KEF^dLa$HhFh-{>nUsMj?&-(*z2KOF;67|fo7L&%|Mo0`0B9K|A9w(BZ#@f)GN*QW|`TLDZUw{|wjI;ugWU|=Os4;h(a8mr9U6fp|?gHofiHg97b0HEM zawd|a@dGN5Oj?=O29KJQSCP}Ya~af%>OHBfW6@Zu(RoBC*9x>?0wLHVB(C0OkoUTC zh|Zh0HzGov)P9@}k|@lOzc?T_v~;2R?h}B_z1b`SM)!Au&1^r>d9Lg;;TYZdLz7ak zKQ1ff{2L9!#rb!=T;x>AqV0y{6on=%6Npf_ix1roWE(5*bNB`v-MOYdPhL)l_hoXe z2tqsFmCGxi(S8W0oowh?IsCwP+*QVYJW~owD{{C0LndW|Id)aqAmwFk;)!VO4VOc#SVa@eQE2{f2(UJK5GMm*F4SMh6Bo z!+RJz9hCd$$I1GUxed&dd*0IQ^Zo4#N+5I#)NH`>8cJa+@c|_u0cl=EPx0bH<=TrYF Gl>Y#ayuB^} From d9e8039a2509a59d839a217593c550090165fc40 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:11:54 -0400 Subject: [PATCH 115/391] Add files via upload --- certificates/BDG.mobileprovision | Bin 0 -> 12504 bytes certificates/BDG.p12 | Bin 0 -> 3293 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 certificates/BDG.mobileprovision create mode 100644 certificates/BDG.p12 diff --git a/certificates/BDG.mobileprovision b/certificates/BDG.mobileprovision new file mode 100644 index 0000000000000000000000000000000000000000..f7aad30edd74446d932642c2ee48f428e179a76d GIT binary patch literal 12504 zcmd6N3Ao$TwQkPIJRSle0YboJVmLXmJlkm!B3ZI5+mbDLc4(+A$?_muKAvQ|g^&OV zBtY77{gzVG$EeBb$E zZEfwn_TFp#>tAbaWIgiYgvsY@aBui*de_A6o$HZDk@d*l?yfGwRAlmm@x3#~cAqkS z46-Va!K4b@Ti5dbCuMMZF#m;ktm23WjtrhF(uF^L6tx)oN*( zUazX^(xldFDX$c?jum~NtdzuZwdsSw`rw15LRFX#({x53B@VO-S-!e*%H-vFvAGha zqi`l!5SQzP!`kK#?Xk#aydD2XrsqxcUa6});n@E{ZKbe5NBCz4~i$N!=efBu+oSa&3%Zy z&u9x7tjmnHWoBz)xxW2sK&F74tfrN6;VS|REiUU1j+lC=xlulLdp)Zi=csqfM~h2U zVsOYJoh&9LQ5c#yos=u0esHgGu`bHmK{?^9V54B$#L7_L!R1e04r@uSq$$c($DK~h z8OU?^L0p*Uus!>r0maXiAZ zIF45_-i=ifK`fRsc`6CFP)d+ti6bM%M6^-o$w-rOdE^x7LXuHOjc;OjBIbeLiBjCy zX`=|2K_D}Az91uFi15*9oB;M< zN&+P?995dZylVD`!bl|I^1&D?tq!c$*(ilU)>J`TqudhQo>IIRX|K(n#e69fmq|7; zCLYa{QU$IV4`R-E)KaF129m%DkdGK#o0ra)Xg{7eFs=v}Y!MR3-gzgfv7pg`#G@4{ z6m==ds3p%A2#GDYYA`k}knHGn=Vd>fDMo$eya*SH`kg3iN457($qT;;RLy18Oo`X0 znQFl5(WmLUH6S>Ul9WyR3mL`Zt)mUqlS<18J>#)UCXON)uHuPeM#5Gu=AsB~_L~U3 z%@%Ds^$c1J;%>CRS}?ZUfkMWQ^;-)GL$q#YByZJ6>I3=)O-54zTJ*=t{$j7h3P!7~ zX-X?%!sIdvt_)XBR02LBX%Xe1vf)DvibkSOO1UXrHe-e$3q!&FR?%9upBFqQ|~VioC-Qiol0w?o1v@v2#6^+N` zq?h~#gHU?16U%b zvd|xJNgF3#0K9NBTEBce=E=icGG8#XV60lgiNQWu^2r!ig8!k-QJ~zBRwAf=MHZfP zXya)zw7>fbj=8ZW$OKpbYtrV6adIjesR?en2G~P7ofQ&g!!8Uk*Ojd*f}2zM7VNzV zPU)y~8@&&aj1c1#z}J%C&ev%yuHl5>VF^sHvNVk(r|={ zTtEzjqv=L0nRVNuvLk>yDnugcL*sSUV+p2QwuZCG#*J!R6ijq3k}*UUdt3(jLa1DtwhuijTy&l*^DMLVGrU?qY)1|y$GH*)tzWITn|fhjZhMj5?;}!A4$#453+&*CV~@OAED7d zQRoAXmd?tf$V}VIGyT$_MQW$TI~9sz3V=>{g4aAj18->_UY0U>2}`gvfdbl~zzk3$ zOFD!7WROakaF%eRNoWI)EJ+MU$0kUV-!OhJMvx6fCdEl@bM6Lp_l7 zD36ZW!4X?Bsc4vX8LM@Hbd@O*EEU)a4<4O2hNV!L0_Ng^b=g7wh?ytlCRntF5`<>i zE){7qArur#QkfiKphH}eb`rGHg;n5v0j#x3V__OK%7L(|5~ACq{Z9!6xxHs01Fcf< z93dl_RCquZWIz_ekV%qqE#dZLD8d6M2~5BZ9>7V{E(7eBF;Pm95h5955MWJNKnG2( zuo%N+9L;z_pkpCJB*cZ#JdVm5L-TNh>m8coYu4+m&?M4kSwjqM)PBksoJFDpoBh>& zb*ePxibR58miFLW-XA4e49R7DAz6+W=qiJ==6KVPWm*Yn%gA&{$k0Yt&L8r~z~5{* z7SYZgJcC08-5SLd0r6EzVx?d)g2l{!&gCGzW`T{G?D>r3x4IOTtOgxcBoh^y?vTIg ztjEDKxfmR6xHMf5fHii|n9)3zP1D&5crECF;8b@c;$D;`JA7MX|dK6PdLttmcM2xM1 z-L#TTLk4WNF2MM}a4fKwbjA21c!^$kmg^NDc6c3TSwgr{T=p9t9u#eQT(A+YMLOOOu!=T20D^Fu z1XkZd6P>7lrisCb!3F1GVkH(KBp>iNxK3|7&gND{$E5R z1OOP@0uVFPp}fk-S~Nmij%rgAEg}VY*p>%X)!?8!;ew+sIDuRR(JDpoEjk!iJsPuQ zSsw&rgVEP0ThTE2sHkc<@__Rk96Jci$(I|BxUdZEj6`Tv$e}DI@eujt`~jB2FezM? zSQe!uCX1rMxHV{Zux@YOmY}n|LWF?quyWe%QV>hSgt1X0Ym-Y-xF1-fEryuQh^@kN zzKGtz^&07dM`(%#B8u46P{Wl(1BQ4MZORVTUJ0>+n=r+avPF!gS}xuetO;ThwHbYK z*ch=UN_dHL=ExkIgOeV`a)P!BW6eZyKUL@tdJBTJw=UM|#!4mM-$DazmcxOE(pXLi zv{dseXotn(nq8<`SOn|LpHL!$8$_PR-#i~uuRd5L- zgSK3{l8k>zxMmX4q~teQ|1wpnEv1EFVfVY;_U zm2#yJQ!^V)NtaE?yX~2fr(U4qMb_eM2&txlRB*o(M|(xc6G&hKz5+09XDSJ6kO=Uv zQVSPt<~q_)d`HXP!iqFkQAu_na>YD@x)Mx5bO}KnMmZ5li?BU8G~cn83hfRLM%ZAt z9Uq$O_&u}4#vsxq9Zg`%7Mi0~qBFJ^M@xfadr}2pyKF^ucHWVM>WPF~s0jYD!L-mO zorCB+N44mjCkbuvKeUA;6`TqDVM*FejgW^PmdC|dcoX1K5c2?@%YY|6G*w8!E0Gm14x8d6LIPvnD@LOAIuTAM*2v-LWuM1eQ8{Tif&>mm_;aY4 z#^b{gL<_|-*-|??@n`Mzl#yxi&e2h2yA4NMzGz2RcnzaSCZk1&Oh=Y9q)}AR$%^9a zm%LQGFdS(z&D2rjb)wD)_(;fl7+poWY02>gRds7R#36nRixVz)$z983y{;ffQTY^8 zSL{iTyMmz2Qt4}K88A%gWUq`2=r_edm;(IvD>hyP8_%OTG=h5z{ziXqG#RZGiw*;C zK$%3!YY7@ioXnb1QWZ|e&dPSPr<_aL+_^OFCOoG8VooVK`c1r(CW#gmAWC|D!A2*oN_VkoE2l*~B_)cHT8A5J85p@8houUx3S?^Pz+*ZYUrir69C{#gnYce3XIv6)=nJhJQOx%zd3UI@P zW-${SQQVN=fe1y4bgLd@okGxS3+Kcb(&#lc91(*IiPK`x$Wj^9Zx5%bphe1uHHDX9M45@c;K*7nL%6IT+6q4arqMR$jsD#H=AL+df=TaI$p6Ikea990kPN z>X6Ps?!kkFh(J5m#=%eJ_yz;m!QcsqwE+uF##JD7E=l2=G z6Ux!Jj}g4ev6SSs_Zo~(1WcAHePXemEh|N>^I%D8s4iA2D?*LeYJ4NBW+O*{O5{LIQWz|9 zEiH;wRVnA8n7CAhYFA&jm{x{|u9j=<%GfADjx|iOQc-xITaZE%ltMCF99qkeeCK?f zSX!BMpgP%?9V$-_3o%g394<&|CDo;Qt@OFHkXqWG{JMdmM9F7BmcAs}M;4)z#ceHA>(sJ$LaENW*qd~lLj?W-m$`M#tK)xhC(4b>e-^&TF7 z_>ih%vhd&An&GX`@|+`U$WRN)mcPDQYw1I5eMWl-u`V-MmKn@PRcnt`d~Tlr6wd!i z@flL8oziRtm1U@|X5lObH(~jpg0(d~1j`(*PY?AOI;O!R7}kX0~b7$rgojQimmrd7ZBc#o*Bu^H8=Rh7@16KAZ~9!a^mnywYScFV_z~3>a}ZOyNWF z4vb_L97&T|NcK6z6yIky+xfnf&6@19iw0ZLl;j;oair$9cOcTalQ1;Y;53F2GY%f> za((;az{;KLr=N?gA7@6^k3Dtg*zT_G?n(Sdk&mC9AN>TGK6Lw`Ybsoas2KW?-U(yF zjrgX3G>Kb$RiEH0oH2u6wD}Q+E zw*7^_TzOLV%by+D|DXSM_EP#6r4ujjd60ZF_Dn49nKWt`ZmD_WG`ie{<#B z&vsqPFS8xK?YU=WDKkT7a<^xmzAL|S-;UaW?wW*IrPJm zQs=$(#GywI$|pVf)(Zl+O^2+SFsXM!*ZA=hyShO2j0iILjC5^SIB*9>;VY#PmteF< z#MGC}miQ!ML-g7dr;j@eS&S^)Ie({a!)XJb@nw0$SMBagp$(a*eK2F(>~XWs9^-uL zvG0#R{D*HJvFvM3_($kR^##Zb?RAf~fN^8HCtif~4Cp>DOjtIq8?=A4F@Pp?jal_x zaP^pdS9kyQrf;o$`SoAlXxS1YztOktqWiwA-1+jB=jm~4#Xa(!>~9p)%J=$P%eucn zIP0G5)ii5*kiZ@4B}=B^n#cWGwF>#yA`KVqA7 z>CRQ#b{%go9XH)=5ib3HWTNfW*CO`PqMP6LXQE4D(@vR9y>ad$YbCb-kLNef+H&92 zvG>1y?WeQfYskm@_uhVC|G#_g`11KK^B=YpPt#9r-L-eOaRc+<>TQ=~{L)7MLp%1| zWjH+fu1zame&e`vypLS@F#Y0R9!+iEcJhJWKYzjFpS}9$2X1PtSv>1}{!eT1%tvP> zA9(2P6W>~O%aP>!-+sv9nFhFl18yuCz>V|xr{4A0f=?c`RY+=-MGqO?2_tPDP7w}Z z+|(21b&UAy0An^XOY1lGgz24roT5~9PE>;lgAK755Tn6pGa&#IfEY%T0crnB{Lf(5 zKLd~_iCkCD%8SQsc>Ie8_pkZrv~?fOK`Q*>rEjcgE&Sd0SNa{~p4x9ed1&9E_zkz5 z{^gVn%U|vL@x+g3V$$RL4hmE6 z`u*H1{i`mRyTyI=*43Xpx9!4LfAiCmXKYX2kAAc3-&U)w3l=`N|ApX@hxb4FgC+MQ z=&Mdz;CSHG$GDZxe|&h+rt8ny_@A##bDhl|IXFkYd{KVmgPXFmu2Zjj_{4iNcV0$3 zaLJ!0bfZGgFV8va*C#&t)~z>f+EX59zN2x)-|x6|06;!K*8Lgm4eYNCAmfp-V1}bG zr1!Zj`Gfl8FAwLhT)OIKpON(1Nox>%5HkQn?1&XH4?Y8iob_L0h<3BC44MN-vQ(S* z^l@h*i;x97&)9kThIz-t5v})8Fmg;_8wDd>6OiL3OljXU1Q_W){_AjL*|WE9yYjbN zb!SZe#z(3uZ5-?Hll|E8HIuG~MX_teMmdlc!>)&3uz z-M8_)bN63-n(9CJTIi;}_fA;1mwzNQb*^_x?yUKfUbJof&RXfL^MAGC+Ul9Y> z?Z0l*n}(Y&3GL@Nbl)8(%rm&i%g$f<;lm$av0&T}58S$G<$8I-bxYsb?EGq5C`5T{mf0%H>V#GEDKKbo*18Z=MO&n+qFBM4eZ$)3rJfg-KG2N>06iR zl+r!tb|F{X>wLE7Co4YbrOrC#i7%hI`@R{kt~vGPr&rv)?3agoW{lZ7b@v&wmsVd| zKK=F7>|1vAZzY4_y}#0WZlC< zox72B_nflsPEM4w$s#Z6fHhY$*@})IzBH;!HFe2iQ&&pDonIXU$hCr4(P^P*soMD_ z4c~~`Us0#c0Wok2=xqhw3dypdgSe^y9~1>0uM~x>7TH&HN?KQ|h)YIIR8rtuZ8GRN zG`~jVOXVaa1VB6(v)V~9EIrCJ+cfAr>vmsXO$83k1&l9G28I<^_}bXBPR_-aFfRxrVc$!>_Sc*dYC-k(9;gG>_(fxYP7*! zYh=Oj+i?b<%&)(8+|0da8a4zHEpc1oHq)bD2&W-u4|g&fPDM@`adrOs5o{UaCWIUA z+G8LPCo*Niq|W{Ou@k|N2Y+{WcaOOURBZUUYZWrFb0L4?==QTlY9dSlMmnbN$3K7I zaq{U&XIyp4Ti4vqY}&o~g_mc&?&|r=jfbvX@t66({>AP2XR3SaH-BROz3tPN&lpF) zyXQNeKR@{F?5f$|{KELd?H(z+OHuf~vM-x`CGrmQBczEisu6W{!iK?=1*1c0N zpkKdnP4}J;-kkRdOZGIrc_4Uu)%v5$X3$6e`tk**-uCC&r~Kr#=09Hg)BZm`==%3d z_(jH<8+Y>fCv)fR|J8)-<;`2UJy&h}XyaeEpOd?I?!n)leEH%-$NNu#lMhi+P%|Mir29XZw zE_wOK@jM^j5BGK5=XqYoaeuph#|=Y}PZ9tLVF>aB2+5;}mk}ovKq4RpLH-VeAP=~) zePIYl-@g$AgMc*MSPcX~z)h?AHvz&-{}Escm==uxUr7$*1X0O!0NNuSaMeE%GPdT^ z;^QPBP-BIWLx}(HRRR()0Co>TVixfdXhjGDJ_1pVC$<^*y_nW``e~hBG=FyP9ReZ+ z)nE+JX~yid$Vqjv9BAwH0#^2=VkO6m)J}eU%dK2|+-A2?yE8=`w*aVr5xwNizX>4y zTn4*<(F`*rpZ)oe?j6de#ZRpCzLHT3e|#LzvNKeKHNvQvH%>)sNX|CJqwl9Y z55NKM-(dK+KQ;BfonK9nJdG{G{6<`D7o4t58W)rY4L$>8%eNB#O(63chrH2PJMCvT zRu)eq(u8zgAeGxiyLf*+x&>_y&=x%Is}LL;1j$vjw>=w4aglMyj#lAa^dHJQG-QO% zl*Kk$n_AMz((4siJ*eEi7qo?Ab1@+-HH^$(>LImdXVXk+zAv+v%dmsvG(Km|C%!(#tw!J(o}E zzxmREZ}b-xe1lJdr-~)^&jKfeN?uwQmL6dyi>DU4`c2^}orqk8nTrDR7A1-_Ct1Lp z!uz*N-}`OBegbfHCC@#=>-Rcs>+ESx1_mNhobsDrN$73l152OBgva}N4$^fdvwE=U z;q!+cpDni@x$571&!&^y&XgPNFDUB+Wj1$QpEAXU|;Llt|j6#oe*ZX^-Lp++z`(g^Z#}CG=84kCi$8pf&)a}8f z;j$Y2q-!s;>Dg_sTNN*BJvqj+TOIC}OcJ(9j53Kt9tOvO){*$pRT}v!;skN(E z5^vXn0f~b7xNxAw-nPK=uWg^13*6n(3JfN}HI=KR9~IdRgLER#QAAlbLw%S`QTMFq z6B0y}&L@Xs4peG6CV085H^qLt-{wz5bMY>R5y5v7=!Er@d{NFk->t4~G(NWyPQtUR zVN-Rgv=n`=Yqh^n&77;^obS8#k@bC0vUG=y*9n<*3k;8NgKNh<4b;+l`%V#c`MDRG z*C&Ze3QG1JOrv@HCUC**z&}Fl=Voi_q?D125Jb=zED*^zW{LHaV^nqCf_EzGyg%21KVH&y zMrKxbe+HY(lU<&e4I6WR_;%RiCJ{IBVzF_y{~nPbZ5-8xdmSw#7=vj&X+V=cnGo@} zyh=W3!dm{~?YTP3HRdNX9X^0aad*;NRLc-T2wRvvg&p`>HqyZY=d?%EV=f43T$X$S z{?#IzmWr8$x65bNF#Jvwx25CP4{vG~#`Eq(#yg7b=0Cz}4J3pTtP5TGD`_M$h)Ubc z9TN~F8Q7ZV;Sdnjkc||30rqx%QmO|eHD-ZawGrZ^vf$6s3bi)X1EIF7)N@f?48!=1 z`C}BH&BN!+5HFb_Ckmn57THWzIzpMGbV|ed`-$DQ&GmwRK8xWC<5yUdG((USG&Dq_ zm@Gy6Q6U9Jo)3A`x&_90_oEl@`4aEV2QTRC35tXHZC=p6wU>Gbv>lnl4K4e$SV{j8`6CHG7A>+kKhb*!YqJiug)&;-v$o%o8}YbC1LT{aZ8ap(P6Y zY0obeY}hzwzf5s)?IiX+LGY?d_3&&y#~Ig|@L$2E5Z-V1G)A}> zO-VI-lvmRZLVyNcFBXwI^kx!L!yN0jN)hMk4aE{B&G~K}Z(0cFzL9?yn_;pR;!Qi+ zdw#ooz4G8;;^4}?g35H19a86el=yYg3C6sH;14}RxfV8s-IC|e_o9aH8uTTt-Vz7M zUl>mK5d)eW9zb}N3NCMbn!Ppmjo?XoO|RqYuAh6F{#zWA$r^8p5aB+T{svv`Zwov2 z$0Fw{?9b^!i%IqOcWC%DT%+l()52bM;0ODyZ3+>v0)4swb3gGLZ_irbD-ErPeP!xW-@t(7Vcc76d{s5mqAx3@ZJ zPVP=`!4jXD_M+RAXvZ(WB3(Tr=Q|7~GyC-LzXq0@78~hT`JUL2uR`CNgb{NIg+c?+ zCm)9jco3_|H^ia2qC6=8svozqak29GsdG`C%g-iqB(NJImrOyF2p!eYL0Bd*?s@E7 z>XwYuN|>+?slByXMS_ahwFIMqqqnX~8gG(>_bv^po`t3JjWzr+cD`-oOHy~i*(jB& zWht6&2kYbY@Nt-V@zOEE=_9WmPSm9+`n9%4`v{Uqj_D#~cq-~*aIkl!FmAPOS})vW z1e;Ny{TEp+I%=rm#f{$~G}L20Y!WkEXPMUS6wM~fG%ID8*t$oYPbCCIo`54;NtEjS z-SIGQEy>-X$X-S{>XKe@g+7Nqe!1n)Dger@Gk!UeDG3_q_2i3Yd$G3& zQ2=7qc8#X^E*dyP<)bCe89QHu$Y0jH##VG^=UM35Zl^t^uS!2VO`ERcNUCigU^=Sr=patG)#k7eQ>N2P_&~#0ZT46>Z|)5D?kj@9 zew0nXl0o;`+nKdWu<~T2g*0`Z2I3V1v`mAGNOYPv=xv@u@dRJ@muE6`^|&3%J`lWr z%*Co+a?FaKt(9tasuW6izIvG?AO3Mr`Q=&m_d@YO z-Xx7%zR6r^_@g?_*yl4AXQZ>mqApWjMhB@Udzi4w@B| z&BM)RzgbS`7U9#*BBW+IX2L{oy$tp%o4Gy9R8-lK0&qMX|{cW|i?v!k+)L zXh~@KVa!he$^aLDC%_!w0AK~E-gH}l9l#l232?u;vjSvps`LM5DmPP>FliV+jN+dU z0Ra#U0I_xyOSw!bXT)MNNd4fhc-^2|1gZ#_&BqtDYsN%$rmYe`10s>;d Fe*kt!7>)n{ literal 0 HcmV?d00001 From 0302191767d4fcaeb26f03e606676d2c881b9bf7 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:15:00 -0400 Subject: [PATCH 116/391] Update Sign.yml --- .github/workflows/Sign.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index 0bf8b13f..6fd43bec 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -29,6 +29,7 @@ jobs: - name: Run sign script env: P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + UDID: ${{ secrets.UDID }} # Add your UDID as a secret in the repository settings run: | node <<'EOF' const axios = require('axios'); @@ -40,6 +41,7 @@ jobs: form.append('p12', fs.createReadStream('certificates/BDG.p12')); form.append('mobileprovision', fs.createReadStream('certificates/BDG.mobileprovision')); form.append('p12_password', process.env.P12_PASSWORD); + form.append('udid', process.env.UDID); // Include the UDID in the form data form.append('save_cert', 'on'); // Set to 'on' to save certificates axios.post('https://api.ipasign.pro/sign', form, { From 8623b9b4015653761b4df66802c46b12d70a295d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:19:55 -0400 Subject: [PATCH 117/391] Update Sign.yml --- .github/workflows/Sign.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index 6fd43bec..3c36155a 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -27,6 +27,7 @@ jobs: curl -L -o certificates/app.ipa https://github.com/BDGHubNoKey/Backdoor/releases/download/v0.0.8/feather_v0.0.8.ipa - name: Run sign script + id: sign-ipa env: P12_PASSWORD: ${{ secrets.P12_PASSWORD }} UDID: ${{ secrets.UDID }} # Add your UDID as a secret in the repository settings @@ -49,8 +50,12 @@ jobs: }) .then(response => { console.log('Install Link:', response.data.installLink); + console.log(`::set-output name=installLink::${response.data.installLink}`); }) .catch(error => { console.error('Error:', error.response ? error.response.data : error.message); }); - EOF \ No newline at end of file + EOF + + - name: Display download link + run: echo "Download Link: ${{ steps.sign-ipa.outputs.installLink }}" \ No newline at end of file From 98efe5619c53f6e93cabe8ed5fe9af6248c2f32d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:23:03 -0400 Subject: [PATCH 118/391] Update Sign.yml --- .github/workflows/Sign.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index 3c36155a..28fbb2b7 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -1,4 +1,4 @@ -name: Sign IPA +name: Sign and Download IPA on: push: @@ -7,7 +7,7 @@ on: workflow_dispatch: jobs: - sign-ipa: + sign-and-download-ipa: runs-on: ubuntu-latest steps: @@ -27,7 +27,6 @@ jobs: curl -L -o certificates/app.ipa https://github.com/BDGHubNoKey/Backdoor/releases/download/v0.0.8/feather_v0.0.8.ipa - name: Run sign script - id: sign-ipa env: P12_PASSWORD: ${{ secrets.P12_PASSWORD }} UDID: ${{ secrets.UDID }} # Add your UDID as a secret in the repository settings @@ -49,13 +48,9 @@ jobs: headers: form.getHeaders() }) .then(response => { - console.log('Install Link:', response.data.installLink); - console.log(`::set-output name=installLink::${response.data.installLink}`); + console.log('Download Link:', response.data.downloadLink); }) .catch(error => { console.error('Error:', error.response ? error.response.data : error.message); }); - EOF - - - name: Display download link - run: echo "Download Link: ${{ steps.sign-ipa.outputs.installLink }}" \ No newline at end of file + EOF \ No newline at end of file From 060cbd8cb40bcd9acbb4994eb3ac893665f0c310 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:25:24 -0400 Subject: [PATCH 119/391] Update Sign.yml --- .github/workflows/Sign.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index 28fbb2b7..6fd43bec 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -1,4 +1,4 @@ -name: Sign and Download IPA +name: Sign IPA on: push: @@ -7,7 +7,7 @@ on: workflow_dispatch: jobs: - sign-and-download-ipa: + sign-ipa: runs-on: ubuntu-latest steps: @@ -48,7 +48,7 @@ jobs: headers: form.getHeaders() }) .then(response => { - console.log('Download Link:', response.data.downloadLink); + console.log('Install Link:', response.data.installLink); }) .catch(error => { console.error('Error:', error.response ? error.response.data : error.message); From 1a5efc063270dd0a4e72ea0ba2e21451695f6178 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:34:15 -0400 Subject: [PATCH 120/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 147 +++--------------------- 1 file changed, 14 insertions(+), 133 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 77d80fb0..9315bf0b 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -84,14 +84,14 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // Set up constraints with padding NSLayoutConstraint.activate([ - navigationBar.topAnchor.constraint(equalTo: view.topAnchor, constant: 20), - navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), - navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 20), - fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), - fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), - fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor, constant: -20), + fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor), activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), @@ -263,7 +263,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData activityIndicator.startAnimating() DispatchQueue.global().async { do { - try FileOperations.copyFile(at: fileURL, to: destinationURL) + try self.fileManager.copyItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { self.activityIndicator.stopAnimating() self.loadFiles() @@ -288,7 +288,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData self.activityIndicator.startAnimating() DispatchQueue.global().async { do { - try FileOperations.moveFile(at: fileURL, to: destinationURL) + try self.fileManager.moveItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { self.activityIndicator.stopAnimating() self.loadFiles() @@ -311,7 +311,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData activityIndicator.startAnimating() DispatchQueue.global().async { do { - try FileOperations.compressFile(at: fileURL, to: destinationURL) + try self.fileManager.zipItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { self.activityIndicator.stopAnimating() self.loadFiles() @@ -336,7 +336,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData self.activityIndicator.startAnimating() DispatchQueue.global().async { do { - try FileOperations.renameFile(at: fileURL, to: newName) + try self.fileManager.moveItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { self.activityIndicator.stopAnimating() self.loadFiles() @@ -358,7 +358,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData activityIndicator.startAnimating() DispatchQueue.global().async { do { - try FileOperations.deleteFile(at: fileURL) + try self.fileManager.removeItem(at: fileURL) DispatchQueue.main.async { self.activityIndicator.stopAnimating() self.loadFiles() @@ -377,7 +377,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData activityIndicator.startAnimating() DispatchQueue.global().async { do { - try FileOperations.unzipFile(at: fileURL, to: destinationURL) + try self.fileManager.unzipItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { self.activityIndicator.stopAnimating() self.loadFiles() @@ -406,123 +406,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } - } - - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - openFile(fileURL) - } - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() - } - - // MARK: - File Operations - private func openFile(_ fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - - switch fileExtension { - case "txt": - openTextEditor(fileURL) - case "plist": - openPlistEditor(fileURL) - default: - openHexEditor(fileURL) - } - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } - - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as! NSItemProviderWriting) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } - } - - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - - // MARK: - Create Files Directory - private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") - } - } - - // MARK: - UIButton Configuration for iOS 15 and later - func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - } -} \ No newline at end of file + \ No newline at end of file From f1bfc63c95cb396658251ee3e0536f503e6c95d8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:35:32 -0400 Subject: [PATCH 121/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 125 +++++++++++++++++++++++- 1 file changed, 123 insertions(+), 2 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 9315bf0b..f4a06fb6 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -404,6 +404,127 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData present(activityController, animated: true, completion: nil) } - // MARK: - UIDocumentPickerViewControllerDelegate + // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - \ No newline at end of file + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } + } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + openFile(fileURL) + } + + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - File Operations + private func openFile(_ fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + + switch fileExtension { + case "txt": + openTextEditor(fileURL) + case "plist": + openPlistEditor(fileURL) + case "ipa": + showFileOptions(for: fileURL) + default: + openHexEditor(fileURL) + } + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as! NSItemProviderWriting) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + + // MARK: - Create Files Directory + private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } + } + + // MARK: - UIButton Configuration for iOS 15 and later + func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } +} \ No newline at end of file From ca8c1407e682da932ebefa51390693a0956c0f8b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:41:34 -0400 Subject: [PATCH 122/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 121 +----------------------- 1 file changed, 2 insertions(+), 119 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index f4a06fb6..d4d57a1f 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -404,127 +404,10 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData present(activityController, animated: true, completion: nil) } - // MARK: - UIDocumentPickerViewControllerDelegate + // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let selectedFileURL = urls.first else { return } // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } - } - - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - openFile(fileURL) - } - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() - } - - // MARK: - File Operations - private func openFile(_ fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - - switch fileExtension { - case "txt": - openTextEditor(fileURL) - case "plist": - openPlistEditor(fileURL) - case "ipa": - showFileOptions(for: fileURL) - default: - openHexEditor(fileURL) - } - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } - - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as! NSItemProviderWriting) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } - } - - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - - // MARK: - Create Files Directory - private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") - } - } - - // MARK: - UIButton Configuration for iOS 15 and later - func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - } -} \ No newline at end of file + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPath \ No newline at end of file From 57f3fba57211ce4d2521d748532163c3a3b0c8b3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:47:30 -0400 Subject: [PATCH 123/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index d4d57a1f..310a4462 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -402,12 +402,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func shareFile(at fileURL: URL) { let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) present(activityController, animated: true, completion: nil) - } - - // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPath \ No newline at end of file + } \ No newline at end of file From a9f7c6ee807b9f1aafea6664478e9451bfb88296 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:53:42 -0400 Subject: [PATCH 124/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 146 ------------------------ 1 file changed, 146 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 310a4462..7a73e5b8 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -256,150 +256,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData alertController.addAction(createAction) alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(alertController, animated: true, completion: nil) - } - - private func copyFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.copyItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Copy failed with error: \(error.localizedDescription)") - } - } - } - } - - private func moveFile(at fileURL: URL) { - let alertController = UIAlertController(title: "Move File", message: "Enter new file path", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "New file path" - } - let moveAction = UIAlertAction(title: "Move", style: .default) { _ in - guard let newPath = alertController.textFields?.first?.text else { return } - let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) - self.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Move failed with error: \(error.localizedDescription)") - } - } - } - } - alertController.addAction(moveAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func compressFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.zipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Compression failed with error: \(error.localizedDescription)") - } - } - } - } - - private func renameFile(at fileURL: URL) { - let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = fileURL.lastPathComponent - } - let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in - guard let newName = alertController.textFields?.first?.text else { return } - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - self.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Rename failed with error: \(error.localizedDescription)") - } - } - } - } - alertController.addAction(renameAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func deleteFile(at fileURL: URL) { - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.removeItem(at: fileURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") - } - } - } - } - - private func unzipFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") - } - } - } - } - - private func hexEditFile(at fileURL: URL) { - guard let navigationController = self.navigationController else { - presentAlert(title: "Error", message: "Navigation controller is missing") - return - } - FileOperations.hexEditFile(at: fileURL, in: navigationController) - } - - private func shareFile(at fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - present(activityController, animated: true, completion: nil) } \ No newline at end of file From 2b79094354e6ae5a5bb368d9e772e6ba85d10cdb Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:54:54 -0400 Subject: [PATCH 125/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 165 +++++++++++++++++++++++- 1 file changed, 164 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 7a73e5b8..0ecb6463 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -256,4 +256,167 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData alertController.addAction(createAction) alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(alertController, animated: true, completion: nil) - } \ No newline at end of file + } + private func copyFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.copyItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Copy failed with error: \(error.localizedDescription)") + } + } + } + } + + private func moveFile(at fileURL: URL) { + let alertController = UIAlertController(title: "Move File", message: "Enter new file path", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "New file path" + } + let moveAction = UIAlertAction(title: "Move", style: .default) { _ in + guard let newPath = alertController.textFields?.first?.text else { return } + let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) + self.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Move failed with error: \(error.localizedDescription)") + } + } + } + } + alertController.addAction(moveAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func compressFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.zipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Compression failed with error: \(error.localizedDescription)") + } + } + } + } + + private func renameFile(at fileURL: URL) { + let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = fileURL.lastPathComponent + } + let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in + guard let newName = alertController.textFields?.first?.text else { return } + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + self.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Rename failed with error: \(error.localizedDescription)") + } + } + } + } + alertController.addAction(renameAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func deleteFile(at fileURL: URL) { + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.removeItem(at: fileURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") + } + } + } + } + + private func unzipFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") + } + } + } + } + + private func hexEditFile(at fileURL: URL) { + guard let navigationController = self.navigationController else { + presentAlert(title: "Error", message: "Navigation controller is missing") + return + } + FileOperations.hexEditFile(at: fileURL, in: navigationController) + } + + private func shareFile(at fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityController, animated: true, completion: nil) + } + + // MARK: - UIDocumentPickerViewControllerDelegate + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } + } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsIn \ No newline at end of file From b8c9b4cdbb37575877b65c5f1c0968d11a2f27b3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 05:55:33 -0400 Subject: [PATCH 126/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 115 +++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 0ecb6463..ccaea3e8 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -419,4 +419,117 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsIn \ No newline at end of file + func tableView(_ tableView: UITableView, numberOfRowsIn + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + presentFileOptions(for: fileURL) + } + + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - File Operations + private func presentFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) + + switch fileExtension { + case "txt": + menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) + case "plist": + menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) + case "ipa": + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + default: + menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + + // MARK: - Create Files Directory + private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } + } + + // MARK: - UIButton Configuration for iOS 15 and later + func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } +} \ No newline at end of file From 41330fb654660bee60589a84a589b8f9a3d752eb Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:07:20 -0400 Subject: [PATCH 127/391] Update HexEditorViewController.swift --- iOS/Views/Home/HexEditorViewController.swift | 36 +++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/iOS/Views/Home/HexEditorViewController.swift b/iOS/Views/Home/HexEditorViewController.swift index 047a6ccb..bd5f3303 100644 --- a/iOS/Views/Home/HexEditorViewController.swift +++ b/iOS/Views/Home/HexEditorViewController.swift @@ -68,12 +68,18 @@ class HexEditorViewController: UIViewController, UITextViewDelegate { } private func loadFileContent() { - do { - let data = try Data(contentsOf: fileURL) - let hexString = data.map { String(format: "%02x", $0) }.joined(separator: " ") - textView.text = hexString - } catch { - presentAlert(title: "Error", message: "Failed to load file content: \(error.localizedDescription)") + DispatchQueue.global(qos: .userInitiated).async { + do { + let data = try Data(contentsOf: self.fileURL) + let hexString = data.map { String(format: "%02x", $0) }.joined(separator: " ") + DispatchQueue.main.async { + self.textView.text = hexString + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Failed to load file content: \(error.localizedDescription)") + } + } } } @@ -89,12 +95,18 @@ class HexEditorViewController: UIViewController, UITextViewDelegate { return } } - do { - try data.write(to: fileURL) - hasUnsavedChanges = false - presentAlert(title: "Success", message: "File saved successfully.") - } catch { - presentAlert(title: "Error", message: "Failed to save file: \(error.localizedDescription)") + DispatchQueue.global(qos: .userInitiated).async { + do { + try data.write(to: self.fileURL) + self.hasUnsavedChanges = false + DispatchQueue.main.async { + self.presentAlert(title: "Success", message: "File saved successfully.") + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Failed to save file: \(error.localizedDescription)") + } + } } } From 7b6a2416b17ae11a65d45cad5177a3ce0f9131b8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:07:43 -0400 Subject: [PATCH 128/391] Update PlistEditorViewController.swift --- .../Home/PlistEditorViewController.swift | 110 ++++-------------- 1 file changed, 24 insertions(+), 86 deletions(-) diff --git a/iOS/Views/Home/PlistEditorViewController.swift b/iOS/Views/Home/PlistEditorViewController.swift index 22395cf7..bb888d76 100644 --- a/iOS/Views/Home/PlistEditorViewController.swift +++ b/iOS/Views/Home/PlistEditorViewController.swift @@ -68,94 +68,32 @@ class PlistEditorViewController: UIViewController, UITextViewDelegate { } private func loadFileContent() { - do { - let data = try Data(contentsOf: fileURL) - if let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] { - textView.text = convertPlistToString(plist: plist) + DispatchQueue.global(qos: .userInitiated).async { + do { + let data = try Data(contentsOf: self.fileURL) + if let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] { + DispatchQueue.main.async { + self.textView.text = self.convertPlistToString(plist: plist) + } + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Failed to load plist content: \(error.localizedDescription)") + } } - } catch { - presentAlert(title: "Error", message: "Failed to load plist content: \(error.localizedDescription)") } } @objc private func saveChanges() { - // Implement save changes logic - } - - @objc private func copyContent() { - UIPasteboard.general.string = textView.text - presentAlert(title: "Copied", message: "Content copied to clipboard.") - } - - @objc private func undoAction() { - textView.undoManager?.undo() - } - - @objc private func redoAction() { - textView.undoManager?.redo() - } - - @objc private func promptFindReplace() { - let alert = UIAlertController(title: "Find and Replace", message: "Enter text to find and replace:", preferredStyle: .alert) - alert.addTextField { textField in - textField.placeholder = "Find" - } - alert.addTextField { textField in - textField.placeholder = "Replace" - } - alert.addAction(UIAlertAction(title: "Replace", style: .default, handler: { [weak self] _ in - guard let findText = alert.textFields?[0].text, let replaceText = alert.textFields?[1].text else { return } - self?.findAndReplace(findText: findText, replaceText: replaceText) - })) - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alert, animated: true, completion: nil) - } - - private func findAndReplace(findText: String, replaceText: String) { - textView.text = textView.text.replacingOccurrences(of: findText, with: replaceText) - hasUnsavedChanges = true - } - - private func promptSaveChanges() { - let alert = UIAlertController(title: "Unsaved Changes", message: "You have unsaved changes. Do you want to save them before leaving?", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak self] _ in - self?.saveChanges() - self?.navigationController?.popViewController(animated: true) - })) - alert.addAction(UIAlertAction(title: "Discard", style: .destructive, handler: { [weak self] _ in - self?.navigationController?.popViewController(animated: true) - })) - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alert, animated: true, completion: nil) - } - - private func startAutoSaveTimer() { - autoSaveTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(autoSaveChanges), userInfo: nil, repeats: true) - } - - private func stopAutoSaveTimer() { - autoSaveTimer?.invalidate() - autoSaveTimer = nil - } - - @objc private func autoSaveChanges() { - if hasUnsavedChanges { - saveChanges() - } - } - - func textViewDidChange(_ textView: UITextView) { - hasUnsavedChanges = true - } - - private func convertPlistToString(plist: [String: Any]) -> String { - // Implement conversion logic - return "" - } - - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } -} \ No newline at end of file + guard let plistString = textView.text else { return } + let plistData = plistString.data(using: .utf8)! + DispatchQueue.global(qos: .userInitiated).async { + do { + try plistData.write(to: self.fileURL) + self.hasUnsavedChanges = false + DispatchQueue.main.async { + self.presentAlert(title: "Success", message: "File saved successfully.") + } + } catch { + DispatchQueue.main.async { + self.present \ No newline at end of file From faf18a110d59a41573c852c7fa879e55f31f418a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:12:20 -0400 Subject: [PATCH 129/391] Update TextEditorViewController.swift --- iOS/Views/Home/TextEditorViewController.swift | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/iOS/Views/Home/TextEditorViewController.swift b/iOS/Views/Home/TextEditorViewController.swift index f9329f61..a9c5debb 100644 --- a/iOS/Views/Home/TextEditorViewController.swift +++ b/iOS/Views/Home/TextEditorViewController.swift @@ -68,22 +68,34 @@ class TextEditorViewController: UIViewController { } private func loadFileContent() { - do { - let fileContent = try String(contentsOf: fileURL) - textView.text = fileContent - } catch { - presentAlert(title: "Error", message: "Failed to load file content: \(error.localizedDescription)") + DispatchQueue.global(qos: .userInitiated).async { + do { + let fileContent = try String(contentsOf: self.fileURL) + DispatchQueue.main.async { + self.textView.text = fileContent + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Failed to load file content: \(error.localizedDescription)") + } + } } } @objc private func saveChanges() { guard let newText = textView.text else { return } - do { - try newText.write(to: fileURL, atomically: true, encoding: .utf8) - hasUnsavedChanges = false - presentAlert(title: "Success", message: "File saved successfully.") - } catch { - presentAlert(title: "Error", message: "Failed to save file: \(error.localizedDescription)") + DispatchQueue.global(qos: .userInitiated).async { + do { + try newText.write(to: self.fileURL, atomically: true, encoding: .utf8) + self.hasUnsavedChanges = false + DispatchQueue.main.async { + self.presentAlert(title: "Success", message: "File saved successfully.") + } + } catch { + DispatchQueue.main.async { + self.presentAlert(title: "Error", message: "Failed to save file: \(error.localizedDescription)") + } + } } } @@ -121,12 +133,6 @@ class TextEditorViewController: UIViewController { hasUnsavedChanges = true } - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - private func promptSaveChanges() { let alert = UIAlertController(title: "Unsaved Changes", message: "You have unsaved changes. Do you want to save them before leaving?", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak self] _ in From 15b528b7f3fd6a6765195e63f7ff175b39dff4b8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:14:49 -0400 Subject: [PATCH 130/391] Update PlistEditorViewController.swift --- .../Home/PlistEditorViewController.swift | 87 ++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/PlistEditorViewController.swift b/iOS/Views/Home/PlistEditorViewController.swift index bb888d76..7bb23fbe 100644 --- a/iOS/Views/Home/PlistEditorViewController.swift +++ b/iOS/Views/Home/PlistEditorViewController.swift @@ -96,4 +96,89 @@ class PlistEditorViewController: UIViewController, UITextViewDelegate { } } catch { DispatchQueue.main.async { - self.present \ No newline at end of file + self.presentAlert(title: "Error", message: "Failed to save file: \(error.localizedDescription)") + } + } + } + } + + @objc private func copyContent() { + UIPasteboard.general.string = textView.text + presentAlert(title: "Copied", message: "Content copied to clipboard.") + } + + @objc private func undoAction() { + textView.undoManager?.undo() + } + + @objc private func redoAction() { + textView.undoManager?.redo() + } + + @objc private func promptFindReplace() { + let alert = UIAlertController(title: "Find and Replace", message: "Enter text to find and replace:", preferredStyle: .alert) + alert.addTextField { textField in + textField.placeholder = "Find" + } + alert.addTextField { textField in + textField.placeholder = "Replace" + } + alert.addAction(UIAlertAction(title: "Replace", style: .default, handler: { [weak self] _ in + guard let findText = alert.textFields?[0].text, let replaceText = alert.textFields?[1].text else { return } + self?.findAndReplace(findText: findText, replaceText: replaceText) + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func findAndReplace(findText: String, replaceText: String) { + textView.text = textView.text.replacingOccurrences(of: findText, with: replaceText) + hasUnsavedChanges = true + } + + private func promptSaveChanges() { + let alert = UIAlertController(title: "Unsaved Changes", message: "You have unsaved changes. Do you want to save them before leaving?", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak self] _ in + self?.saveChanges() + self?.navigationController?.popViewController(animated: true) + })) + alert.addAction(UIAlertAction(title: "Discard", style: .destructive, handler: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func startAutoSaveTimer() { + autoSaveTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(autoSaveChanges), userInfo: nil, repeats: true) + } + + private func stopAutoSaveTimer() { + autoSaveTimer?.invalidate() + autoSaveTimer = nil + } + + @objc private func autoSaveChanges() { + if hasUnsavedChanges { + saveChanges() + } + } + + private func convertPlistToString(plist: [String: Any]) -> String { + // Convert the plist dictionary to a readable string format + // This is a placeholder implementation; you can customize this method to format the plist as needed + return plist.description + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } +} + +extension PlistEditorViewController: UITextViewDelegate { + func textViewDidChange(_ textView: UITextView) { + hasUnsavedChanges = true + } +} \ No newline at end of file From 15a5e93921beba4439669157c8e3186675959356 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:24:00 -0400 Subject: [PATCH 131/391] Update main.yml --- .github/workflows/main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d1b2b038..6edfd009 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,7 +1,9 @@ name: Create New Release on: - workflow_dispatch: + push: + branches: + - main jobs: build: @@ -32,7 +34,7 @@ jobs: ${{ runner.os }}-homebrew- - name: Compile - run: | + run: | make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone mv packages/* upload/ From acbd1f0be50f446fa9636bd7bef26d9a39e25f6b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:30:31 -0400 Subject: [PATCH 132/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 173 +----------------------- 1 file changed, 5 insertions(+), 168 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index ccaea3e8..41308e43 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -48,7 +48,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData button.addTarget(self, action: #selector(uploadFile), for: .touchUpInside) return button }() - + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -71,7 +71,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // Setup Navigation Bar let navItem = UINavigationItem(title: "Files") - let menuButton = UIBarButtonItem(title: "⋮", style: .plain, target: self, action: #selector(showMenu)) + let menuButton = UIBarButtonItem(title: "\u{22ee}", style: .plain, target: self, action: #selector(showMenu)) let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) navItem.rightBarButtonItems = [menuButton, sortButton] navigationBar.setItems([navItem], animated: false) @@ -257,7 +257,8 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(alertController, animated: true, completion: nil) } - private func copyFile(at fileURL: URL) { + + private func copyFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") activityIndicator.startAnimating() DispatchQueue.global().async { @@ -368,168 +369,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") } } - } - } - - private func unzipFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") - } - } - } - } - - private func hexEditFile(at fileURL: URL) { - guard let navigationController = self.navigationController else { - presentAlert(title: "Error", message: "Navigation controller is missing") - return - } - FileOperations.hexEditFile(at: fileURL, in: navigationController) - } - - private func shareFile(at fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - present(activityController, animated: true, completion: nil) - } - - // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } - } - - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsIn - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - presentFileOptions(for: fileURL) - } - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() - } - - // MARK: - File Operations - private func presentFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) - - switch fileExtension { - case "txt": - menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) - case "plist": - menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) - case "ipa": - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - default: - menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) - } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } - - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as NSString) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } - } - - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - - // MARK: - Create Files Directory - private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") - } - } - - // MARK: - UIButton Configuration for iOS 15 and later - func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - } -} \ No newline at end of file + \ No newline at end of file From 5b0936c4cb430fcd830cba7e266c142a76ffeb1c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:31:01 -0400 Subject: [PATCH 133/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 164 +++++++++++++++++++++++- 1 file changed, 162 insertions(+), 2 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 41308e43..b6643ada 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -366,7 +366,167 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") + self.presentAlert(title: "Error", message: "Delete failed with error: \(error. } + } + + private func unzipFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") } } - \ No newline at end of file + } + } + + private func hexEditFile(at fileURL: URL) { + guard let navigationController = self.navigationController else { + presentAlert(title: "Error", message: "Navigation controller is missing") + return + } + FileOperations.hexEditFile(at: fileURL, in: navigationController) + } + + private func shareFile(at fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityController, animated: true, completion: nil) + } + + // MARK: - UIDocumentPickerViewControllerDelegate + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } + } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + presentFileOptions(for: fileURL) + } + + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - File Operations + private func presentFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) + + switch fileExtension { + case "txt": + menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) + case "plist": + menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) + case "ipa": + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + default: + menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } + + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + + // MARK: - Create Files Directory + private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } + } + + // MARK: - UIButton Configuration for iOS 15 and later + func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } +} \ No newline at end of file From 3dd262a4cac5171b6bd54cede9abcf94a5c465ca Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:34:10 -0400 Subject: [PATCH 134/391] Update PlistEditorViewController.swift --- iOS/Views/Home/PlistEditorViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Home/PlistEditorViewController.swift b/iOS/Views/Home/PlistEditorViewController.swift index 7bb23fbe..296ca146 100644 --- a/iOS/Views/Home/PlistEditorViewController.swift +++ b/iOS/Views/Home/PlistEditorViewController.swift @@ -1,6 +1,6 @@ import UIKit -class PlistEditorViewController: UIViewController, UITextViewDelegate { +class PlistEditorViewController: UIViewController { private let fileURL: URL private var textView: UITextView! private var toolbar: UIToolbar! @@ -59,7 +59,7 @@ class PlistEditorViewController: UIViewController, UITextViewDelegate { textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), textView.leadingAnchor.constraint(equalTo: view.leadingAnchor), textView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - textView.bottomAnchor.constraint(equalTo: toolbar.topAnchor), + textView.bottomAnchor.constraint.equalTo(toolbar.topAnchor), toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor), toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor), @@ -169,7 +169,7 @@ class PlistEditorViewController: UIViewController, UITextViewDelegate { // This is a placeholder implementation; you can customize this method to format the plist as needed return plist.description } - + private func presentAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) From d5c5c41cc99415429cbe556d392ab285cd21e553 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:34:42 -0400 Subject: [PATCH 135/391] Update PlistEditorViewController.swift --- .../Home/PlistEditorViewController.swift | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/iOS/Views/Home/PlistEditorViewController.swift b/iOS/Views/Home/PlistEditorViewController.swift index 296ca146..0af096d2 100644 --- a/iOS/Views/Home/PlistEditorViewController.swift +++ b/iOS/Views/Home/PlistEditorViewController.swift @@ -1,7 +1,7 @@ import UIKit -class PlistEditorViewController: UIViewController { - private let fileURL: URL +class TextEditorViewController: UIViewController { + private var fileURL: URL private var textView: UITextView! private var toolbar: UIToolbar! private var hasUnsavedChanges = false @@ -59,7 +59,7 @@ class PlistEditorViewController: UIViewController { textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), textView.leadingAnchor.constraint(equalTo: view.leadingAnchor), textView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - textView.bottomAnchor.constraint.equalTo(toolbar.topAnchor), + textView.bottomAnchor.constraint(equalTo: toolbar.topAnchor), toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor), toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor), @@ -70,26 +70,23 @@ class PlistEditorViewController: UIViewController { private func loadFileContent() { DispatchQueue.global(qos: .userInitiated).async { do { - let data = try Data(contentsOf: self.fileURL) - if let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] { - DispatchQueue.main.async { - self.textView.text = self.convertPlistToString(plist: plist) - } + let fileContent = try String(contentsOf: self.fileURL) + DispatchQueue.main.async { + self.textView.text = fileContent } } catch { DispatchQueue.main.async { - self.presentAlert(title: "Error", message: "Failed to load plist content: \(error.localizedDescription)") + self.presentAlert(title: "Error", message: "Failed to load file content: \(error.localizedDescription)") } } } } @objc private func saveChanges() { - guard let plistString = textView.text else { return } - let plistData = plistString.data(using: .utf8)! + guard let newText = textView.text else { return } DispatchQueue.global(qos: .userInitiated).async { do { - try plistData.write(to: self.fileURL) + try newText.write(to: self.fileURL, atomically: true, encoding: .utf8) self.hasUnsavedChanges = false DispatchQueue.main.async { self.presentAlert(title: "Success", message: "File saved successfully.") @@ -164,12 +161,6 @@ class PlistEditorViewController: UIViewController { } } - private func convertPlistToString(plist: [String: Any]) -> String { - // Convert the plist dictionary to a readable string format - // This is a placeholder implementation; you can customize this method to format the plist as needed - return plist.description - } - private func presentAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) @@ -177,7 +168,7 @@ class PlistEditorViewController: UIViewController { } } -extension PlistEditorViewController: UITextViewDelegate { +extension TextEditorViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { hasUnsavedChanges = true } From 3a8e3bead048ba77fbe0e229a2a2b836c8c23b8f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:47:41 -0400 Subject: [PATCH 136/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 132 +----------------------- 1 file changed, 5 insertions(+), 127 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index b6643ada..8c0210f6 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -366,7 +366,10 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Delete failed with error: \(error. } + self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") + } + } + } } private func unzipFile(at fileURL: URL) { @@ -404,129 +407,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } - } - - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - presentFileOptions(for: fileURL) - } - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() - } - - // MARK: - File Operations - private func presentFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) - - switch fileExtension { - case "txt": - menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) - case "plist": - menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) - case "ipa": - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - default: - menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) - } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } - - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as NSString) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } - } - - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - - // MARK: - Create Files Directory - private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") - } - } - - // MARK: - UIButton Configuration for iOS 15 and later - func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - } -} \ No newline at end of file + \ No newline at end of file From 0e5375cfd1761f408d1c168151ae3336b7e9003e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:48:13 -0400 Subject: [PATCH 137/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 130 +++++++++++++++++++++++- 1 file changed, 127 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 8c0210f6..f4faba86 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -405,6 +405,130 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - \ No newline at end of file +func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } +} + +// MARK: - UITableViewDelegate, UITableViewDataSource +func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count +} + +func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell +} + +func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + presentFileOptions(for: fileURL) +} + +// MARK: - UISearchResultsUpdating +func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() +} + +// MARK: - File Operations +private func presentFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) + + switch fileExtension { + case "txt": + menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) + case "plist": + menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) + case "ipa": + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + default: + menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) +} + +private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) +} + +private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) +} + +private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) +} + +private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) +} + +// MARK: - UITableViewDragDelegate +func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] +} + +// MARK: - UITableViewDropDelegate +func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } +} + +func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) +} + +func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) +} + +// MARK: - Create Files Directory +private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } +} + +// MARK: - UIButton Configuration for iOS 15 and later +func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } +} \ No newline at end of file From 9d03fc08cb355f74be627062ca70271ede9654a7 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:49:29 -0400 Subject: [PATCH 138/391] Update TextEditorViewController.swift --- iOS/Views/Home/TextEditorViewController.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Home/TextEditorViewController.swift b/iOS/Views/Home/TextEditorViewController.swift index a9c5debb..c759a544 100644 --- a/iOS/Views/Home/TextEditorViewController.swift +++ b/iOS/Views/Home/TextEditorViewController.swift @@ -1,6 +1,6 @@ import UIKit -class TextEditorViewController: UIViewController { +class TextEditorViewController: UIViewController, UITextViewDelegate { private var fileURL: URL private var textView: UITextView! private var toolbar: UIToolbar! @@ -160,9 +160,13 @@ class TextEditorViewController: UIViewController { saveChanges() } } -} -extension TextEditorViewController: UITextViewDelegate { + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + func textViewDidChange(_ textView: UITextView) { hasUnsavedChanges = true } From 5e1c716785ba15191975a208f61c52a01f9e3dd8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 06:50:25 -0400 Subject: [PATCH 139/391] Update PlistEditorViewController.swift --- iOS/Views/Home/PlistEditorViewController.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/iOS/Views/Home/PlistEditorViewController.swift b/iOS/Views/Home/PlistEditorViewController.swift index 0af096d2..44547f35 100644 --- a/iOS/Views/Home/PlistEditorViewController.swift +++ b/iOS/Views/Home/PlistEditorViewController.swift @@ -1,6 +1,6 @@ import UIKit -class TextEditorViewController: UIViewController { +class PlistEditorViewController: UIViewController, UITextViewDelegate { private var fileURL: URL private var textView: UITextView! private var toolbar: UIToolbar! @@ -166,9 +166,7 @@ class TextEditorViewController: UIViewController { alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) present(alert, animated: true, completion: nil) } -} -extension TextEditorViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { hasUnsavedChanges = true } From cf882a61081a846b7dca9769ca74435cfe024759 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:01:44 -0400 Subject: [PATCH 140/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 486 +----------------------- 1 file changed, 1 insertion(+), 485 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index f4faba86..53e25888 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -47,488 +47,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData button.translatesAutoresizingMaskIntoConstraints = false button.addTarget(self, action: #selector(uploadFile), for: .touchUpInside) return button - }() - - // MARK: - Lifecycle - override func viewDidLoad() { - super.viewDidLoad() - setupUI() - setupActivityIndicator() - loadFiles() - fileListTableView.delegate = self - fileListTableView.dataSource = self - fileListTableView.dragDelegate = self - fileListTableView.dropDelegate = self - searchController.searchResultsUpdater = self - navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = false - filteredFileList = fileList - } - - // MARK: - UI Setup - private func setupUI() { - view.backgroundColor = .white - - // Setup Navigation Bar - let navItem = UINavigationItem(title: "Files") - let menuButton = UIBarButtonItem(title: "\u{22ee}", style: .plain, target: self, action: #selector(showMenu)) - let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) - navItem.rightBarButtonItems = [menuButton, sortButton] - navigationBar.setItems([navItem], animated: false) - - // Add UI elements to the view - view.addSubview(navigationBar) - view.addSubview(fileListTableView) - view.addSubview(activityIndicator) - view.addSubview(uploadButton) - - // Set up constraints with padding - NSLayoutConstraint.activate([ - navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), - navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - - fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), - fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor), - - activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), - - uploadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), - uploadButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), - uploadButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), - uploadButton.heightAnchor.constraint(equalToConstant: 50) - ]) - - // Register the table view cell - fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") - - // Add long press gesture recognizer to table view - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - fileListTableView.addGestureRecognizer(longPressRecognizer) - } - - private func setupActivityIndicator() { - view.addSubview(activityIndicator) - NSLayoutConstraint.activate([ - activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) - ]) - } - - // MARK: - Load Files - private func loadFiles() { - do { - fileList = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) - sortFiles() - filteredFileList = fileList - fileListTableView.reloadData() - } catch { - presentAlert(title: "Error", message: "Failed to load files: \(error.localizedDescription)") - } - } - - private func sortFiles() { - switch sortOrder { - case .name: - fileList.sort(by: { $0.lowercased() < $1.lowercased() }) - case .date: - fileList.sort(by: { getFileDate($0) < getFileDate($1) }) - case .size: - fileList.sort(by: { getFileSize($0) < getFileSize($1) }) - } - } - - private func getFileDate(_ fileName: String) -> Date { - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) - return attributes?[.modificationDate] as? Date ?? Date.distantPast - } - - private func getFileSize(_ fileName: String) -> UInt64 { - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) - return attributes?[.size] as? UInt64 ?? 0 - } - - // MARK: - Actions - @objc private func showMenu() { - let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - menu.addAction(UIAlertAction(title: "Select", style: .default, handler: { _ in self.selectFiles() })) - menu.addAction(UIAlertAction(title: "Import", style: .default, handler: { _ in self.importFile() })) - menu.addAction(UIAlertAction(title: "New Folder", style: .default, handler: { _ in self.createNewFolder() })) - menu.addAction(UIAlertAction(title: "New File", style: .default, handler: { _ in self.createNewFile() })) - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - - @objc private func changeSortOrder() { - let sortMenu = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) - sortMenu.addAction(UIAlertAction(title: "Name", style: .default, handler: { _ in self.sortOrder = .name; self.loadFiles() })) - sortMenu.addAction(UIAlertAction(title: "Date", style: .default, handler: { _ in self.sortOrder = .date; self.loadFiles() })) - sortMenu.addAction(UIAlertAction(title: "Size", style: .default, handler: { _ in self.sortOrder = .size; self.loadFiles() })) - sortMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(sortMenu, animated: true, completion: nil) - } - - @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { - if gesture.state == .began { - let point = gesture.location(in: fileListTableView) - if let indexPath = fileListTableView.indexPathForRow(at: point) { - let fileName = filteredFileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - showFileOptions(for: fileURL) - } - } - } - - private func showFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - - if fileExtension == "ipa" { - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - } else { - menu.addAction(UIAlertAction(title: "Copy", style: .default, handler: { _ in self.copyFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Move", style: .default, handler: { _ in self.moveFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Compress", style: .default, handler: { _ in self.compressFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Delete", style: .default, handler: { _ in self.deleteFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Share", style: .default, handler: { _ in self.shareFile(at: fileURL) })) - } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - - private func selectFiles() { - // Implement select files functionality - } - - @objc private func uploadFile() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = self - documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) - } - - private func importFile() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = self - documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) - } - - private func createNewFolder() { - let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "Folder name" - } - let createAction = UIAlertAction(title: "Create", style: .default) { _ in - guard let folderName = alertController.textFields?.first?.text else { return } - let folderURL = self.documentsDirectory.appendingPathComponent(folderName) - do { - try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) - self.loadFiles() - } catch { - self.presentAlert(title: "Error", message: "Failed to create folder: \(error.localizedDescription)") - } - } - alertController.addAction(createAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func createNewFile() { - let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "File name" - } - let createAction = UIAlertAction(title: "Create", style: .default) { _ in - guard let fileName = alertController.textFields?.first?.text else { return } - let fileURL = self.documentsDirectory.appendingPathComponent(fileName) - self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) - self.loadFiles() - } - alertController.addAction(createAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func copyFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.copyItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Copy failed with error: \(error.localizedDescription)") - } - } - } - } - - private func moveFile(at fileURL: URL) { - let alertController = UIAlertController(title: "Move File", message: "Enter new file path", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "New file path" - } - let moveAction = UIAlertAction(title: "Move", style: .default) { _ in - guard let newPath = alertController.textFields?.first?.text else { return } - let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) - self.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Move failed with error: \(error.localizedDescription)") - } - } - } - } - alertController.addAction(moveAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func compressFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.zipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Compression failed with error: \(error.localizedDescription)") - } - } - } - } - - private func renameFile(at fileURL: URL) { - let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = fileURL.lastPathComponent - } - let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in - guard let newName = alertController.textFields?.first?.text else { return } - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - self.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Rename failed with error: \(error.localizedDescription)") - } - } - } - } - alertController.addAction(renameAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func deleteFile(at fileURL: URL) { - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.removeItem(at: fileURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") - } - } - } - } - - private func unzipFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") - } - } - } - } - - private func hexEditFile(at fileURL: URL) { - guard let navigationController = self.navigationController else { - presentAlert(title: "Error", message: "Navigation controller is missing") - return - } - FileOperations.hexEditFile(at: fileURL, in: navigationController) - } - - private func shareFile(at fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - present(activityController, animated: true, completion: nil) - } - - // MARK: - UIDocumentPickerViewControllerDelegate -func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } -} - -// MARK: - UITableViewDelegate, UITableViewDataSource -func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count -} - -func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell -} - -func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - presentFileOptions(for: fileURL) -} - -// MARK: - UISearchResultsUpdating -func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() -} - -// MARK: - File Operations -private func presentFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) - - switch fileExtension { - case "txt": - menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) - case "plist": - menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) - case "ipa": - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - default: - menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) - } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) -} - -private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) -} - -private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) -} - -private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) -} - -private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) -} - -// MARK: - UITableViewDragDelegate -func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as NSString) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] -} - -// MARK: - UITableViewDropDelegate -func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } -} - -func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) -} - -func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) -} - -// MARK: - Create Files Directory -private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") - } -} - -// MARK: - UIButton Configuration for iOS 15 and later -func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } -} \ No newline at end of file + }() \ No newline at end of file From c5e6374946e1f1e7c4f61fddf41d299af7d478d0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:02:06 -0400 Subject: [PATCH 141/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 70 ++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 53e25888..7d43a9bf 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -47,4 +47,72 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData button.translatesAutoresizingMaskIntoConstraints = false button.addTarget(self, action: #selector(uploadFile), for: .touchUpInside) return button - }() \ No newline at end of file + }() + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + setupActivityIndicator() + loadFiles() + fileListTableView.delegate = self + fileListTableView.dataSource = self + fileListTableView.dragDelegate = self + fileListTableView.dropDelegate = self + searchController.searchResultsUpdater = self + navigationItem.searchController = searchController + navigationItem.hidesSearchBarWhenScrolling = false + filteredFileList = fileList + } + + // MARK: - UI Setup + private func setupUI() { + view.backgroundColor = .white + + // Setup Navigation Bar + let navItem = UINavigationItem(title: "Files") + let menuButton = UIBarButtonItem(title: "\u{22ee}", style: .plain, target: self, action: #selector(showMenu)) + let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) + navItem.rightBarButtonItems = [menuButton, sortButton] + navigationBar.setItems([navItem], animated: false) + + // Add UI elements to the view + view.addSubview(navigationBar) + view.addSubview(fileListTableView) + view.addSubview(activityIndicator) + view.addSubview(uploadButton) + + // Set up constraints with padding + NSLayoutConstraint.activate([ + navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor), + + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), + + uploadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), + uploadButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + uploadButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + uploadButton.heightAnchor.constraint(equalToConstant: 50) + ]) + + // Register the table view cell + fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") + + // Add long press gesture recognizer to table view + let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) + fileListTableView.addGestureRecognizer(longPressRecognizer) + } + + private func setupActivityIndicator() { + view.addSubview(activityIndicator) + NSLayoutConstraint.activate([ + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + } \ No newline at end of file From 444b509829135b16a3da822a2abd239c0017cfff Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:02:38 -0400 Subject: [PATCH 142/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 139 ++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 7d43a9bf..cbd74b09 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -115,4 +115,143 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) + } + // MARK: - Load Files + private func loadFiles() { + do { + fileList = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) + sortFiles() + filteredFileList = fileList + fileListTableView.reloadData() + } catch { + presentAlert(title: "Error", message: "Failed to load files: \(error.localizedDescription)") + } + } + + private func sortFiles() { + switch sortOrder { + case .name: + fileList.sort(by: { $0.lowercased() < $1.lowercased() }) + case .date: + fileList.sort(by: { getFileDate($0) < getFileDate($1) }) + case .size: + fileList.sort(by: { getFileSize($0) < getFileSize($1) }) + } + } + + private func getFileDate(_ fileName: String) -> Date { + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) + return attributes?[.modificationDate] as? Date ?? Date.distantPast + } + + private func getFileSize(_ fileName: String) -> UInt64 { + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) + return attributes?[.size] as? UInt64 ?? 0 + } + + // MARK: - Actions + @objc private func showMenu() { + let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + menu.addAction(UIAlertAction(title: "Select", style: .default, handler: { _ in self.selectFiles() })) + menu.addAction(UIAlertAction(title: "Import", style: .default, handler: { _ in self.importFile() })) + menu.addAction(UIAlertAction(title: "New Folder", style: .default, handler: { _ in self.createNewFolder() })) + menu.addAction(UIAlertAction(title: "New File", style: .default, handler: { _ in self.createNewFile() })) + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + @objc private func changeSortOrder() { + let sortMenu = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) + sortMenu.addAction(UIAlertAction(title: "Name", style: .default, handler: { _ in self.sortOrder = .name; self.loadFiles() })) + sortMenu.addAction(UIAlertAction(title: "Date", style: .default, handler: { _ in self.sortOrder = .date; self.loadFiles() })) + sortMenu.addAction(UIAlertAction(title: "Size", style: .default, handler: { _ in self.sortOrder = .size; self.loadFiles() })) + sortMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(sortMenu, animated: true, completion: nil) + } + + @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { + if gesture.state == .began { + let point = gesture.location(in: fileListTableView) + if let indexPath = fileListTableView.indexPathForRow(at: point) { + let fileName = filteredFileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + showFileOptions(for: fileURL) + } + } + } + + private func showFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + if fileExtension == "ipa" { + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + } else { + menu.addAction(UIAlertAction(title: "Copy", style: .default, handler: { _ in self.copyFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Move", style: .default, handler: { _ in self.moveFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Compress", style: .default, handler: { _ in self.compressFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Delete", style: .default, handler: { _ in self.deleteFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Share", style: .default, handler: { _ in self.shareFile(at: fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + private func selectFiles() { + // Implement select files functionality + } + + @objc private func uploadFile() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + private func importFile() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + private func createNewFolder() { + let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "Folder name" + } + let createAction = UIAlertAction(title: "Create", style: .default) { _ in + guard let folderName = alertController.textFields?.first?.text else { return } + let folderURL = self.documentsDirectory.appendingPathComponent(folderName) + do { + try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) + self.loadFiles() + } catch { + self.presentAlert(title: "Error", message: "Failed to create folder: \(error.localizedDescription)") + } + } + alertController.addAction(createAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func createNewFile() { + let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "File name" + } + let createAction = UIAlertAction(title: "Create", style: .default) { _ in + guard let fileName = alertController.textFields?.first?.text else { return } + let fileURL = self.documentsDirectory.appendingPathComponent(fileName) + self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + self.loadFiles() + } + alertController.addAction(createAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) } \ No newline at end of file From 49ee15392e990d42d971428c09557ec35ee00ac2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:03:07 -0400 Subject: [PATCH 143/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 164 +++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index cbd74b09..aa7ba3fd 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -254,4 +254,166 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData alertController.addAction(createAction) alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(alertController, animated: true, completion: nil) - } \ No newline at end of file + } + private func copyFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.copyItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Copy failed with error: \(error.localizedDescription)") + } + } + } + } + + private func moveFile(at fileURL: URL) { + let alertController = UIAlertController(title: "Move File", message: "Enter new file path", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "New file path" + } + let moveAction = UIAlertAction(title: "Move", style: .default) { _ in + guard let newPath = alertController.textFields?.first?.text else { return } + let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) + self.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Move failed with error: \(error.localizedDescription)") + } + } + } + } + alertController.addAction(moveAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func compressFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.zipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Compression failed with error: \(error.localizedDescription)") + } + } + } + } + + private func renameFile(at fileURL: URL) { + let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = fileURL.lastPathComponent + } + let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in + guard let newName = alertController.textFields?.first?.text else { return } + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + self.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Rename failed with error: \(error.localizedDescription)") + } + } + } + } + alertController.addAction(renameAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func deleteFile(at fileURL: URL) { + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.removeItem(at: fileURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") + } + } + } + } + + private func unzipFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") + } + } + } + } + + private func hexEditFile(at fileURL: URL) { + guard let navigationController = self.navigationController else { + presentAlert(title: "Error", message: "Navigation controller is missing") + return + } + FileOperations.hexEditFile(at: fileURL, in: navigationController) + } + + private func shareFile(at fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityController, animated: true, completion: nil) + } + + // MARK: - UIDocumentPickerViewControllerDelegate + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } + } + + // MARK: - UITableViewDelegate, UITable \ No newline at end of file From 21392b75cfd3f849f335ecae7185534c4f6b46d5 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:03:33 -0400 Subject: [PATCH 144/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 114 +++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index aa7ba3fd..d5c2407b 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -416,4 +416,116 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } - // MARK: - UITableViewDelegate, UITable \ No newline at end of file + // MARK: - UITableViewDelegate, UITableViewDataSource +func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count +} + +func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell +} + +func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + presentFileOptions(for: fileURL) +} + +// MARK: - UISearchResultsUpdating +func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() +} + +// MARK: - File Operations +private func presentFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) + + switch fileExtension { + case "txt": + menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) + case "plist": + menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) + case "ipa": + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + default: + menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) +} + +private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) +} + +private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) +} + +private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) +} + +private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) +} + +// MARK: - UITableViewDragDelegate +func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] +} + +// MARK: - UITableViewDropDelegate +func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } +} + +func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) +} + +func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) +} + +// MARK: - Create Files Directory +private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } +} + +// MARK: - UIButton Configuration for iOS 15 and later +func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } +} \ No newline at end of file From d834ef8928fd2cf053cad0b8e2fd426c79ac2a57 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:06:56 -0400 Subject: [PATCH 145/391] Update main.yml --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6edfd009..d3e08ff7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,6 +5,10 @@ on: branches: - main +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: macos-15 From 57ff51274199ae37c9554b7b2081e99ad62a33f2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:09:54 -0400 Subject: [PATCH 146/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 157 ++---------------------- 1 file changed, 7 insertions(+), 150 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index d5c2407b..c1b85e6c 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -48,7 +48,8 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData button.addTarget(self, action: #selector(uploadFile), for: .touchUpInside) return button }() - // MARK: - Lifecycle + + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() setupUI() @@ -116,7 +117,8 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) } - // MARK: - Load Files + + // MARK: - Load Files private func loadFiles() { do { fileList = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) @@ -255,7 +257,8 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(alertController, animated: true, completion: nil) } - private func copyFile(at fileURL: URL) { + + private func copyFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") activityIndicator.startAnimating() DispatchQueue.global().async { @@ -382,150 +385,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") - } - } - } - } - - private func hexEditFile(at fileURL: URL) { - guard let navigationController = self.navigationController else { - presentAlert(title: "Error", message: "Navigation controller is missing") - return - } - FileOperations.hexEditFile(at: fileURL, in: navigationController) - } - - private func shareFile(at fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - present(activityController, animated: true, completion: nil) - } - - // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } - } - - // MARK: - UITableViewDelegate, UITableViewDataSource -func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count -} - -func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell -} - -func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - presentFileOptions(for: fileURL) -} - -// MARK: - UISearchResultsUpdating -func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() -} - -// MARK: - File Operations -private func presentFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) - - switch fileExtension { - case "txt": - menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) - case "plist": - menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) - case "ipa": - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - default: - menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) - } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) -} - -private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) -} - -private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) -} - -private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) -} - -private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) -} - -// MARK: - UITableViewDragDelegate -func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as NSString) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] -} - -// MARK: - UITableViewDropDelegate -func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } -} - -func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) -} - -func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) -} - -// MARK: - Create Files Directory -private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") - } -} - -// MARK: - UIButton Configuration for iOS 15 and later -func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } -} \ No newline at end of file + self.presentAlert(title \ No newline at end of file From 648a5d04444b2c66d0dd5072433391b77d54caa0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:11:42 -0400 Subject: [PATCH 147/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 108 +++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index c1b85e6c..09c5f7a2 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -384,5 +384,109 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } catch { DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title \ No newline at end of file + self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") + } + } + } + } + + private func hexEditFile(at fileURL: URL) { + guard let navigationController = self.navigationController else { + presentAlert(title: "Error", message: "Navigation controller is missing") + return + } + FileOperations.hexEditFile(at: fileURL, in: navigationController) + } + + private func shareFile(at fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityController, animated: true, completion: nil) + } + + // MARK: - UIDocumentPickerViewControllerDelegate + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } + } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + presentFileOptions(for: fileURL) + } + + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + + // MARK: - Create Files Directory + private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } + } + + // MARK: - UIButton Configuration for iOS 15 and later + func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } +} \ No newline at end of file From 3ff7c5fed5a7996a47776a599b169312041bd4c8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:14:54 -0400 Subject: [PATCH 148/391] Update Sign.yml --- .github/workflows/Sign.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/Sign.yml b/.github/workflows/Sign.yml index 6fd43bec..d00f93a0 100644 --- a/.github/workflows/Sign.yml +++ b/.github/workflows/Sign.yml @@ -6,6 +6,10 @@ on: - main workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: sign-ipa: runs-on: ubuntu-latest From 0796259beabab9bc7d7cc58236f399ea06a5b550 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:20:37 -0400 Subject: [PATCH 149/391] Update repo.yml --- .github/workflows/repo.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index a81ce94a..f540cd72 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -1,7 +1,6 @@ name: Update repo on: - workflow_dispatch: jobs: @@ -14,7 +13,7 @@ jobs: - name: Fetch and update release info run: | - release_info=$(curl -s https://api.github.com/repos/khcrysalis/Feather/releases/latest) + release_info=$(curl -s https://api.github.com/repos/BDGHubNoKey/Backdoor/releases/latest) clean_release_info=$(echo "$release_info" | tr -d '\000-\037') @@ -44,9 +43,9 @@ jobs: size: $size, downloadURL: $url }] + .apps[0].versions - ) | .apps[0].versions |= unique_by(.version)' app-repo.json > updated_app_data.json + ) | .apps[0].versions |= unique_by(.version)' App-repo.json > updated_app_data.json - mv updated_app_data.json app-repo.json + mv updated_app_data.json App-repo.json else echo "No .ipa file found in the latest release or missing information." echo "Updated at: $updated_at" @@ -57,5 +56,5 @@ jobs: uses: EndBug/add-and-commit@v9 with: default_author: github_actions - message: "chore: update repo" - add: app-repo.json + message: "chore: update App-repo.json" + add: App-repo.json \ No newline at end of file From f6567e285102661911e1e64e5b531cdfd48afede Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Mar 2025 11:21:00 +0000 Subject: [PATCH 150/391] chore: update App-repo.json --- App-repo.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/App-repo.json b/App-repo.json index 1a511904..85730583 100644 --- a/App-repo.json +++ b/App-repo.json @@ -14,11 +14,15 @@ "versions": [ { "version": "0.0.8", - "date": "2025-03-08T18:35:10Z", - "size": 12375230, + "date": "2025-03-14T22:42:33Z", + "size": 12684769, "downloadURL": "https://github.com/BDGHubNoKey/Backdoor/releases/download/v0.0.8/feather_v0.0.8.ipa" } - ] + ], + "size": 12684769, + "version": "0.0.8", + "versionDate": "2025-03-14T22:42:33Z", + "downloadURL": "https://github.com/BDGHubNoKey/Backdoor/releases/download/v0.0.8/feather_v0.0.8.ipa" } ] -} \ No newline at end of file +} From b5ebb73726806c098687934a45d1a7193886ee6c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:26:33 -0400 Subject: [PATCH 151/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 119 +----------------------- 1 file changed, 1 insertion(+), 118 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 09c5f7a2..2f01f83b 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -372,121 +372,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } - private func unzipFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") - } - } - } - } - - private func hexEditFile(at fileURL: URL) { - guard let navigationController = self.navigationController else { - presentAlert(title: "Error", message: "Navigation controller is missing") - return - } - FileOperations.hexEditFile(at: fileURL, in: navigationController) - } - - private func shareFile(at fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - present(activityController, animated: true, completion: nil) - } - - // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } - } - - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - presentFileOptions(for: fileURL) - } - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() - } - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as NSString) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } - } - - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - - // MARK: - Create Files Directory - private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") - } - } - - // MARK: - UIButton Configuration for iOS 15 and later - func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - } -} \ No newline at end of file + private func unzip \ No newline at end of file From 53ae0f0ea049a930bcbf7f90dac416d1a2b56c66 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:26:59 -0400 Subject: [PATCH 152/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 162 +++++++++++++++++++++++- 1 file changed, 161 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 2f01f83b..e882301d 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -372,4 +372,164 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } - private func unzip \ No newline at end of file + private func unzipFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") + } + } + } + } + + private func hexEditFile(at fileURL: URL) { + guard let navigationController = self.navigationController else { + presentAlert(title: "Error", message: "Navigation controller is missing") + return + } + FileOperations.hexEditFile(at: fileURL, in: navigationController) + } + + private func shareFile(at fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityController, animated: true, completion: nil) + } + + // MARK: - UIDocumentPickerViewControllerDelegate + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } + } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + presentFileOptions(for: fileURL) + } + + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + + // MARK: - Create Files Directory + private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } + } + + // MARK: - UIButton Configuration for iOS 15 and later + func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } + + // MARK: - Helper Methods + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func presentFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) + + switch fileExtension { + case "txt": + menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) + case "plist": + menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) + case "ipa": + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + default: + menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } +} \ No newline at end of file From 45aca3426130df1096d141ed785573f4dd984870 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:32:33 -0400 Subject: [PATCH 153/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 133 +----------------------- 1 file changed, 3 insertions(+), 130 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index e882301d..9bc18fc5 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -45,7 +45,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let button = UIButton(type: .system) button.setTitle("Upload File", for: .normal) button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget(self, action: #selector(uploadFile), for: .touchUpInside) + button.addTarget(HomeViewController.self, action: #selector(uploadFile), for: .touchUpInside) return button }() @@ -372,7 +372,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } - private func unzipFile(at fileURL: URL) { + private func unzipFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") activityIndicator.startAnimating() DispatchQueue.global().async { @@ -405,131 +405,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } - } - - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - presentFileOptions(for: fileURL) - } - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() - } - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as NSString) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } - } - - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - - // MARK: - Create Files Directory - private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") - } - } - - // MARK: - UIButton Configuration for iOS 15 and later - func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - } - - // MARK: - Helper Methods - private func presentAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alert, animated: true, completion: nil) - } - - private func presentFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) - - switch fileExtension { - case "txt": - menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) - case "plist": - menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) - case "ipa": - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - default: - menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) - } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } -} \ No newline at end of file + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocuments \ No newline at end of file From 248fd29e3ea57c57a2fd8d6ff1ac6b845f169b60 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:34:12 -0400 Subject: [PATCH 154/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 131 +++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 9bc18fc5..8deef227 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -404,5 +404,132 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData present(activityController, animated: true, completion: nil) } - // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocuments \ No newline at end of file + // MARK: - UIDocumentPickerViewControllerDelegate + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + // Handle file import + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } + } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + presentFileOptions(for: fileURL) + } + + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] // Replace with your data source + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + // Handle the dropped items + let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] + tableView.insertRows(at: indexPaths, with: .automatic) + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + + // MARK: - Create Files Directory + private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } + } + + // MARK: - UIButton Configuration for iOS 15 and later + func configureButton(_ button: UIButton) { + if #available(iOS 15.0, *) { + var configuration = UIButton.Configuration.filled() + configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + button.configuration = configuration + } else { + button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } + + // MARK: - Helper Methods + private func presentAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + + private func presentFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) + + switch fileExtension { + case "txt": + menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) + case "plist": + menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) + case "ipa": + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + default: + menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } +} \ No newline at end of file From 0dfe9f6edd267c8939b267877a8164e98f914711 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:41:24 -0400 Subject: [PATCH 155/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 504 +----------------------- 1 file changed, 3 insertions(+), 501 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 8deef227..dd9a2736 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -3,493 +3,7 @@ import ZIPFoundation class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate { - // MARK: - Properties - private var fileList: [String] = [] - private var filteredFileList: [String] = [] - private let fileManager = FileManager.default - private let searchController = UISearchController(searchResultsController: nil) - private var sortOrder: SortOrder = .name - private var documentsDirectory: URL { - let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") - createFilesDirectoryIfNeeded(at: directory) - return directory - } - - enum SortOrder { - case name, date, size - } - - // MARK: - UI Elements - private let navigationBar: UINavigationBar = { - let navBar = UINavigationBar() - navBar.translatesAutoresizingMaskIntoConstraints = false - navBar.barTintColor = .systemBlue - navBar.titleTextAttributes = [.foregroundColor: UIColor.white] - return navBar - }() - - private let fileListTableView: UITableView = { - let tableView = UITableView() - tableView.translatesAutoresizingMaskIntoConstraints = false - return tableView - }() - - private let activityIndicator: UIActivityIndicatorView = { - let indicator = UIActivityIndicatorView(style: .large) - indicator.translatesAutoresizingMaskIntoConstraints = false - indicator.hidesWhenStopped = true - return indicator - }() - - private let uploadButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Upload File", for: .normal) - button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget(HomeViewController.self, action: #selector(uploadFile), for: .touchUpInside) - return button - }() - - // MARK: - Lifecycle - override func viewDidLoad() { - super.viewDidLoad() - setupUI() - setupActivityIndicator() - loadFiles() - fileListTableView.delegate = self - fileListTableView.dataSource = self - fileListTableView.dragDelegate = self - fileListTableView.dropDelegate = self - searchController.searchResultsUpdater = self - navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = false - filteredFileList = fileList - } - - // MARK: - UI Setup - private func setupUI() { - view.backgroundColor = .white - - // Setup Navigation Bar - let navItem = UINavigationItem(title: "Files") - let menuButton = UIBarButtonItem(title: "\u{22ee}", style: .plain, target: self, action: #selector(showMenu)) - let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) - navItem.rightBarButtonItems = [menuButton, sortButton] - navigationBar.setItems([navItem], animated: false) - - // Add UI elements to the view - view.addSubview(navigationBar) - view.addSubview(fileListTableView) - view.addSubview(activityIndicator) - view.addSubview(uploadButton) - - // Set up constraints with padding - NSLayoutConstraint.activate([ - navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), - navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - - fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), - fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor), - - activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), - - uploadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), - uploadButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), - uploadButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), - uploadButton.heightAnchor.constraint(equalToConstant: 50) - ]) - - // Register the table view cell - fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") - - // Add long press gesture recognizer to table view - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - fileListTableView.addGestureRecognizer(longPressRecognizer) - } - - private func setupActivityIndicator() { - view.addSubview(activityIndicator) - NSLayoutConstraint.activate([ - activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) - ]) - } - - // MARK: - Load Files - private func loadFiles() { - do { - fileList = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) - sortFiles() - filteredFileList = fileList - fileListTableView.reloadData() - } catch { - presentAlert(title: "Error", message: "Failed to load files: \(error.localizedDescription)") - } - } - - private func sortFiles() { - switch sortOrder { - case .name: - fileList.sort(by: { $0.lowercased() < $1.lowercased() }) - case .date: - fileList.sort(by: { getFileDate($0) < getFileDate($1) }) - case .size: - fileList.sort(by: { getFileSize($0) < getFileSize($1) }) - } - } - - private func getFileDate(_ fileName: String) -> Date { - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) - return attributes?[.modificationDate] as? Date ?? Date.distantPast - } - - private func getFileSize(_ fileName: String) -> UInt64 { - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) - return attributes?[.size] as? UInt64 ?? 0 - } - - // MARK: - Actions - @objc private func showMenu() { - let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - menu.addAction(UIAlertAction(title: "Select", style: .default, handler: { _ in self.selectFiles() })) - menu.addAction(UIAlertAction(title: "Import", style: .default, handler: { _ in self.importFile() })) - menu.addAction(UIAlertAction(title: "New Folder", style: .default, handler: { _ in self.createNewFolder() })) - menu.addAction(UIAlertAction(title: "New File", style: .default, handler: { _ in self.createNewFile() })) - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - - @objc private func changeSortOrder() { - let sortMenu = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) - sortMenu.addAction(UIAlertAction(title: "Name", style: .default, handler: { _ in self.sortOrder = .name; self.loadFiles() })) - sortMenu.addAction(UIAlertAction(title: "Date", style: .default, handler: { _ in self.sortOrder = .date; self.loadFiles() })) - sortMenu.addAction(UIAlertAction(title: "Size", style: .default, handler: { _ in self.sortOrder = .size; self.loadFiles() })) - sortMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(sortMenu, animated: true, completion: nil) - } - - @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { - if gesture.state == .began { - let point = gesture.location(in: fileListTableView) - if let indexPath = fileListTableView.indexPathForRow(at: point) { - let fileName = filteredFileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - showFileOptions(for: fileURL) - } - } - } - - private func showFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - - if fileExtension == "ipa" { - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - } else { - menu.addAction(UIAlertAction(title: "Copy", style: .default, handler: { _ in self.copyFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Move", style: .default, handler: { _ in self.moveFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Compress", style: .default, handler: { _ in self.compressFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Delete", style: .default, handler: { _ in self.deleteFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Share", style: .default, handler: { _ in self.shareFile(at: fileURL) })) - } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - - private func selectFiles() { - // Implement select files functionality - } - - @objc private func uploadFile() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = self - documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) - } - - private func importFile() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = self - documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) - } - - private func createNewFolder() { - let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "Folder name" - } - let createAction = UIAlertAction(title: "Create", style: .default) { _ in - guard let folderName = alertController.textFields?.first?.text else { return } - let folderURL = self.documentsDirectory.appendingPathComponent(folderName) - do { - try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) - self.loadFiles() - } catch { - self.presentAlert(title: "Error", message: "Failed to create folder: \(error.localizedDescription)") - } - } - alertController.addAction(createAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func createNewFile() { - let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "File name" - } - let createAction = UIAlertAction(title: "Create", style: .default) { _ in - guard let fileName = alertController.textFields?.first?.text else { return } - let fileURL = self.documentsDirectory.appendingPathComponent(fileName) - self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) - self.loadFiles() - } - alertController.addAction(createAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func copyFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.copyItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Copy failed with error: \(error.localizedDescription)") - } - } - } - } - - private func moveFile(at fileURL: URL) { - let alertController = UIAlertController(title: "Move File", message: "Enter new file path", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "New file path" - } - let moveAction = UIAlertAction(title: "Move", style: .default) { _ in - guard let newPath = alertController.textFields?.first?.text else { return } - let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) - self.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Move failed with error: \(error.localizedDescription)") - } - } - } - } - alertController.addAction(moveAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func compressFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.zipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Compression failed with error: \(error.localizedDescription)") - } - } - } - } - - private func renameFile(at fileURL: URL) { - let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = fileURL.lastPathComponent - } - let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in - guard let newName = alertController.textFields?.first?.text else { return } - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - self.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Rename failed with error: \(error.localizedDescription)") - } - } - } - } - alertController.addAction(renameAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func deleteFile(at fileURL: URL) { - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.removeItem(at: fileURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") - } - } - } - } - - private func unzipFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") - } - } - } - } - - private func hexEditFile(at fileURL: URL) { - guard let navigationController = self.navigationController else { - presentAlert(title: "Error", message: "Navigation controller is missing") - return - } - FileOperations.hexEditFile(at: fileURL, in: navigationController) - } - - private func shareFile(at fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - present(activityController, animated: true, completion: nil) - } - - // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - // Handle file import - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") - } - } - - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - presentFileOptions(for: fileURL) - } - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } - fileListTableView.reloadData() - } - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] // Replace with your data source - let itemProvider = NSItemProvider(object: item as NSString) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - // Handle the dropped items - let indexPaths = coordinator.destinationIndexPath.map { [$0] } ?? [] - tableView.insertRows(at: indexPaths, with: .automatic) - } - } - - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - - // MARK: - Create Files Directory - private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") - } - } - - // MARK: - UIButton Configuration for iOS 15 and later - func configureButton(_ button: UIButton) { - if #available(iOS 15.0, *) { - var configuration = UIButton.Configuration.filled() - configuration.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - button.configuration = configuration - } else { - button.contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - } + // ... existing code ... // MARK: - Helper Methods private func presentAlert(title: String, message: String) { @@ -500,7 +14,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func presentFileOptions(for fileURL: URL) { let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: "Open File", message: "Choose how to open the file", preferredStyle: .actionSheet) + let menu = UIAlertController(title: "File Options", message: nil, preferredStyle: .actionSheet) switch fileExtension { case "txt": @@ -518,18 +32,6 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData present(menu, animated: true, completion: nil) } - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } + // ... rest of the existing methods ... - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } } \ No newline at end of file From 71334d82c109fb21317ef30f6151208f02c96763 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:41:47 -0400 Subject: [PATCH 156/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 490 +++++++++++++++++++++++- 1 file changed, 488 insertions(+), 2 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index dd9a2736..a5d900b1 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -3,9 +3,483 @@ import ZIPFoundation class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate { - // ... existing code ... + // MARK: - Properties + private var fileList: [String] = [] + private var filteredFileList: [String] = [] + private let fileManager = FileManager.default + private let searchController = UISearchController(searchResultsController: nil) + private var sortOrder: SortOrder = .name + private var documentsDirectory: URL { + let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") + createFilesDirectoryIfNeeded(at: directory) + return directory + } + + enum SortOrder { + case name, date, size + } + + // MARK: - UI Elements + private let navigationBar: UINavigationBar = { + let navBar = UINavigationBar() + navBar.translatesAutoresizingMaskIntoConstraints = false + navBar.barTintColor = .systemBlue + navBar.titleTextAttributes = [.foregroundColor: UIColor.white] + return navBar + }() + + private let fileListTableView: UITableView = { + let tableView = UITableView() + tableView.translatesAutoresizingMaskIntoConstraints = false + return tableView + }() + + private let activityIndicator: UIActivityIndicatorView = { + let indicator = UIActivityIndicatorView(style: .large) + indicator.translatesAutoresizingMaskIntoConstraints = false + indicator.hidesWhenStopped = true + return indicator + }() + + private let uploadButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Upload File", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(uploadFile), for: .touchUpInside) + return button + }() + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + setupActivityIndicator() + loadFiles() + fileListTableView.delegate = self + fileListTableView.dataSource = self + fileListTableView.dragDelegate = self + fileListTableView.dropDelegate = self + searchController.searchResultsUpdater = self + navigationItem.searchController = searchController + navigationItem.hidesSearchBarWhenScrolling = false + filteredFileList = fileList + } + + // MARK: - UI Setup + private func setupUI() { + view.backgroundColor = .white + + // Setup Navigation Bar + let navItem = UINavigationItem(title: "Files") + let menuButton = UIBarButtonItem(title: "\u{22ee}", style: .plain, target: self, action: #selector(showMenu)) + let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) + navItem.rightBarButtonItems = [menuButton, sortButton] + navigationBar.setItems([navItem], animated: false) + + // Add UI elements to the view + view.addSubview(navigationBar) + view.addSubview(fileListTableView) + view.addSubview(activityIndicator) + view.addSubview(uploadButton) + + // Set up constraints with padding + NSLayoutConstraint.activate([ + navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor), + + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), + + uploadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), + uploadButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + uploadButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + uploadButton.heightAnchor.constraint(equalToConstant: 50) + ]) + + // Register the table view cell + fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") + + // Add long press gesture recognizer to table view + let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) + fileListTableView.addGestureRecognizer(longPressRecognizer) + } + + private func setupActivityIndicator() { + view.addSubview(activityIndicator) + NSLayoutConstraint.activate([ + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + } + + // MARK: - Load Files + private func loadFiles() { + do { + fileList = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) + sortFiles() + filteredFileList = fileList + fileListTableView.reloadData() + } catch { + presentAlert(title: "Error", message: "Failed to load files: \(error.localizedDescription)") + } + } + + private func sortFiles() { + switch sortOrder { + case .name: + fileList.sort(by: { $0.lowercased() < $1.lowercased() }) + case .date: + fileList.sort(by: { getFileDate($0) < getFileDate($1) }) + case .size: + fileList.sort(by: { getFileSize($0) < getFileSize($1) }) + } + } + + private func getFileDate(_ fileName: String) -> Date { + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) + return attributes?[.modificationDate] as? Date ?? Date.distantPast + } + + private func getFileSize(_ fileName: String) -> UInt64 { + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) + return attributes?[.size] as? UInt64 ?? 0 + } + + // MARK: - Actions + @objc private func showMenu() { + let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + menu.addAction(UIAlertAction(title: "Select", style: .default, handler: { _ in self.selectFiles() })) + menu.addAction(UIAlertAction(title: "Import", style: .default, handler: { _ in self.importFile() })) + menu.addAction(UIAlertAction(title: "New Folder", style: .default, handler: { _ in self.createNewFolder() })) + menu.addAction(UIAlertAction(title: "New File", style: .default, handler: { _ in self.createNewFile() })) + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + @objc private func changeSortOrder() { + let sortMenu = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) + sortMenu.addAction(UIAlertAction(title: "Name", style: .default, handler: { _ in self.sortOrder = .name; self.loadFiles() })) + sortMenu.addAction(UIAlertAction(title: "Date", style: .default, handler: { _ in self.sortOrder = .date; self.loadFiles() })) + sortMenu.addAction(UIAlertAction(title: "Size", style: .default, handler: { _ in self.sortOrder = .size; self.loadFiles() })) + sortMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(sortMenu, animated: true, completion: nil) + } + + @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { + if gesture.state == .began { + let point = gesture.location(in: fileListTableView) + if let indexPath = fileListTableView.indexPathForRow(at: point) { + let fileName = filteredFileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + showFileOptions(for: fileURL) + } + } + } + + private func showFileOptions(for fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + if fileExtension == "ipa" { + menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) + } else { + menu.addAction(UIAlertAction(title: "Copy", style: .default, handler: { _ in self.copyFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Move", style: .default, handler: { _ in self.moveFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Compress", style: .default, handler: { _ in self.compressFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Delete", style: .default, handler: { _ in self.deleteFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Share", style: .default, handler: { _ in self.shareFile(at: fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(menu, animated: true, completion: nil) + } + + private func selectFiles() { + // Implement select files functionality + } + + @objc private func uploadFile() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + private func importFile() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + private func createNewFolder() { + let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "Folder name" + } + let createAction = UIAlertAction(title: "Create", style: .default) { _ in + guard let folderName = alertController.textFields?.first?.text else { return } + let folderURL = self.documentsDirectory.appendingPathComponent(folderName) + do { + try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) + self.loadFiles() + } catch { + self.presentAlert(title: "Error", message: "Failed to create folder: \(error.localizedDescription)") + } + } + alertController.addAction(createAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func createNewFile() { + let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "File name" + } + let createAction = UIAlertAction(title: "Create", style: .default) { _ in + guard let fileName = alertController.textFields?.first?.text else { return } + let fileURL = self.documentsDirectory.appendingPathComponent(fileName) + self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + self.loadFiles() + } + alertController.addAction(createAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func copyFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.copyItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Copy failed with error: \(error.localizedDescription)") + } + } + } + } + + private func moveFile(at fileURL: URL) { + let alertController = UIAlertController(title: "Move File", message: "Enter new file path", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "New file path" + } + let moveAction = UIAlertAction(title: "Move", style: .default) { _ in + guard let newPath = alertController.textFields?.first?.text else { return } + let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) + self.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Move failed with error: \(error.localizedDescription)") + } + } + } + } + alertController.addAction(moveAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func compressFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.zipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Compression failed with error: \(error.localizedDescription)") + } + } + } + } + + private func renameFile(at fileURL: URL) { + let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = fileURL.lastPathComponent + } + let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in + guard let newName = alertController.textFields?.first?.text else { return } + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + self.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Rename failed with error: \(error.localizedDescription)") + } + } + } + } + alertController.addAction(renameAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + private func deleteFile(at fileURL: URL) { + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.removeItem(at: fileURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") + } + } + } + } + + private func unzipFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") + } + } + } + } + + private func hexEditFile(at fileURL: URL) { + guard let navigationController = self.navigationController else { + presentAlert(title: "Error", message: "Navigation controller is missing") + return + } + FileOperations.hexEditFile(at: fileURL, in: navigationController) + } + + private func shareFile(at fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityController, animated: true, completion: nil) + } + + // MARK: - UIDocumentPickerViewControllerDelegate + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + } + } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + presentFileOptions(for: fileURL) + } + + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.contains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + guard let string = items.first as? String else { return } + // Here you might want to add the string to your fileList or handle it accordingly + self.fileList.append(string) + self.loadFiles() + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) + } // MARK: - Helper Methods + private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + } + } + private func presentAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) @@ -32,6 +506,18 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData present(menu, animated: true, completion: nil) } - // ... rest of the existing methods ... + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } } \ No newline at end of file From 9abed8d0174ed8bf38aab8e443bafffbc359e657 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 07:46:36 -0400 Subject: [PATCH 157/391] Update repo.yml --- .github/workflows/repo.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml index f540cd72..c318c500 100644 --- a/.github/workflows/repo.yml +++ b/.github/workflows/repo.yml @@ -1,7 +1,8 @@ name: Update repo on: - workflow_dispatch: + release: + types: [published] jobs: update_repo: From 51098389f5f648842b89810d5778acf92a91724b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 08:02:35 -0400 Subject: [PATCH 158/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 245 +++++++++++++----------- 1 file changed, 133 insertions(+), 112 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index a5d900b1..d47008aa 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -31,6 +31,9 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private let fileListTableView: UITableView = { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.separatorStyle = .singleLine + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 44 return tableView }() @@ -38,6 +41,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let indicator = UIActivityIndicatorView(style: .large) indicator.translatesAutoresizingMaskIntoConstraints = false indicator.hidesWhenStopped = true + indicator.color = .systemBlue return indicator }() @@ -45,6 +49,10 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let button = UIButton(type: .system) button.setTitle("Upload File", for: .normal) button.translatesAutoresizingMaskIntoConstraints = false + button.setTitleColor(.white, for: .normal) + button.backgroundColor = .systemBlue + button.layer.cornerRadius = 10 + button.clipsToBounds = true button.addTarget(self, action: #selector(uploadFile), for: .touchUpInside) return button }() @@ -55,23 +63,16 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData setupUI() setupActivityIndicator() loadFiles() - fileListTableView.delegate = self - fileListTableView.dataSource = self - fileListTableView.dragDelegate = self - fileListTableView.dropDelegate = self - searchController.searchResultsUpdater = self - navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = false - filteredFileList = fileList + configureTableView() } // MARK: - UI Setup private func setupUI() { - view.backgroundColor = .white + view.backgroundColor = .systemBackground // Setup Navigation Bar let navItem = UINavigationItem(title: "Files") - let menuButton = UIBarButtonItem(title: "\u{22ee}", style: .plain, target: self, action: #selector(showMenu)) + let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) navItem.rightBarButtonItems = [menuButton, sortButton] navigationBar.setItems([navItem], animated: false) @@ -82,7 +83,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData view.addSubview(activityIndicator) view.addSubview(uploadButton) - // Set up constraints with padding + // Set up constraints NSLayoutConstraint.activate([ navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), @@ -91,7 +92,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor), + fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor, constant: -20), activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), @@ -118,57 +119,90 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData ]) } + private func configureTableView() { + fileListTableView.delegate = self + fileListTableView.dataSource = self + fileListTableView.dragDelegate = self + fileListTableView.dropDelegate = self + searchController.searchResultsUpdater = self + navigationItem.searchController = searchController + navigationItem.hidesSearchBarWhenScrolling = false + filteredFileList = fileList + } + // MARK: - Load Files private func loadFiles() { - do { - fileList = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) - sortFiles() - filteredFileList = fileList - fileListTableView.reloadData() - } catch { - presentAlert(title: "Error", message: "Failed to load files: \(error.localizedDescription)") + activityIndicator.startAnimating() + DispatchQueue.global().async { [weak self] in + do { + self?.fileList = try self?.fileManager.contentsOfDirectory(atPath: self?.documentsDirectory.path ?? "") ?? [] + self?.sortFiles() + DispatchQueue.main.async { + self?.filteredFileList = self?.fileList ?? [] + self?.fileListTableView.reloadData() + self?.activityIndicator.stopAnimating() + } + } catch { + DispatchQueue.main.async { + self?.activityIndicator.stopAnimating() + self?.handleError(error, withTitle: "Loading Files") + } + } } } private func sortFiles() { switch sortOrder { case .name: - fileList.sort(by: { $0.lowercased() < $1.lowercased() }) + fileList.sort { $0.lowercased() < $1.lowercased() } case .date: - fileList.sort(by: { getFileDate($0) < getFileDate($1) }) + fileList.sort { getFileDate($0) < getFileDate($1) } case .size: - fileList.sort(by: { getFileSize($0) < getFileSize($1) }) + fileList.sort { getFileSize($0) < getFileSize($1) } } } private func getFileDate(_ fileName: String) -> Date { let fileURL = documentsDirectory.appendingPathComponent(fileName) - let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) - return attributes?[.modificationDate] as? Date ?? Date.distantPast + return (try? fileManager.attributesOfItem(atPath: fileURL.path)[.modificationDate] as? Date) ?? Date.distantPast } private func getFileSize(_ fileName: String) -> UInt64 { let fileURL = documentsDirectory.appendingPathComponent(fileName) - let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path) - return attributes?[.size] as? UInt64 ?? 0 + return (try? fileManager.attributesOfItem(atPath: fileURL.path)[.size] as? UInt64) ?? 0 } // MARK: - Actions @objc private func showMenu() { let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - menu.addAction(UIAlertAction(title: "Select", style: .default, handler: { _ in self.selectFiles() })) - menu.addAction(UIAlertAction(title: "Import", style: .default, handler: { _ in self.importFile() })) - menu.addAction(UIAlertAction(title: "New Folder", style: .default, handler: { _ in self.createNewFolder() })) - menu.addAction(UIAlertAction(title: "New File", style: .default, handler: { _ in self.createNewFile() })) + ["Select", "Import", "New Folder", "New File"].forEach { actionTitle in + menu.addAction(UIAlertAction(title: actionTitle, style: .default) { _ in + switch actionTitle { + case "Select": self.selectFiles() + case "Import": self.importFile() + case "New Folder": self.createNewFolder() + case "New File": self.createNewFile() + default: break + } + }) + } menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(menu, animated: true, completion: nil) } @objc private func changeSortOrder() { let sortMenu = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) - sortMenu.addAction(UIAlertAction(title: "Name", style: .default, handler: { _ in self.sortOrder = .name; self.loadFiles() })) - sortMenu.addAction(UIAlertAction(title: "Date", style: .default, handler: { _ in self.sortOrder = .date; self.loadFiles() })) - sortMenu.addAction(UIAlertAction(title: "Size", style: .default, handler: { _ in self.sortOrder = .size; self.loadFiles() })) + ["Name", "Date", "Size"].forEach { sortOption in + sortMenu.addAction(UIAlertAction(title: sortOption, style: .default) { _ in + switch sortOption { + case "Name": self.sortOrder = .name + case "Date": self.sortOrder = .date + case "Size": self.sortOrder = .size + default: return + } + self.loadFiles() + }) + } sortMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(sortMenu, animated: true, completion: nil) } @@ -186,18 +220,28 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func showFileOptions(for fileURL: URL) { let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let menu = UIAlertController(title: "File Options", message: "Select an action for this file", preferredStyle: .actionSheet) - if fileExtension == "ipa" { + switch fileExtension { + case "txt": + menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) + case "plist": + menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) + case "ipa": menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - } else { - menu.addAction(UIAlertAction(title: "Copy", style: .default, handler: { _ in self.copyFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Move", style: .default, handler: { _ in self.moveFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Compress", style: .default, handler: { _ in self.compressFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Delete", style: .default, handler: { _ in self.deleteFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Share", style: .default, handler: { _ in self.shareFile(at: fileURL) })) + default: + menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) + } + + menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { _ in self.deleteFile(at: fileURL) })) + menu.addAction(UIAlertAction(title: "Share", style: .default, handler: { _ in self.shareFile(at: fileURL) })) + + if let popoverController = menu.popoverPresentationController { + popoverController.sourceView = self.view + popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) + popoverController.permittedArrowDirections = [] } menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) @@ -223,45 +267,29 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } private func createNewFolder() { - let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "Folder name" - } - let createAction = UIAlertAction(title: "Create", style: .default) { _ in - guard let folderName = alertController.textFields?.first?.text else { return } + showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in let folderURL = self.documentsDirectory.appendingPathComponent(folderName) do { try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) self.loadFiles() } catch { - self.presentAlert(title: "Error", message: "Failed to create folder: \(error.localizedDescription)") + self.handleError(error, withTitle: "Creating Folder") } } - alertController.addAction(createAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) } private func createNewFile() { - let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "File name" - } - let createAction = UIAlertAction(title: "Create", style: .default) { _ in - guard let fileName = alertController.textFields?.first?.text else { return } + showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in let fileURL = self.documentsDirectory.appendingPathComponent(fileName) self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) self.loadFiles() } - alertController.addAction(createAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) } private func copyFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") activityIndicator.startAnimating() DispatchQueue.global().async { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") do { try self.fileManager.copyItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { @@ -271,19 +299,14 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Copy failed with error: \(error.localizedDescription)") + self.handleError(error, withTitle: "Copying File") } } } } private func moveFile(at fileURL: URL) { - let alertController = UIAlertController(title: "Move File", message: "Enter new file path", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "New file path" - } - let moveAction = UIAlertAction(title: "Move", style: .default) { _ in - guard let newPath = alertController.textFields?.first?.text else { return } + showInputAlert(title: "Move File", message: "Enter new file path", actionTitle: "Move") { newPath in let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) self.activityIndicator.startAnimating() DispatchQueue.global().async { @@ -296,14 +319,11 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Move failed with error: \(error.localizedDescription)") + self.handleError(error, withTitle: "Moving File") } } } } - alertController.addAction(moveAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) } private func compressFile(at fileURL: URL) { @@ -319,19 +339,14 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Compression failed with error: \(error.localizedDescription)") + self.handleError(error, withTitle: "Compressing File") } } } } private func renameFile(at fileURL: URL) { - let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = fileURL.lastPathComponent - } - let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in - guard let newName = alertController.textFields?.first?.text else { return } + showInputAlert(title: "Rename File", message: "Enter new file name", actionTitle: "Rename", initialText: fileURL.lastPathComponent) { newName in let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) self.activityIndicator.startAnimating() DispatchQueue.global().async { @@ -344,14 +359,11 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Rename failed with error: \(error.localizedDescription)") + self.handleError(error, withTitle: "Renaming File") } } } } - alertController.addAction(renameAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) } private func deleteFile(at fileURL: URL) { @@ -366,7 +378,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Delete failed with error: \(error.localizedDescription)") + self.handleError(error, withTitle: "Deleting File") } } } @@ -385,7 +397,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.presentAlert(title: "Error", message: "Unzip failed with error: \(error.localizedDescription)") + self.handleError(error, withTitle: "Unzipping File") } } } @@ -393,7 +405,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func hexEditFile(at fileURL: URL) { guard let navigationController = self.navigationController else { - presentAlert(title: "Error", message: "Navigation controller is missing") + handleError(NSError(domain: "NavigationError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Navigation controller is missing"])) return } FileOperations.hexEditFile(at: fileURL, in: navigationController) @@ -414,7 +426,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData try fileManager.copyItem(at: selectedFileURL, to: destinationURL) loadFiles() } catch { - presentAlert(title: "Error", message: "Failed to import file: \(error.localizedDescription)") + handleError(error, withTitle: "Importing File") } } @@ -435,19 +447,19 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] let fileURL = documentsDirectory.appendingPathComponent(fileName) - presentFileOptions(for: fileURL) + showFileOptions(for: fileURL) } // MARK: - UISearchResultsUpdating func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.contains(searchText) } + filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } fileListTableView.reloadData() } // MARK: - UITableViewDragDelegate func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] + let item = self.fileList[indexPath.row] let itemProvider = NSItemProvider(object: item as NSString) let dragItem = UIDragItem(itemProvider: itemProvider) return [dragItem] @@ -457,7 +469,6 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { coordinator.session.loadObjects(ofClass: NSString.self) { items in guard let string = items.first as? String else { return } - // Here you might want to add the string to your fileList or handle it accordingly self.fileList.append(string) self.loadFiles() } @@ -476,36 +487,41 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData do { try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) } catch { - presentAlert(title: "Error", message: "Failed to create 'files' directory: \(error.localizedDescription)") + handleError(error, withTitle: "Creating Files Directory") } } - private func presentAlert(title: String, message: String) { + private func presentAlert(title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) + alert.addAction(action) + + if handler != nil { + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + } + present(alert, animated: true, completion: nil) } - private func presentFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: "File Options", message: nil, preferredStyle: .actionSheet) - - switch fileExtension { - case "txt": - menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) - case "plist": - menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) - case "ipa": - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - default: - menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) + private func showInputAlert(title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = initialText } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) + let confirmAction = UIAlertAction(title: actionTitle, style: .default) { [weak alertController] _ in + guard let textField = alertController?.textFields?.first, let text = textField.text else { return } + completion(text) + } + alertController.addAction(confirmAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) } + private func handleError(_ error: Error, withTitle title: String = "Error") { + presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") + } + + // MARK: - File Handling Methods private func openTextEditor(_ fileURL: URL) { let textEditorVC = TextEditorViewController(fileURL: fileURL) navigationController?.pushViewController(textEditorVC, animated: true) @@ -520,4 +536,9 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let hexEditorVC = HexEditorViewController(fileURL: fileURL) navigationController?.pushViewController(hexEditorVC, animated: true) } +} + +// Extension for additional utility methods if needed +extension HomeViewController { + // Any additional utility methods can be added here } \ No newline at end of file From 27581e1ae4cd07089f8e87fc7a08bf5bcf0545e1 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 08:15:36 -0400 Subject: [PATCH 159/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 234 ++++++------------------ 1 file changed, 53 insertions(+), 181 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index d47008aa..0341ca0b 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,8 +1,11 @@ import UIKit import ZIPFoundation -class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate { +// Assuming the separated components are in the same module +// If they are in different modules, remember to import them accordingly +class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate { + // MARK: - Properties private var fileList: [String] = [] private var filteredFileList: [String] = [] @@ -19,44 +22,6 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData case name, date, size } - // MARK: - UI Elements - private let navigationBar: UINavigationBar = { - let navBar = UINavigationBar() - navBar.translatesAutoresizingMaskIntoConstraints = false - navBar.barTintColor = .systemBlue - navBar.titleTextAttributes = [.foregroundColor: UIColor.white] - return navBar - }() - - private let fileListTableView: UITableView = { - let tableView = UITableView() - tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.separatorStyle = .singleLine - tableView.rowHeight = UITableView.automaticDimension - tableView.estimatedRowHeight = 44 - return tableView - }() - - private let activityIndicator: UIActivityIndicatorView = { - let indicator = UIActivityIndicatorView(style: .large) - indicator.translatesAutoresizingMaskIntoConstraints = false - indicator.hidesWhenStopped = true - indicator.color = .systemBlue - return indicator - }() - - private let uploadButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Upload File", for: .normal) - button.translatesAutoresizingMaskIntoConstraints = false - button.setTitleColor(.white, for: .normal) - button.backgroundColor = .systemBlue - button.layer.cornerRadius = 10 - button.clipsToBounds = true - button.addTarget(self, action: #selector(uploadFile), for: .touchUpInside) - return button - }() - // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -75,55 +40,55 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) navItem.rightBarButtonItems = [menuButton, sortButton] - navigationBar.setItems([navItem], animated: false) - + HomeViewUI.navigationBar.setItems([navItem], animated: false) + // Add UI elements to the view - view.addSubview(navigationBar) - view.addSubview(fileListTableView) - view.addSubview(activityIndicator) - view.addSubview(uploadButton) + view.addSubview(HomeViewUI.navigationBar) + view.addSubview(HomeViewUI.fileListTableView) + view.addSubview(HomeViewUI.activityIndicator) + view.addSubview(HomeViewUI.uploadButton) // Set up constraints NSLayoutConstraint.activate([ - navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), - navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + HomeViewUI.navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + HomeViewUI.navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + HomeViewUI.navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), - fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: uploadButton.topAnchor, constant: -20), + HomeViewUI.fileListTableView.topAnchor.constraint(equalTo: HomeViewUI.navigationBar.bottomAnchor), + HomeViewUI.fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + HomeViewUI.fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + HomeViewUI.fileListTableView.bottomAnchor.constraint(equalTo: HomeViewUI.uploadButton.topAnchor, constant: -20), - activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), + HomeViewUI.activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + HomeViewUI.activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), - uploadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), - uploadButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), - uploadButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), - uploadButton.heightAnchor.constraint(equalToConstant: 50) + HomeViewUI.uploadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), + HomeViewUI.uploadButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + HomeViewUI.uploadButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + HomeViewUI.uploadButton.heightAnchor.constraint(equalToConstant: 50) ]) // Register the table view cell - fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") + HomeViewUI.fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") // Add long press gesture recognizer to table view let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - fileListTableView.addGestureRecognizer(longPressRecognizer) + HomeViewUI.fileListTableView.addGestureRecognizer(longPressRecognizer) } private func setupActivityIndicator() { - view.addSubview(activityIndicator) + view.addSubview(HomeViewUI.activityIndicator) NSLayoutConstraint.activate([ - activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) + HomeViewUI.activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + HomeViewUI.activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) } private func configureTableView() { - fileListTableView.delegate = self - fileListTableView.dataSource = self - fileListTableView.dragDelegate = self - fileListTableView.dropDelegate = self + HomeViewUI.fileListTableView.delegate = self + HomeViewUI.fileListTableView.dataSource = self + HomeViewUI.fileListTableView.dragDelegate = self + HomeViewUI.fileListTableView.dropDelegate = self searchController.searchResultsUpdater = self navigationItem.searchController = searchController navigationItem.hidesSearchBarWhenScrolling = false @@ -132,7 +97,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: - Load Files private func loadFiles() { - activityIndicator.startAnimating() + HomeViewUI.activityIndicator.startAnimating() DispatchQueue.global().async { [weak self] in do { self?.fileList = try self?.fileManager.contentsOfDirectory(atPath: self?.documentsDirectory.path ?? "") ?? [] @@ -209,8 +174,8 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { if gesture.state == .began { - let point = gesture.location(in: fileListTableView) - if let indexPath = fileListTableView.indexPathForRow(at: point) { + let point = gesture.location(in: HomeViewUI.fileListTableView) + if let indexPath = HomeViewUI.fileListTableView.indexPathForRow(at: point) { let fileName = filteredFileList[indexPath.row] let fileURL = documentsDirectory.appendingPathComponent(fileName) showFileOptions(for: fileURL) @@ -258,7 +223,7 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData documentPicker.modalPresentationStyle = .formSheet present(documentPicker, animated: true, completion: nil) } - + private func importFile() { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = self @@ -286,65 +251,6 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } - private func copyFile(at fileURL: URL) { - activityIndicator.startAnimating() - DispatchQueue.global().async { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("Copy_\(fileURL.lastPathComponent)") - do { - try self.fileManager.copyItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.handleError(error, withTitle: "Copying File") - } - } - } - } - - private func moveFile(at fileURL: URL) { - showInputAlert(title: "Move File", message: "Enter new file path", actionTitle: "Move") { newPath in - let destinationURL = self.documentsDirectory.appendingPathComponent(newPath) - self.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.handleError(error, withTitle: "Moving File") - } - } - } - } - } - - private func compressFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("\(fileURL.lastPathComponent).zip") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.zipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.handleError(error, withTitle: "Compressing File") - } - } - } - } - private func renameFile(at fileURL: URL) { showInputAlert(title: "Rename File", message: "Enter new file name", actionTitle: "Rename", initialText: fileURL.lastPathComponent) { newName in let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) @@ -402,20 +308,27 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } } - - private func hexEditFile(at fileURL: URL) { - guard let navigationController = self.navigationController else { - handleError(NSError(domain: "NavigationError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Navigation controller is missing"])) - return - } - FileOperations.hexEditFile(at: fileURL, in: navigationController) - } - + private func shareFile(at fileURL: URL) { let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) present(activityController, animated: true, completion: nil) } + private func openTextEditor(_ fileURL: URL) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } + + private func openPlistEditor(_ fileURL: URL) { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } + + private func openHexEditor(_ fileURL: URL) { + let hexEditorVC = HexEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(hexEditorVC, animated: true) + } + // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let selectedFileURL = urls.first else { @@ -430,31 +343,11 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData } } - // MARK: - UITableViewDelegate, UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - showFileOptions(for: fileURL) - } - // MARK: - UISearchResultsUpdating func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } - fileListTableView.reloadData() + HomeViewUI.fileListTableView.reloadData() } // MARK: - UITableViewDragDelegate @@ -520,25 +413,4 @@ class HomeViewController: UIViewController, UITableViewDelegate, UITableViewData private func handleError(_ error: Error, withTitle title: String = "Error") { presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") } - - // MARK: - File Handling Methods - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } -} - -// Extension for additional utility methods if needed -extension HomeViewController { - // Any additional utility methods can be added here } \ No newline at end of file From c4d621d9178004112b26854a2b0149705c5e0e3c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 08:17:24 -0400 Subject: [PATCH 160/391] Add files via upload --- iOS/Views/Home/HomeViewFileHandlers.swift | 102 ++++++++++++++++++++ iOS/Views/Home/HomeViewTableHandlers.swift | 22 +++++ iOS/Views/Home/HomeViewUI.swift | 39 ++++++++ iOS/Views/Home/HomeViewUtilities.swift | 33 +++++++ 4 files changed, 196 insertions(+) create mode 100644 iOS/Views/Home/HomeViewFileHandlers.swift create mode 100644 iOS/Views/Home/HomeViewTableHandlers.swift create mode 100644 iOS/Views/Home/HomeViewUI.swift create mode 100644 iOS/Views/Home/HomeViewUtilities.swift diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift new file mode 100644 index 00000000..e91ccb30 --- /dev/null +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -0,0 +1,102 @@ +import UIKit +import ZIPFoundation + +extension HomeViewController { + + @objc private func uploadFile() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + private func importFile() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = self + documentPicker.modalPresentationStyle = .formSheet + present(documentPicker, animated: true, completion: nil) + } + + private func createNewFolder() { + showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in + let folderURL = self.documentsDirectory.appendingPathComponent(folderName) + do { + try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) + self.loadFiles() + } catch { + self.handleError(error, withTitle: "Creating Folder") + } + } + } + + private func createNewFile() { + showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in + let fileURL = self.documentsDirectory.appendingPathComponent(fileName) + self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + self.loadFiles() + } + } + + private func renameFile(at fileURL: URL) { + showInputAlert(title: "Rename File", message: "Enter new file name", actionTitle: "Rename", initialText: fileURL.lastPathComponent) { newName in + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + self.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.handleError(error, withTitle: "Renaming File") + } + } + } + } + } + + private func deleteFile(at fileURL: URL) { + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.removeItem(at: fileURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.handleError(error, withTitle: "Deleting File") + } + } + } + } + + private func unzipFile(at fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadFiles() + } + } catch { + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.handleError(error, withTitle: "Unzipping File") + } + } + } + } + + private func shareFile(at fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityController, animated: true, completion: nil) + } +} \ No newline at end of file diff --git a/iOS/Views/Home/HomeViewTableHandlers.swift b/iOS/Views/Home/HomeViewTableHandlers.swift new file mode 100644 index 00000000..f5404e66 --- /dev/null +++ b/iOS/Views/Home/HomeViewTableHandlers.swift @@ -0,0 +1,22 @@ +import UIKit + +extension HomeViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + showFileOptions(for: fileURL) + } +} \ No newline at end of file diff --git a/iOS/Views/Home/HomeViewUI.swift b/iOS/Views/Home/HomeViewUI.swift new file mode 100644 index 00000000..3d3a850c --- /dev/null +++ b/iOS/Views/Home/HomeViewUI.swift @@ -0,0 +1,39 @@ +import UIKit + +class HomeViewUI { + static let navigationBar: UINavigationBar = { + let navBar = UINavigationBar() + navBar.translatesAutoresizingMaskIntoConstraints = false + navBar.barTintColor = .systemBlue + navBar.titleTextAttributes = [.foregroundColor: UIColor.white] + return navBar + }() + + static let fileListTableView: UITableView = { + let tableView = UITableView() + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.separatorStyle = .singleLine + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 44 + return tableView + }() + + static let activityIndicator: UIActivityIndicatorView = { + let indicator = UIActivityIndicatorView(style: .large) + indicator.translatesAutoresizingMaskIntoConstraints = false + indicator.hidesWhenStopped = true + indicator.color = .systemBlue + return indicator + }() + + static let uploadButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Upload File", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitleColor(.white, for: .normal) + button.backgroundColor = .systemBlue + button.layer.cornerRadius = 10 + button.clipsToBounds = true + return button + }() +} \ No newline at end of file diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift new file mode 100644 index 00000000..1b6fb519 --- /dev/null +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -0,0 +1,33 @@ +import UIKit + +extension HomeViewController { + func presentAlert(title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) + alert.addAction(action) + + if handler != nil { + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + } + + present(alert, animated: true, completion: nil) + } + + func showInputAlert(title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = initialText + } + let confirmAction = UIAlertAction(title: actionTitle, style: .default) { [weak alertController] _ in + guard let textField = alertController?.textFields?.first, let text = textField.text else { return } + completion(text) + } + alertController.addAction(confirmAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + func handleError(_ error: Error, withTitle title: String = "Error") { + presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") + } +} \ No newline at end of file From 4bf265c53827096f41753a13e19d5fbfa4c81188 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 08:24:13 -0400 Subject: [PATCH 161/391] Update Info.plist --- iOS/Info.plist | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOS/Info.plist b/iOS/Info.plist index cd078865..642c13bd 100644 --- a/iOS/Info.plist +++ b/iOS/Info.plist @@ -121,5 +121,7 @@ + CFBundleShortVersionString + 0.1.0 - + \ No newline at end of file From 6c23f31aab6b69713da3de519cd7f8726c585971 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 08:41:31 -0400 Subject: [PATCH 162/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 127 +++++++----------------- 1 file changed, 34 insertions(+), 93 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 0341ca0b..432b5fd6 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,11 +1,8 @@ import UIKit import ZIPFoundation -// Assuming the separated components are in the same module -// If they are in different modules, remember to import them accordingly +class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { -class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate { - // MARK: - Properties private var fileList: [String] = [] private var filteredFileList: [String] = [] @@ -22,6 +19,9 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe case name, date, size } + private let fileListTableView = UITableView() + private let activityIndicator = UIActivityIndicatorView(style: .large) + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -34,61 +34,43 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe // MARK: - UI Setup private func setupUI() { view.backgroundColor = .systemBackground - + // Setup Navigation Bar let navItem = UINavigationItem(title: "Files") let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) navItem.rightBarButtonItems = [menuButton, sortButton] - HomeViewUI.navigationBar.setItems([navItem], animated: false) + navigationController?.navigationBar.setItems([navItem], animated: false) // Add UI elements to the view - view.addSubview(HomeViewUI.navigationBar) - view.addSubview(HomeViewUI.fileListTableView) - view.addSubview(HomeViewUI.activityIndicator) - view.addSubview(HomeViewUI.uploadButton) - + view.addSubview(fileListTableView) + view.addSubview(activityIndicator) + // Set up constraints NSLayoutConstraint.activate([ - HomeViewUI.navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - HomeViewUI.navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), - HomeViewUI.navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - - HomeViewUI.fileListTableView.topAnchor.constraint(equalTo: HomeViewUI.navigationBar.bottomAnchor), - HomeViewUI.fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - HomeViewUI.fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - HomeViewUI.fileListTableView.bottomAnchor.constraint(equalTo: HomeViewUI.uploadButton.topAnchor, constant: -20), - - HomeViewUI.activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - HomeViewUI.activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), - - HomeViewUI.uploadButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), - HomeViewUI.uploadButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), - HomeViewUI.uploadButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), - HomeViewUI.uploadButton.heightAnchor.constraint(equalToConstant: 50) + fileListTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) - + // Register the table view cell - HomeViewUI.fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") - - // Add long press gesture recognizer to table view - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - HomeViewUI.fileListTableView.addGestureRecognizer(longPressRecognizer) + fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") } private func setupActivityIndicator() { - view.addSubview(HomeViewUI.activityIndicator) - NSLayoutConstraint.activate([ - HomeViewUI.activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - HomeViewUI.activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) - ]) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.hidesWhenStopped = true } private func configureTableView() { - HomeViewUI.fileListTableView.delegate = self - HomeViewUI.fileListTableView.dataSource = self - HomeViewUI.fileListTableView.dragDelegate = self - HomeViewUI.fileListTableView.dropDelegate = self + fileListTableView.delegate = self + fileListTableView.dataSource = self + fileListTableView.dragDelegate = self + fileListTableView.dropDelegate = self searchController.searchResultsUpdater = self navigationItem.searchController = searchController navigationItem.hidesSearchBarWhenScrolling = false @@ -97,7 +79,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe // MARK: - Load Files private func loadFiles() { - HomeViewUI.activityIndicator.startAnimating() + activityIndicator.startAnimating() DispatchQueue.global().async { [weak self] in do { self?.fileList = try self?.fileManager.contentsOfDirectory(atPath: self?.documentsDirectory.path ?? "") ?? [] @@ -172,51 +154,10 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe present(sortMenu, animated: true, completion: nil) } - @objc private func handleLongPress(gesture: UILongPressGestureRecognizer) { - if gesture.state == .began { - let point = gesture.location(in: HomeViewUI.fileListTableView) - if let indexPath = HomeViewUI.fileListTableView.indexPathForRow(at: point) { - let fileName = filteredFileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - showFileOptions(for: fileURL) - } - } - } - - private func showFileOptions(for fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - let menu = UIAlertController(title: "File Options", message: "Select an action for this file", preferredStyle: .actionSheet) - - switch fileExtension { - case "txt": - menu.addAction(UIAlertAction(title: "Open as Text", style: .default, handler: { _ in self.openTextEditor(fileURL) })) - case "plist": - menu.addAction(UIAlertAction(title: "Open as Plist", style: .default, handler: { _ in self.openPlistEditor(fileURL) })) - case "ipa": - menu.addAction(UIAlertAction(title: "Unzip", style: .default, handler: { _ in self.unzipFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Hex Edit", style: .default, handler: { _ in self.hexEditFile(at: fileURL) })) - default: - menu.addAction(UIAlertAction(title: "Open as Hex", style: .default, handler: { _ in self.openHexEditor(fileURL) })) - } - - menu.addAction(UIAlertAction(title: "Rename", style: .default, handler: { _ in self.renameFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { _ in self.deleteFile(at: fileURL) })) - menu.addAction(UIAlertAction(title: "Share", style: .default, handler: { _ in self.shareFile(at: fileURL) })) - - if let popoverController = menu.popoverPresentationController { - popoverController.sourceView = self.view - popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) - popoverController.permittedArrowDirections = [] - } - - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - private func selectFiles() { // Implement select files functionality } - + @objc private func uploadFile() { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = self @@ -230,7 +171,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe documentPicker.modalPresentationStyle = .formSheet present(documentPicker, animated: true, completion: nil) } - + private func createNewFolder() { showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in let folderURL = self.documentsDirectory.appendingPathComponent(folderName) @@ -242,7 +183,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } } } - + private func createNewFile() { showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in let fileURL = self.documentsDirectory.appendingPathComponent(fileName) @@ -250,7 +191,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe self.loadFiles() } } - + private func renameFile(at fileURL: URL) { showInputAlert(title: "Rename File", message: "Enter new file name", actionTitle: "Rename", initialText: fileURL.lastPathComponent) { newName in let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) @@ -271,7 +212,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } } } - + private func deleteFile(at fileURL: URL) { activityIndicator.startAnimating() DispatchQueue.global().async { @@ -308,7 +249,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } } } - + private func shareFile(at fileURL: URL) { let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) present(activityController, animated: true, completion: nil) @@ -347,7 +288,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } - HomeViewUI.fileListTableView.reloadData() + fileListTableView.reloadData() } // MARK: - UITableViewDragDelegate @@ -388,11 +329,11 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) alert.addAction(action) - + if handler != nil { alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) } - + present(alert, animated: true, completion: nil) } From 430706f1668744f49324d43ff0679f05a8199a08 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 08:42:18 -0400 Subject: [PATCH 163/391] Update HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift index 1b6fb519..cdbe45eb 100644 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -5,11 +5,11 @@ extension HomeViewController { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) alert.addAction(action) - + if handler != nil { alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) } - + present(alert, animated: true, completion: nil) } From dc4ee34fa5cf48e15b709cc23661c390d2948c20 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 08:46:02 -0400 Subject: [PATCH 164/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index e91ccb30..f747f835 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -3,21 +3,21 @@ import ZIPFoundation extension HomeViewController { - @objc private func uploadFile() { + @objc func uploadFile() { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = self documentPicker.modalPresentationStyle = .formSheet present(documentPicker, animated: true, completion: nil) } - private func importFile() { + func importFile() { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = self documentPicker.modalPresentationStyle = .formSheet present(documentPicker, animated: true, completion: nil) } - - private func createNewFolder() { + + func createNewFolder() { showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in let folderURL = self.documentsDirectory.appendingPathComponent(folderName) do { @@ -28,8 +28,8 @@ extension HomeViewController { } } } - - private func createNewFile() { + + func createNewFile() { showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in let fileURL = self.documentsDirectory.appendingPathComponent(fileName) self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) @@ -37,7 +37,7 @@ extension HomeViewController { } } - private func renameFile(at fileURL: URL) { + func renameFile(at fileURL: URL) { showInputAlert(title: "Rename File", message: "Enter new file name", actionTitle: "Rename", initialText: fileURL.lastPathComponent) { newName in let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) self.activityIndicator.startAnimating() @@ -57,8 +57,8 @@ extension HomeViewController { } } } - - private func deleteFile(at fileURL: URL) { + + func deleteFile(at fileURL: URL) { activityIndicator.startAnimating() DispatchQueue.global().async { do { @@ -76,7 +76,7 @@ extension HomeViewController { } } - private func unzipFile(at fileURL: URL) { + func unzipFile(at fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") activityIndicator.startAnimating() DispatchQueue.global().async { @@ -94,8 +94,8 @@ extension HomeViewController { } } } - - private func shareFile(at fileURL: URL) { + + func shareFile(at fileURL: URL) { let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) present(activityController, animated: true, completion: nil) } From 9e2e4eb499c795a3990227d2e616c04f53af99f7 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 08:55:37 -0400 Subject: [PATCH 165/391] Update IconsListViewController.swift --- .../AppIcon/IconsListViewController.swift | 174 +++++++++--------- 1 file changed, 83 insertions(+), 91 deletions(-) diff --git a/iOS/Views/Settings/AppIcon/IconsListViewController.swift b/iOS/Views/Settings/AppIcon/IconsListViewController.swift index 67429ad6..48ba99bc 100644 --- a/iOS/Views/Settings/AppIcon/IconsListViewController.swift +++ b/iOS/Views/Settings/AppIcon/IconsListViewController.swift @@ -1,100 +1,92 @@ -// -// IconsListViewController.swift -// feather -// -// Created by samara on 8/11/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import UIKit class IconsListViewController: UITableViewController { - - public class func altImage(_ name: String) -> UIImage { - let path = Bundle.main.bundleURL.appendingPathComponent(name + "@2x.png") - return UIImage(contentsOfFile: path.path) ?? UIImage() - } - - var sections: [String: [AltIcon]] = [ - "Main": [ - AltIcon(displayName: "Backdoor", author: "BDG", key: nil, image: altImage("AppIcon60x60")), - AltIcon(displayName: "macOS Backdoor", author: "BDG", key: "Mac", image: altImage("Mac")), - AltIcon(displayName: "Evil Backdoor", author: "BDG", key: "Evil", image: altImage("Evil")), - AltIcon(displayName: "Classic Backdoor", author: "BDG", key: "Early", image: altImage("Early")) - ], - "Wingio": [ - AltIcon(displayName: "Backdoor", author: "BDG", key: "Wing", image: altImage("Wing")), - ] - ] - - init() { super.init(style: .insetGrouped) } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - override func viewDidLoad() { - super.viewDidLoad() - setupViews() - setupNavigation() - } - - fileprivate func setupViews() { - self.tableView.delegate = self - self.tableView.dataSource = self - self.tableView.rowHeight = 75 - } - - fileprivate func setupNavigation() { - self.title = String.localized("SETTINGS_VIEW_CONTROLLER_CELL_APP_ICON") - self.navigationItem.largeTitleDisplayMode = .never - } - - private func sectionTitles() -> [String] { - return Array(sections.keys).sorted() - } - - private func icons(forSection section: Int) -> [AltIcon] { - let title = sectionTitles()[section] - return sections[title] ?? [] - } + + public class func altImage(_ name: String) -> UIImage { + let path = Bundle.main.bundleURL.appendingPathComponent(name + ".png") + return UIImage(contentsOfFile: path.path) ?? UIImage() + } + + var sections: [String: [AltIcon]] = [ + "Main": [ + AltIcon(displayName: "Backdoor", author: "BDG", key: nil, image: altImage("AppIcon60x60")), + AltIcon(displayName: "macOS Backdoor", author: "BDG", key: "Mac", image: altImage("Mac")), + AltIcon(displayName: "Evil Backdoor", author: "BDG", key: "Evil", image: altImage("Evil")), + AltIcon(displayName: "Classic Backdoor", author: "BDG", key: "Early", image: altImage("Early")) + ], + "Wingio": [ + AltIcon(displayName: "Backdoor", author: "BDG", key: "Wing", image: altImage("Wing")), + ] + ] + + init() { super.init(style: .insetGrouped) } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func viewDidLoad() { + super.viewDidLoad() + setupViews() + setupNavigation() + } + + fileprivate func setupViews() { + self.tableView.delegate = self + self.tableView.dataSource = self + self.tableView.rowHeight = 75 + } + + fileprivate func setupNavigation() { + self.title = String.localized("SETTINGS_VIEW_CONTROLLER_CELL_APP_ICON") + self.navigationItem.largeTitleDisplayMode = .never + } + + private func sectionTitles() -> [String] { + return Array(sections.keys).sorted() + } + + private func icons(forSection section: Int) -> [AltIcon] { + let title = sectionTitles()[section] + return sections[title] ?? [] + } } extension IconsListViewController { - override func numberOfSections(in tableView: UITableView) -> Int { return sectionTitles().count } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return icons(forSection: section).count } - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 40 } - - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let title = sectionTitles()[section] - let headerView = InsetGroupedSectionHeader(title: title) - return headerView - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = IconsListTableViewCell() - let icon = icons(forSection: indexPath.section)[indexPath.row] - cell.altIcon = icon - if UIApplication.shared.alternateIconName == icon.key { - cell.accessoryType = .checkmark - } else { - cell.accessoryType = .none - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - let icon = icons(forSection: indexPath.section)[indexPath.row] - - UIApplication.shared.setAlternateIconName(icon.key) { error in - Debug.shared.log(message:"\(error?.localizedDescription ?? "Unknown Error")") - } - - self.tableView.reloadRows(at: self.tableView.indexPathsForVisibleRows ?? [IndexPath](), with: .none) - } + override func numberOfSections(in tableView: UITableView) -> Int { return sectionTitles().count } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return icons(forSection: section).count } + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 40 } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let title = sectionTitles()[section] + let headerView = InsetGroupedSectionHeader(title: title) + return headerView + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = IconsListTableViewCell() + let icon = icons(forSection: indexPath.section)[indexPath.row] + cell.altIcon = icon + if UIApplication.shared.alternateIconName == icon.key { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let icon = icons(forSection: indexPath.section)[indexPath.row] + + UIApplication.shared.setAlternateIconName(icon.key) { error in + Debug.shared.log(message:"\(error?.localizedDescription ?? "Unknown Error")") + } + + self.tableView.reloadRows(at: self.tableView.indexPathsForVisibleRows ?? [IndexPath](), with: .none) + } } struct AltIcon { - var displayName: String - var author: String - var key: String? - var image: UIImage -} + var displayName: String + var author: String + var key: String? + var image: UIImage +} \ No newline at end of file From 7c62cff4531c610e4d7ff4c04fdcb82e6c659330 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 08:56:09 -0400 Subject: [PATCH 166/391] Update AppSigner.swift --- Shared/Magic/AppSigner.swift | 463 +++++++++++------------------------ 1 file changed, 147 insertions(+), 316 deletions(-) diff --git a/Shared/Magic/AppSigner.swift b/Shared/Magic/AppSigner.swift index 3b151411..122f7379 100644 --- a/Shared/Magic/AppSigner.swift +++ b/Shared/Magic/AppSigner.swift @@ -1,325 +1,156 @@ -// -// AppSigner.swift -// feather -// -// Created by HAHALOSAH on 7/17/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import Foundation import UIKit import AlertKit import CoreData func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, signingOptions: SigningDataWrapper, appPath: URL, completion: @escaping (Result<(URL, NSManagedObject), Error>) -> Void) { - UIApplication.shared.isIdleTimerDisabled = true - DispatchQueue(label: "Signing").async { - let fileManager = FileManager.default - let tmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) - let tmpDirApp = tmpDir.appendingPathComponent(appPath.lastPathComponent) - var iconURL = "" - - - do { - Debug.shared.log(message: "============================================") - Debug.shared.log(message: "\(mainOptions.mainOptions)") - Debug.shared.log(message: "============================================") - Debug.shared.log(message: "\(signingOptions.signingOptions)") - Debug.shared.log(message: "============================================") - try fileManager.createDirectory(at: tmpDir, withIntermediateDirectories: true) - try fileManager.copyItem(at: appPath, to: tmpDirApp) - - if let info = NSDictionary(contentsOf: tmpDirApp.appendingPathComponent("Info.plist"))!.mutableCopy() as? NSMutableDictionary { - try updateInfoPlist(infoDict: info, main: mainOptions, options: signingOptions, icon: mainOptions.mainOptions.iconURL, app: tmpDirApp) - - if let iconsDict = info["CFBundleIcons"] as? [String: Any], - let primaryIconsDict = iconsDict["CFBundlePrimaryIcon"] as? [String: Any], - let iconFiles = primaryIconsDict["CFBundleIconFiles"] as? [String], - let iconFileName = iconFiles.first { - iconURL = iconFileName - } - } - - let handler = TweakHandler(urls: signingOptions.signingOptions.toInject, app: tmpDirApp) - try handler.getInputFiles() - - if !mainOptions.mainOptions.removeInjectPaths.isEmpty { - if let appexe = try? TweakHandler.findExecutable(at: tmpDirApp) { - _ = uninstallDylibs(filePath: appexe.path, dylibPaths: mainOptions.mainOptions.removeInjectPaths) - } - } - - try updatePlugIns(options: signingOptions, app: tmpDirApp) - try removeDumbAssPlaceHolderExtension(options: signingOptions, app: tmpDirApp) - try updateMobileProvision(app: tmpDirApp) - - let certPath = try CoreDataManager.shared.getCertifcatePath(source: mainOptions.mainOptions.certificate) - let provisionPath = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.provisionPath ?? "")").path - let p12Path = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.p12Path ?? "")").path - - Debug.shared.log(message: "🦋 Start Signing 🦋") - - try signAppWithZSign(tmpDirApp: tmpDirApp, certPaths: (provisionPath, p12Path), password: mainOptions.mainOptions.certificate?.password ?? "", main: mainOptions, options: signingOptions) - - Debug.shared.log(message: "🦋 End Signing 🦋") - - let signedUUID = UUID().uuidString - try fileManager.createDirectory(at: getDocumentsDirectory().appendingPathComponent("Apps/Signed"), withIntermediateDirectories: true) - let signedPath = getDocumentsDirectory().appendingPathComponent("Apps/Signed").appendingPathComponent(signedUUID) - try fileManager.moveItem(at: tmpDir, to: signedPath) - - DispatchQueue.main.async { - var signedAppObject: NSManagedObject? = nil - - CoreDataManager.shared.addToSignedApps( - version: (mainOptions.mainOptions.version ?? bundle.version)!, - name: (mainOptions.mainOptions.name ?? bundle.name)!, - bundleidentifier: (mainOptions.mainOptions.bundleId ?? bundle.bundleId)!, - iconURL: iconURL, - uuid: signedUUID, - appPath: appPath.lastPathComponent, - timeToLive: mainOptions.mainOptions.certificate?.certData?.expirationDate ?? Date(), - teamName: mainOptions.mainOptions.certificate?.certData?.name ?? "", - originalSourceURL: bundle.sourceURL - ) { result in - - - switch result { - case .success(let signedApp): - signedAppObject = signedApp - case .failure(let error): - Debug.shared.log(message: "signApp: \(error)", type: .error) - completion(.failure(error)) - } - } - - Debug.shared.log(message: String.localized("SUCCESS_SIGNED", arguments: "\((mainOptions.mainOptions.name ?? bundle.name) ?? String.localized("UNKNOWN"))"), type: .success) - Debug.shared.log(message: "============================================") - - UIApplication.shared.isIdleTimerDisabled = false - completion(.success((signedPath, signedAppObject!))) - } - } catch { - DispatchQueue.main.async { - UIApplication.shared.isIdleTimerDisabled = false - Debug.shared.log(message: "signApp: \(error)", type: .critical) - completion(.failure(error)) - } - } - } -} - - -func resignApp(certificate: Certificate, appPath: URL, completion: @escaping (Bool) -> Void) { - UIApplication.shared.isIdleTimerDisabled = true - DispatchQueue(label: "Resigning").async { - do { - let certPath = try CoreDataManager.shared.getCertifcatePath(source: certificate) - let provisionPath = certPath.appendingPathComponent("\(certificate.provisionPath ?? "")").path - let p12Path = certPath.appendingPathComponent("\(certificate.p12Path ?? "")").path - - Debug.shared.log(message: "============================================") - Debug.shared.log(message: "🦋 Start Resigning 🦋") - - try signAppWithZSign(tmpDirApp: appPath, certPaths: (provisionPath, p12Path), password: certificate.password ?? "") - - Debug.shared.log(message: "🦋 End Resigning 🦋") - DispatchQueue.main.async { - UIApplication.shared.isIdleTimerDisabled = false - Debug.shared.log(message: String.localized("SUCCESS_RESIGN"), type: .success) - } - Debug.shared.log(message: "============================================") - completion(true) - } catch { - Debug.shared.log(message: "\(error)", type: .warning) - completion(false) - } - } -} - -private func signAppWithZSign(tmpDirApp: URL, certPaths: (provisionPath: String, p12Path: String), password: String, main: SigningMainDataWrapper? = nil, options: SigningDataWrapper? = nil) throws { - if zsign(tmpDirApp.path, - certPaths.provisionPath, - certPaths.p12Path, - password, - main?.mainOptions.bundleId ?? "", - main?.mainOptions.name ?? "", - main?.mainOptions.version ?? "", - options?.signingOptions.removeProvisioningFile ?? true - ) != 0 { - throw NSError(domain: "AppSigningErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: String.localized("ERROR_ZSIGN_FAILED")]) - } -} - -func injectDylib(filePath: String, dylibPath: String, weakInject: Bool) -> Bool { - let bCreate: Bool = false - let success = InjectDyLib(filePath, dylibPath, weakInject, bCreate) - return success -} - -func changeDylib(filePath: String, oldPath: String, newPath: String) -> Bool { - let success = ChangeDylibPath(filePath, oldPath, newPath) - return success -} - -func updateMobileProvision(app: URL) throws { - let provisioningFilePath = app.appendingPathComponent("embedded.mobileprovision") - if FileManager.default.fileExists(atPath: provisioningFilePath.path) { - do { - try FileManager.default.removeItem(at: provisioningFilePath) - Debug.shared.log(message: "Embedded.mobileprovision file removed successfully!") - } catch { - throw error - } - } else { - Debug.shared.log(message: "Could not find any mobileprovision to remove. ") - } -} - -func listDylibs(filePath: String) -> [String]? { - let dylibPathsArray = NSMutableArray() - - let success = ListDylibs(filePath, dylibPathsArray) - - if success { - let dylibPaths = dylibPathsArray as! [String] - return dylibPaths - } else { - Debug.shared.log(message: "Failed to list dylibs.") - return nil - } -} - -func uninstallDylibs(filePath: String, dylibPaths: [String]) -> Bool { - return UninstallDylibs(filePath, dylibPaths) -} - - -func updatePlugIns(options: SigningDataWrapper, app: URL) throws { - if options.signingOptions.removePlugins { - let filemanager = FileManager.default - let path = app.appendingPathComponent("PlugIns") - if filemanager.fileExists(atPath: path.path) { - do { - try filemanager.removeItem(at: path) - Debug.shared.log(message: "Removed PlugIns!") - } catch { - throw error - } - } else { - Debug.shared.log(message: "Could not find any PlugIns to remove.") - } - } -} - -func removeDumbAssPlaceHolderExtension(options: SigningDataWrapper, app: URL) throws { - if options.signingOptions.removeWatchPlaceHolder { - let filemanager = FileManager.default - let path = app.appendingPathComponent("com.apple.WatchPlaceholder") - if filemanager.fileExists(atPath: path.path) { - do { - try filemanager.removeItem(at: path) - Debug.shared.log(message: "Removed placeholder watch app!") - } catch { - throw error - } - } else { - Debug.shared.log(message: "Placeholder watch app not found.") - } - } + UIApplication.shared.isIdleTimerDisabled = true + DispatchQueue(label: "Signing").async { + let fileManager = FileManager.default + let tmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) + let tmpDirApp = tmpDir.appendingPathComponent(appPath.lastPathComponent) + var iconURL = "" + + do { + Debug.shared.log(message: "============================================") + Debug.shared.log(message: "\(mainOptions.mainOptions)") + Debug.shared.log(message: "============================================") + Debug.shared.log(message: "\(signingOptions.signingOptions)") + Debug.shared.log(message: "============================================") + try fileManager.createDirectory(at: tmpDir, withIntermediateDirectories: true) + try fileManager.copyItem(at: appPath, to: tmpDirApp) + + if let info = NSDictionary(contentsOf: tmpDirApp.appendingPathComponent("Info.plist"))!.mutableCopy() as? NSMutableDictionary { + try updateInfoPlist(infoDict: info, main: mainOptions, options: signingOptions, icon: mainOptions.mainOptions.iconURL, app: tmpDirApp) + + if let iconsDict = info["CFBundleIcons"] as? [String: Any], + let primaryIconsDict = iconsDict["CFBundlePrimaryIcon"] as? [String: Any], + let iconFiles = primaryIconsDict["CFBundleIconFiles"] as? [String], + let iconFileName = iconFiles.first { + iconURL = iconFileName + } + } + + let handler = TweakHandler(urls: signingOptions.signingOptions.toInject, app: tmpDirApp) + try handler.getInputFiles() + + if !mainOptions.mainOptions.removeInjectPaths.isEmpty { + if let appexe = try? TweakHandler.findExecutable(at: tmpDirApp) { + _ = uninstallDylibs(filePath: appexe.path, dylibPaths: mainOptions.mainOptions.removeInjectPaths) + } + } + + try updatePlugIns(options: signingOptions, app: tmpDirApp) + try removeDumbAssPlaceHolderExtension(options: signingOptions, app: tmpDirApp) + try updateMobileProvision(app: tmpDirApp) + + let certPath = try CoreDataManager.shared.getCertifcatePath(source: mainOptions.mainOptions.certificate) + let provisionPath = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.provisionPath ?? "")").path + let p12Path = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.p12Path ?? "")").path + + Debug.shared.log(message: "🦋 Start Signing 🦋") + + try signAppWithZSign(tmpDirApp: tmpDirApp, certPaths: (provisionPath, p12Path), password: mainOptions.mainOptions.certificate?.password ?? "", main: mainOptions, options: signingOptions) + + Debug.shared.log(message: "🦋 End Signing 🦋") + + let signedUUID = UUID().uuidString + try fileManager.createDirectory(at: getDocumentsDirectory().appendingPathComponent("Apps/Signed"), withIntermediateDirectories: true) + let signedPath = getDocumentsDirectory().appendingPathComponent("Apps/Signed").appendingPathComponent(signedUUID) + try fileManager.moveItem(at: tmpDir, to: signedPath) + + DispatchQueue.main.async { + var signedAppObject: NSManagedObject? = nil + + CoreDataManager.shared.addToSignedApps( + version: (mainOptions.mainOptions.version ?? bundle.version)!, + name: (mainOptions.mainOptions.name ?? bundle.name)!, + bundleidentifier: (mainOptions.mainOptions.bundleId ?? bundle.bundleId)!, + iconURL: iconURL, + uuid: signedUUID, + appPath: appPath.lastPathComponent, + timeToLive: mainOptions.mainOptions.certificate?.certData?.expirationDate ?? Date(), + teamName: mainOptions.mainOptions.certificate?.certData?.name ?? "", + originalSourceURL: bundle.sourceURL + ) { result in + + switch result { + case .success(let signedApp): + signedAppObject = signedApp + case .failure(let error): + Debug.shared.log(message: "signApp: \(error)", type: .error) + completion(.failure(error)) + } + } + + Debug.shared.log(message: String.localized("SUCCESS_SIGNED", arguments: "\((mainOptions.mainOptions.name ?? bundle.name) ?? String.localized("UNKNOWN"))"), type: .success) + Debug.shared.log(message: "============================================") + + UIApplication.shared.isIdleTimerDisabled = false + completion(.success((signedPath, signedAppObject!))) + } + } catch { + DispatchQueue.main.async { + UIApplication.shared.isIdleTimerDisabled = false + Debug.shared.log(message: "signApp: \(error)", type: .critical) + completion(.failure(error)) + } + } + } } func updateInfoPlist(infoDict: NSMutableDictionary, main: SigningMainDataWrapper, options: SigningDataWrapper, icon: UIImage?, app: URL) throws { - if (main.mainOptions.iconURL != nil) { - - let imageSizes = [ - (width: 120, height: 120, name: "FRIcon60x60@2x.png"), - (width: 152, height: 152, name: "FRIcon76x76@2x~ipad.png") - ] - - for imageSize in imageSizes { - let resizedImage = main.mainOptions.iconURL!.resize(imageSize.width, imageSize.height) - let imageData = resizedImage.pngData() - let fileURL = app.appendingPathComponent(imageSize.name) - - do { - try imageData?.write(to: fileURL) - Debug.shared.log(message: "Saved image to: \(fileURL)") - } catch { - Debug.shared.log(message: "Failed to save image: \(imageSize.name), error: \(error)") - throw error - } - } - - let cfBundleIcons: [String: Any] = [ - "CFBundlePrimaryIcon": [ - "CFBundleIconFiles": ["FRIcon60x60"], - "CFBundleIconName": "FRIcon" - ] - ] - - let cfBundleIconsIpad: [String: Any] = [ - "CFBundlePrimaryIcon": [ - "CFBundleIconFiles": ["FRIcon60x60", "FRIcon76x76"], - "CFBundleIconName": "FRIcon" - ] - ] - - infoDict["CFBundleIcons"] = cfBundleIcons - infoDict["CFBundleIcons~ipad"] = cfBundleIconsIpad - - } else { - Debug.shared.log(message: "updateInfoPlist.updateicon: Does not include an icon, skipping!") - } - - if options.signingOptions.forceTryToLocalize && (main.mainOptions.name != nil) { - if let displayName = infoDict.value(forKey: "CFBundleDisplayName") as? String { - if displayName != main.mainOptions.name { - updateLocalizedInfoPlist(in: app, newDisplayName: main.mainOptions.name!) - } - } else { - Debug.shared.log(message: "updateInfoPlist.displayName: CFBundleDisplayName not found, skipping!") - } - } - - if options.signingOptions.forceFileSharing { infoDict.setObject(true, forKey: "UISupportsDocumentBrowser" as NSCopying) } - if options.signingOptions.forceiTunesFileSharing { infoDict.setObject(true, forKey: "UIFileSharingEnabled" as NSCopying) } - if options.signingOptions.removeSupportedDevices { infoDict.removeObject(forKey: "UISupportedDevices") } - if options.signingOptions.removeURLScheme { infoDict.removeObject(forKey: "CFBundleURLTypes") } - if options.signingOptions.forceProMotion { infoDict.setObject(true, forKey: "CADisableMinimumFrameDurationOnPhone" as NSCopying)} - if options.signingOptions.forceGameMode { infoDict.setObject(true, forKey: "GCSupportsGameMode" as NSCopying)} - if options.signingOptions.forceForceFullScreen { infoDict.setObject(true, forKey: "UIRequiresFullScreen" as NSCopying) } - if options.signingOptions.forceMinimumVersion != "Automatic" { infoDict.setObject(options.signingOptions.forceMinimumVersion, forKey: "MinimumOSVersion" as NSCopying) } - if options.signingOptions.forceLightDarkAppearence != "Automatic" { infoDict.setObject(options.signingOptions.forceLightDarkAppearence, forKey: "UIUserInterfaceStyle" as NSCopying)} - try infoDict.write(to: app.appendingPathComponent("Info.plist")) -} - -func updateLocalizedInfoPlist(in appDirectory: URL, newDisplayName: String) { - let fileManager = FileManager.default - do { - let contents = try fileManager.contentsOfDirectory(at: appDirectory, includingPropertiesForKeys: nil) - let localizationBundles = contents.filter { $0.pathExtension == "lproj" } - - guard !localizationBundles.isEmpty else { - Debug.shared.log(message: "No .lproj directories found in \(appDirectory.path), skipping!") - return - } - - for localizationBundle in localizationBundles { - let infoPlistStringsURL = localizationBundle.appendingPathComponent("InfoPlist.strings") - - if fileManager.fileExists(atPath: infoPlistStringsURL.path) { - var localizedStrings = try String(contentsOf: infoPlistStringsURL, encoding: .utf8) - let localizedDict = NSDictionary(contentsOf: infoPlistStringsURL) as! [String: String] - - if localizedDict["CFBundleDisplayName"] != newDisplayName { - localizedStrings = localizedStrings.replacingOccurrences(of: localizedDict["CFBundleDisplayName"] ?? "", with: newDisplayName) - try localizedStrings.write(to: infoPlistStringsURL, atomically: true, encoding: .utf8) - Debug.shared.log(message: "Updated CFBundleDisplayName in \(infoPlistStringsURL.path)") - } - } - } - } catch { - Debug.shared.log(message: "Unable to localize, skipping!", type: .debug) - } -} + if let iconURL = main.mainOptions.iconURL { + + let iconSizes = [60, 76, 120, 152] + var cfBundleIconFiles = [String]() + + for size in iconSizes { + let fileName = "AppIcon\(size)x\(size).png" + let resizedImage = iconURL.resize(size, size) + let imageData = resizedImage.pngData() + let fileURL = app.appendingPathComponent(fileName) + + do { + try imageData?.write(to: fileURL) + Debug.shared.log(message: "Saved image to: \(fileURL)") + cfBundleIconFiles.append(fileName) + } catch { + Debug.shared.log(message: "Failed to save image: \(fileName), error: \(error)") + throw error + } + } + + let cfBundleIcons: [String: Any] = [ + "CFBundlePrimaryIcon": [ + "CFBundleIconFiles": cfBundleIconFiles, + "CFBundleIconName": "AppIcon" + ] + ] + + infoDict["CFBundleIcons"] = cfBundleIcons + } else { + Debug.shared.log(message: "updateInfoPlist.updateicon: Does not include an icon, skipping!") + } + + if options.signingOptions.forceTryToLocalize && (main.mainOptions.name != nil) { + if let displayName = infoDict.value(forKey: "CFBundleDisplayName") as? String { + if displayName != main.mainOptions.name { + updateLocalizedInfoPlist(in: app, newDisplayName: main.mainOptions.name!) + } + } else { + Debug.shared.log(message: "updateInfoPlist.displayName: CFBundleDisplayName not found, skipping!") + } + } + + if options.signingOptions.forceFileSharing { infoDict.setObject(true, forKey: "UISupportsDocumentBrowser" as NSCopying) } + if options.signingOptions.forceiTunesFileSharing { infoDict.setObject(true, forKey: "UIFileSharingEnabled" as NSCopying) } + if options.signingOptions.removeSupportedDevices { infoDict.removeObject(forKey: "UISupportedDevices") } + if options.signingOptions.removeURLScheme { infoDict.removeObject(forKey: "CFBundleURLTypes") } + if options.signingOptions.forceProMotion { infoDict.setObject(true, forKey: "CADisableMinimumFrameDurationOnPhone" as NSCopying) } + if options.signingOptions.forceGameMode { infoDict.setObject(true, forKey: "GCSupportsGameMode" as NSCopying) } + if options.signingOptions.forceForceFullScreen { infoDict.setObject(true, forKey: "UIRequiresFullScreen" as NSCopying) } + if options.signingOptions.forceMinimumVersion != "Automatic" { infoDict.setObject(options.signingOptions.forceMinimumVersion, forKey: "MinimumOSVersion" as NSCopying) } + if options.signingOptions.forceLightDarkAppearence != "Automatic" { infoDict.setObject(options.signingOptions.forceLightDarkAppearence, forKey: "UIUserInterfaceStyle" as NSCopying) } + try infoDict.write(to: app.appendingPathComponent("Info.plist")) +} \ No newline at end of file From a5ae0cc54406346255f975bfad583d1b458c40fe Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 09:00:40 -0400 Subject: [PATCH 167/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 432b5fd6..c1442cf4 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -354,4 +354,15 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe private func handleError(_ error: Error, withTitle title: String = "Error") { presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") } + + // MARK: - UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return filteredFileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) + cell.textLabel?.text = filteredFileList[indexPath.row] + return cell + } } \ No newline at end of file From a4b443ac9ca35ae412a76b17e6737099e16dd961 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 09:11:35 -0400 Subject: [PATCH 168/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index c1442cf4..e3d6c0f1 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -9,7 +9,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe private let fileManager = FileManager.default private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name - private var documentsDirectory: URL { + var documentsDirectory: URL { // Changed from private to internal (default) let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") createFilesDirectoryIfNeeded(at: directory) return directory @@ -19,8 +19,8 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe case name, date, size } - private let fileListTableView = UITableView() - private let activityIndicator = UIActivityIndicatorView(style: .large) + let fileListTableView = UITableView() // Changed from private to internal (default) + let activityIndicator = UIActivityIndicatorView(style: .large) // Changed from private to internal (default) // MARK: - Lifecycle override func viewDidLoad() { From 3c0f8b814f72b6f7ded3c5ee2db6f0ce2c8640f8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 09:27:52 -0400 Subject: [PATCH 169/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 152 ++++++------------------ 1 file changed, 34 insertions(+), 118 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index e3d6c0f1..5c3a4cc5 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -2,26 +2,29 @@ import UIKit import ZIPFoundation class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { - + // MARK: - Properties private var fileList: [String] = [] private var filteredFileList: [String] = [] private let fileManager = FileManager.default private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name + let fileHandlers = HomeViewFileHandlers() + let utilities = HomeViewUtilities() + var documentsDirectory: URL { // Changed from private to internal (default) let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") createFilesDirectoryIfNeeded(at: directory) return directory } - + enum SortOrder { case name, date, size } - + let fileListTableView = UITableView() // Changed from private to internal (default) let activityIndicator = UIActivityIndicatorView(style: .large) // Changed from private to internal (default) - + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -30,42 +33,42 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe loadFiles() configureTableView() } - + // MARK: - UI Setup private func setupUI() { view.backgroundColor = .systemBackground - + // Setup Navigation Bar let navItem = UINavigationItem(title: "Files") let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) navItem.rightBarButtonItems = [menuButton, sortButton] navigationController?.navigationBar.setItems([navItem], animated: false) - + // Add UI elements to the view view.addSubview(fileListTableView) view.addSubview(activityIndicator) - + // Set up constraints NSLayoutConstraint.activate([ fileListTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) - + // Register the table view cell fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") } - + private func setupActivityIndicator() { activityIndicator.translatesAutoresizingMaskIntoConstraints = false activityIndicator.hidesWhenStopped = true } - + private func configureTableView() { fileListTableView.delegate = self fileListTableView.dataSource = self @@ -76,7 +79,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe navigationItem.hidesSearchBarWhenScrolling = false filteredFileList = fileList } - + // MARK: - Load Files private func loadFiles() { activityIndicator.startAnimating() @@ -92,12 +95,12 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } catch { DispatchQueue.main.async { self?.activityIndicator.stopAnimating() - self?.handleError(error, withTitle: "Loading Files") + self?.utilities.handleError(error, withTitle: "Loading Files") } } } } - + private func sortFiles() { switch sortOrder { case .name: @@ -108,17 +111,17 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe fileList.sort { getFileSize($0) < getFileSize($1) } } } - + private func getFileDate(_ fileName: String) -> Date { let fileURL = documentsDirectory.appendingPathComponent(fileName) return (try? fileManager.attributesOfItem(atPath: fileURL.path)[.modificationDate] as? Date) ?? Date.distantPast } - + private func getFileSize(_ fileName: String) -> UInt64 { let fileURL = documentsDirectory.appendingPathComponent(fileName) return (try? fileManager.attributesOfItem(atPath: fileURL.path)[.size] as? UInt64) ?? 0 } - + // MARK: - Actions @objc private func showMenu() { let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) @@ -136,7 +139,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(menu, animated: true, completion: nil) } - + @objc private func changeSortOrder() { let sortMenu = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) ["Name", "Date", "Size"].forEach { sortOption in @@ -153,39 +156,39 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe sortMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) present(sortMenu, animated: true, completion: nil) } - + private func selectFiles() { // Implement select files functionality } - + @objc private func uploadFile() { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = self documentPicker.modalPresentationStyle = .formSheet present(documentPicker, animated: true, completion: nil) } - + private func importFile() { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = self documentPicker.modalPresentationStyle = .formSheet present(documentPicker, animated: true, completion: nil) } - + private func createNewFolder() { - showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in + utilities.showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in let folderURL = self.documentsDirectory.appendingPathComponent(folderName) do { try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) self.loadFiles() } catch { - self.handleError(error, withTitle: "Creating Folder") + self.utilities.handleError(error, withTitle: "Creating Folder") } } } - + private func createNewFile() { - showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in + utilities.showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in let fileURL = self.documentsDirectory.appendingPathComponent(fileName) self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) self.loadFiles() @@ -193,7 +196,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } private func renameFile(at fileURL: URL) { - showInputAlert(title: "Rename File", message: "Enter new file name", actionTitle: "Rename", initialText: fileURL.lastPathComponent) { newName in + utilities.showInputAlert(title: "Rename File", message: "Enter new file name", actionTitle: "Rename", initialText: fileURL.lastPathComponent) { newName in let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) self.activityIndicator.startAnimating() DispatchQueue.global().async { @@ -206,70 +209,13 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } catch { DispatchQueue.main.async { self.activityIndicator.stopAnimating() - self.handleError(error, withTitle: "Renaming File") + self.utilities.handleError(error, withTitle: "Renaming File") } } } } } - private func deleteFile(at fileURL: URL) { - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.removeItem(at: fileURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.handleError(error, withTitle: "Deleting File") - } - } - } - } - - private func unzipFile(at fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.handleError(error, withTitle: "Unzipping File") - } - } - } - } - - private func shareFile(at fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - present(activityController, animated: true, completion: nil) - } - - private func openTextEditor(_ fileURL: URL) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } - - private func openPlistEditor(_ fileURL: URL) { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } - - private func openHexEditor(_ fileURL: URL) { - let hexEditorVC = HexEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(hexEditorVC, animated: true) - } - // MARK: - UIDocumentPickerViewControllerDelegate func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let selectedFileURL = urls.first else { @@ -280,7 +226,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe try fileManager.copyItem(at: selectedFileURL, to: destinationURL) loadFiles() } catch { - handleError(error, withTitle: "Importing File") + utilities.handleError(error, withTitle: "Importing File") } } @@ -321,38 +267,8 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe do { try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) } catch { - handleError(error, withTitle: "Creating Files Directory") - } - } - - private func presentAlert(title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) - alert.addAction(action) - - if handler != nil { - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - } - - present(alert, animated: true, completion: nil) - } - - private func showInputAlert(title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = initialText - } - let confirmAction = UIAlertAction(title: actionTitle, style: .default) { [weak alertController] _ in - guard let textField = alertController?.textFields?.first, let text = textField.text else { return } - completion(text) + utilities.handleError(error, withTitle: "Creating Files Directory") } - alertController.addAction(confirmAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - private func handleError(_ error: Error, withTitle title: String = "Error") { - presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") } // MARK: - UITableViewDataSource From 532085c5549b63a05c3ecc7c6399363fb1f68ed0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 09:38:06 -0400 Subject: [PATCH 170/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 128 ++++++------------------ 1 file changed, 31 insertions(+), 97 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 5c3a4cc5..445e2edc 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -9,10 +9,10 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe private let fileManager = FileManager.default private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name - let fileHandlers = HomeViewFileHandlers() let utilities = HomeViewUtilities() + let fileHandlers = HomeViewFileHandlers() - var documentsDirectory: URL { // Changed from private to internal (default) + var documentsDirectory: URL { let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") createFilesDirectoryIfNeeded(at: directory) return directory @@ -22,8 +22,8 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe case name, date, size } - let fileListTableView = UITableView() // Changed from private to internal (default) - let activityIndicator = UIActivityIndicatorView(style: .large) // Changed from private to internal (default) + let fileListTableView = UITableView() + let activityIndicator = UIActivityIndicatorView(style: .large) // MARK: - Lifecycle override func viewDidLoad() { @@ -38,18 +38,15 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe private func setupUI() { view.backgroundColor = .systemBackground - // Setup Navigation Bar let navItem = UINavigationItem(title: "Files") let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) navItem.rightBarButtonItems = [menuButton, sortButton] navigationController?.navigationBar.setItems([navItem], animated: false) - // Add UI elements to the view view.addSubview(fileListTableView) view.addSubview(activityIndicator) - // Set up constraints NSLayoutConstraint.activate([ fileListTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), @@ -60,7 +57,6 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) - // Register the table view cell fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") } @@ -81,7 +77,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } // MARK: - Load Files - private func loadFiles() { + func loadFiles() { activityIndicator.startAnimating() DispatchQueue.global().async { [weak self] in do { @@ -129,9 +125,13 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe menu.addAction(UIAlertAction(title: actionTitle, style: .default) { _ in switch actionTitle { case "Select": self.selectFiles() - case "Import": self.importFile() - case "New Folder": self.createNewFolder() - case "New File": self.createNewFile() + case "Import": self.fileHandlers.importFile(viewController: self) + case "New Folder": self.showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in + self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) + } + case "New File": self.showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in + self.fileHandlers.createNewFile(viewController: self, fileName: fileName) + } default: break } }) @@ -162,81 +162,35 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } @objc private func uploadFile() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = self - documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) + fileHandlers.uploadFile(viewController: self) } - private func importFile() { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = self - documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) - } - - private func createNewFolder() { - utilities.showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in - let folderURL = self.documentsDirectory.appendingPathComponent(folderName) - do { - try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) - self.loadFiles() - } catch { - self.utilities.handleError(error, withTitle: "Creating Folder") - } + private func createFilesDirectoryIfNeeded(at directory: URL) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + utilities.handleError(error, withTitle: "Creating Files Directory") } } - private func createNewFile() { - utilities.showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in - let fileURL = self.documentsDirectory.appendingPathComponent(fileName) - self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) - self.loadFiles() - } - } - - private func renameFile(at fileURL: URL) { - utilities.showInputAlert(title: "Rename File", message: "Enter new file name", actionTitle: "Rename", initialText: fileURL.lastPathComponent) { newName in - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - self.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.utilities.handleError(error, withTitle: "Renaming File") - } - } - } - } + // MARK: - UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return filteredFileList.count } - - // MARK: - UIDocumentPickerViewControllerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - return - } - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - utilities.handleError(error, withTitle: "Importing File") - } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) + cell.textLabel?.text = filteredFileList[indexPath.row] + return cell } - + // MARK: - UISearchResultsUpdating func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } fileListTableView.reloadData() } - + // MARK: - UITableViewDragDelegate func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { let item = self.fileList[indexPath.row] @@ -244,7 +198,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe let dragItem = UIDragItem(itemProvider: itemProvider) return [dragItem] } - + // MARK: - UITableViewDropDelegate func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { coordinator.session.loadObjects(ofClass: NSString.self) { items in @@ -253,32 +207,12 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe self.loadFiles() } } - + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { return session.canLoadObjects(ofClass: NSString.self) } - + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { return UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) } - - // MARK: - Helper Methods - private func createFilesDirectoryIfNeeded(at directory: URL) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - utilities.handleError(error, withTitle: "Creating Files Directory") - } - } - - // MARK: - UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return filteredFileList.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) - cell.textLabel?.text = filteredFileList[indexPath.row] - return cell - } } \ No newline at end of file From 0e280dec7fd08862ac0884e6add6fd03970772bb Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 09:38:28 -0400 Subject: [PATCH 171/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 99 +++++++++++------------ 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index f747f835..48a195bb 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -1,102 +1,97 @@ import UIKit import ZIPFoundation -extension HomeViewController { +class HomeViewFileHandlers { + private let fileManager = FileManager.default - @objc func uploadFile() { + func uploadFile(viewController: UIViewController) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = self + documentPicker.delegate = viewController as? UIDocumentPickerDelegate documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) + viewController.present(documentPicker, animated: true, completion: nil) } - func importFile() { + func importFile(viewController: UIViewController) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = self + documentPicker.delegate = viewController as? UIDocumentPickerDelegate documentPicker.modalPresentationStyle = .formSheet - present(documentPicker, animated: true, completion: nil) + viewController.present(documentPicker, animated: true, completion: nil) } - func createNewFolder() { - showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in - let folderURL = self.documentsDirectory.appendingPathComponent(folderName) - do { - try self.fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) - self.loadFiles() - } catch { - self.handleError(error, withTitle: "Creating Folder") - } + func createNewFolder(viewController: HomeViewController, folderName: String) { + let folderURL = viewController.documentsDirectory.appendingPathComponent(folderName) + do { + try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) + viewController.loadFiles() + } catch { + viewController.utilities.handleError(error, withTitle: "Creating Folder") } } - func createNewFile() { - showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in - let fileURL = self.documentsDirectory.appendingPathComponent(fileName) - self.fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) - self.loadFiles() - } + func createNewFile(viewController: HomeViewController, fileName: String) { + let fileURL = viewController.documentsDirectory.appendingPathComponent(fileName) + fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + viewController.loadFiles() } - func renameFile(at fileURL: URL) { - showInputAlert(title: "Rename File", message: "Enter new file name", actionTitle: "Rename", initialText: fileURL.lastPathComponent) { newName in - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - self.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.handleError(error, withTitle: "Renaming File") - } + func renameFile(viewController: HomeViewController, fileURL: URL, newName: String) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + viewController.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + } + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.utilities.handleError(error, withTitle: "Renaming File") } } } } - func deleteFile(at fileURL: URL) { - activityIndicator.startAnimating() + func deleteFile(viewController: HomeViewController, fileURL: URL) { + viewController.activityIndicator.startAnimating() DispatchQueue.global().async { do { try self.fileManager.removeItem(at: fileURL) DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() } } catch { DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.handleError(error, withTitle: "Deleting File") + viewController.activityIndicator.stopAnimating() + viewController.utilities.handleError(error, withTitle: "Deleting File") } } } } - func unzipFile(at fileURL: URL) { + func unzipFile(viewController: HomeViewController, fileURL: URL) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") - activityIndicator.startAnimating() + viewController.activityIndicator.startAnimating() DispatchQueue.global().async { do { try self.fileManager.unzipItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.loadFiles() + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() } } catch { DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.handleError(error, withTitle: "Unzipping File") + viewController.activityIndicator.stopAnimating() + viewController.utilities.handleError(error, withTitle: "Unzipping File") } } } } - func shareFile(at fileURL: URL) { + func shareFile(viewController: UIViewController, fileURL: URL) { let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - present(activityController, animated: true, completion: nil) + viewController.present(activityController, animated: true, completion: nil) } } \ No newline at end of file From f9a8d7d033241973f5c6c1c787fc4ed0e862b64d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 09:58:11 -0400 Subject: [PATCH 172/391] Update Localizable.strings --- .../en.lproj/Localizable.strings | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index d851df34..a71fd365 100644 --- a/Shared/Localizations/en.lproj/Localizable.strings +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -1,3 +1,50 @@ +import UIKit + +class PlistEditorViewController: UIViewController { + private var fileURL: URL + private var textView: UITextView! + + init(fileURL: URL) { + self.fileURL = fileURL + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + loadFileContent() + } + + private func setupUI() { + textView = UITextView(frame: view.bounds) + view.addSubview(textView) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveChanges)) + } + + private func loadFileContent() { + if let data = try? Data(contentsOf: fileURL), + let plist = try? PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil), + let plistData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0), + let plistString = String(data: plistData, encoding: .utf8) { + textView.text = plistString + } + } + + @objc private func saveChanges() { + if let newText = textView.text, + let data = newText.data(using: .utf8), + let plist = try? PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil), + let plistData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0) { + try? plistData.write(to: fileURL) + } + navigationController?.popViewController(animated: true) + } +} + /* Localizable.strings feather From 18a1c9e58108bf716885a089987c416c2da27612 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 10:05:31 -0400 Subject: [PATCH 173/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 1110 ++++++++------------ 1 file changed, 437 insertions(+), 673 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 0eca73f5..e8692663 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -1,691 +1,455 @@ -// -// LibraryViewController.swift -// feather -// -// Created by samara on 8/12/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import Foundation import CoreData import UniformTypeIdentifiers class LibraryViewController: UITableViewController { - var signedApps: [SignedApps]? - var downloadedApps: [DownloadedApps]? - - var filteredSignedApps: [SignedApps] = [] - var filteredDownloadedApps: [DownloadedApps] = [] - - var installer: Installer? - - public var searchController: UISearchController! - var popupVC: PopupViewController! - var loaderAlert: UIAlertController? - - init() { super.init(style: .grouped) } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - override func viewDidLoad() { - super.viewDidLoad() - setupViews() - setupSearchController() - fetchSources() - loaderAlert = presentLoader() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setupNavigation() - } - - fileprivate func setupViews() { - self.tableView.dataSource = self - self.tableView.delegate = self - tableView.register(AppsTableViewCell.self, forCellReuseIdentifier: "RoundedBackgroundCell") - NotificationCenter.default.addObserver(self, selector: #selector(afetch), name: Notification.Name("lfetch"), object: nil) - NotificationCenter.default.addObserver( - self, - selector: #selector(handleInstallNotification(_:)), - name: Notification.Name("InstallDownloadedApp"), - object: nil - ) - } - - @objc private func handleInstallNotification(_ notification: Notification) { - guard let downloadedApp = notification.userInfo?["downloadedApp"] as? DownloadedApps else { return } - - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - signingDataWrapper.signingOptions.installAfterSigned = true - - let ap = SigningsViewController( - signingDataWrapper: signingDataWrapper, - application: downloadedApp, - appsViewController: self - ) - - ap.signingCompletionHandler = { success in - if success { - Debug.shared.log(message: "Signing completed successfully", type: .success) - } - } - - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - - present(navigationController, animated: true) - } - - deinit { - NotificationCenter.default.removeObserver(self, name: Notification.Name("lfetch"), object: nil) - NotificationCenter.default.removeObserver(self, name: Notification.Name("InstallDownloadedApp"), object: nil) - } - - fileprivate func setupNavigation() { - self.navigationController?.navigationBar.prefersLargeTitles = true - self.title = String.localized("TAB_LIBRARY") - } - - private func handleAppUpdate(for signedApp: SignedApps) { + var signedApps: [SignedApps]? + var downloadedApps: [DownloadedApps]? + + var filteredSignedApps: [SignedApps] = [] + var filteredDownloadedApps: [DownloadedApps] = [] + + var installer: Installer? + + public var searchController: UISearchController! + var popupVC: PopupViewController! + var loaderAlert: UIAlertController? + + init() { super.init(style: .grouped) } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func viewDidLoad() { + super.viewDidLoad() + setupViews() + setupSearchController() + fetchSources() + loaderAlert = presentLoader() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setupNavigation() + } + + fileprivate func setupViews() { + self.tableView.dataSource = self + self.tableView.delegate = self + tableView.register(AppsTableViewCell.self, forCellReuseIdentifier: "RoundedBackgroundCell") + NotificationCenter.default.addObserver(self, selector: #selector(afetch), name: Notification.Name("lfetch"), object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(handleInstallNotification(_:)), + name: Notification.Name("InstallDownloadedApp"), + object: nil + ) + } + + @objc private func handleInstallNotification(_ notification: Notification) { + guard let downloadedApp = notification.userInfo?["downloadedApp"] as? DownloadedApps else { return } + + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + signingDataWrapper.signingOptions.installAfterSigned = true + + let ap = SigningsViewController( + signingDataWrapper: signingDataWrapper, + application: downloadedApp, + appsViewController: self + ) + + ap.signingCompletionHandler = { success in + if success { + Debug.shared.log(message: "Signing completed successfully", type: .success) + } + } + + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + + present(navigationController, animated: true) + } + + deinit { + NotificationCenter.default.removeObserver(self, name: Notification.Name("lfetch"), object: nil) + NotificationCenter.default.removeObserver(self, name: Notification.Name("InstallDownloadedApp"), object: nil) + } + + fileprivate func setupNavigation() { + self.navigationController?.navigationBar.prefersLargeTitles = true + self.title = String.localized("TAB_LIBRARY") + } + + private func handleAppUpdate(for signedApp: SignedApps) { guard let sourceURL = signedApp.originalSourceURL else { - Debug.shared.log(message: "Missing update version or source URL", type: .error) - return - } - - Debug.shared.log(message: "Fetching update from source: \(sourceURL.absoluteString)", type: .info) - - present(loaderAlert!, animated: true) - - // Create mock source if in debug mode - if isDebugMode { - let mockSource = SourceRefreshOperation() - mockSource.createMockSource { mockSourceData in - if let sourceData = mockSourceData { - self.handleSourceData(sourceData, for: signedApp) - } else { - Debug.shared.log(message: "Failed to create mock source", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - } - } - } else { - // Normal source fetch - SourceGET().downloadURL(from: sourceURL) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success((let data, _)): - if case .success(let sourceData) = SourceGET().parse(data: data) { - self.handleSourceData(sourceData, for: signedApp) - } else { - Debug.shared.log(message: "Failed to parse source data", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - } - case .failure(let error): - Debug.shared.log(message: "Failed to fetch source: \(error)", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - } - } - } - } - - private func handleSourceData(_ sourceData: SourcesData, for signedApp: SignedApps) { - guard let bundleId = signedApp.bundleidentifier, - let updateVersion = signedApp.updateVersion, - let app = sourceData.apps.first(where: { $0.bundleIdentifier == bundleId }), - let versions = app.versions else { - Debug.shared.log(message: "Failed to find app in source", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - return - } - - // Look for the version that matches our update version - for version in versions { - if version.version == updateVersion { - // Found the matching version - Debug.shared.log(message: "Found matching version: \(version.version)", type: .info) - - let uuid = UUID().uuidString - - DispatchQueue.global(qos: .background).async { - do { - let tempDirectory = FileManager.default.temporaryDirectory - let destinationURL = tempDirectory.appendingPathComponent("\(uuid).ipa") - - // Download the file - if let data = try? Data(contentsOf: version.downloadURL) { - try data.write(to: destinationURL) - - let dl = AppDownload() - try handleIPAFile(destinationURL: destinationURL, uuid: uuid, dl: dl) - - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) { - // Force Sign & Install - let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - signingDataWrapper.signingOptions.installAfterSigned = true - - // Store the original signed app for deletion after update - let originalSignedApp = signedApp - - let ap = SigningsViewController( - signingDataWrapper: signingDataWrapper, - application: downloadedApp, - appsViewController: self - ) - - // Add completion handler to delete the original app after successful signing - ap.signingCompletionHandler = { [weak self] success in - if success { - CoreDataManager.shared.deleteAllSignedAppContent(for: originalSignedApp) - self?.fetchSources() - self?.tableView.reloadData() - } - } - - let navigationController = UINavigationController(rootViewController: ap) - - navigationController.shouldPresentFullScreen() - - self.present(navigationController, animated: true) - } - } - } - } - } catch { - Debug.shared.log(message: "Failed to handle update: \(error)", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - } - } - return - } - } - - Debug.shared.log(message: "Could not find version \(updateVersion) in source", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - } - - private var isDebugMode: Bool { - var isDebug = false - assert({ - isDebug = true - return true - }()) - return isDebug - } + Debug.shared.log(message: "Missing update version or source URL", type: .error) + return + } + + Debug.shared.log(message: "Fetching update from source: \(sourceURL.absoluteString)", type: .info) + + present(loaderAlert!, animated: true) + + // Create mock source if in debug mode + if isDebugMode { + let mockSource = SourceRefreshOperation() + mockSource.createMockSource { mockSourceData in + if let sourceData = mockSourceData { + self.handleSourceData(sourceData, for: signedApp) + } else { + Debug.shared.log(message: "Failed to create mock source", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + } + } + } else { + // Normal source fetch + SourceGET().downloadURL(from: sourceURL) { [weak self] result in + guard let self = self else { return } + + switch result { + case .success((let data, _)): + if case .success(let sourceData) = SourceGET().parse(data: data) { + self.handleSourceData(sourceData, for: signedApp) + } else { + Debug.shared.log(message: "Failed to parse source data", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + } + case .failure(let error): + Debug.shared.log(message: "Failed to fetch source: \(error)", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + } + } + } + } + + private func handleSourceData(_ sourceData: SourcesData, for signedApp: SignedApps) { + guard let bundleId = signedApp.bundleidentifier, + let updateVersion = signedApp.updateVersion, + let app = sourceData.apps.first(where: { $0.bundleIdentifier == bundleId }), + let versions = app.versions else { + Debug.shared.log(message: "Failed to find app in source", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + return + } + + // Look for the version that matches our update version + for version in versions { + if version.version == updateVersion { + // Found the matching version + Debug.shared.log(message: "Found matching version: \(version.version)", type: .info) + + let uuid = UUID().uuidString + + DispatchQueue.global(qos: .background).async { + do { + let tempDirectory = FileManager.default.temporaryDirectory + let destinationURL = tempDirectory.appendingPathComponent("\(uuid).ipa") + + // Download the file + if let data = try? Data(contentsOf: version.downloadURL) { + try data.write(to: destinationURL) + + let dl = AppDownload() + try handleIPAFile(destinationURL: destinationURL, uuid: uuid, dl: dl) + + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) { + // Force Sign & Install + let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + signingDataWrapper.signingOptions.installAfterSigned = true + + // Store the original signed app for deletion after update + let originalSignedApp = signedApp + + let ap = SigningsViewController( + signingDataWrapper: signingDataWrapper, + application: downloadedApp, + appsViewController: self + ) + + // Add completion handler to delete the original app after successful signing + ap.signingCompletionHandler = { [weak self] success in + if success { + CoreDataManager.shared.deleteAllSignedAppContent(for: originalSignedApp) + self?.fetchSources() + self?.tableView.reloadData() + } + } + + let navigationController = UINavigationController(rootViewController: ap) + + navigationController.shouldPresentFullScreen() + + self.present(navigationController, animated: true) + } + } + } + } + } catch { + Debug.shared.log(message: "Failed to handle update: \(error)", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + } + } + return + } + } + + Debug.shared.log(message: "Could not find version \(updateVersion) in source", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + } + + private var isDebugMode: Bool { + var isDebug = false + assert({ + isDebug = true + return true + }()) + return isDebug + } } extension LibraryViewController { - override func numberOfSections(in tableView: UITableView) -> Int { return 2 } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - switch section { - case 0: - return isFiltering ? filteredSignedApps.count : signedApps?.count ?? 0 - case 1: - return isFiltering ? filteredDownloadedApps.count : downloadedApps?.count ?? 0 - default: - return 0 - } - } - - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - switch section { - case 0: - let headerWithButton = GroupedSectionHeader( + override func numberOfSections(in tableView: UITableView) -> Int { return 2 } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch section { + case 0: + return isFiltering ? filteredSignedApps.count : signedApps?.count ?? 0 + case 1: + return isFiltering ? filteredDownloadedApps.count : downloadedApps?.count ?? 0 + default: + return 0 + } + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + switch section { + case 0: + let headerWithButton = GroupedSectionHeader( title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS"), - subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL", arguments: String(signedApps?.count ?? 0)), + subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL", arguments: String(signedApps?.count ?? 0)), buttonTitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_BUTTON_IMPORT"), buttonAction: { - self.startImporting() - }) - return headerWithButton - case 1: - - let headerWithButton = GroupedSectionHeader( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS"), - subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL", arguments: String(downloadedApps?.count ?? 0)) - ) - - return headerWithButton - default: - return nil - } - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = AppsTableViewCell(style: .subtitle, reuseIdentifier: "RoundedBackgroundCell") - cell.selectionStyle = .default - cell.accessoryType = .disclosureIndicator - cell.backgroundColor = .clear - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - - - if let iconURL = source!.value(forKey: "iconURL") as? String { - let imagePath = filePath!.appendingPathComponent(iconURL) - - if let image = CoreDataManager.shared.loadImage(from: imagePath) { - SectionIcons.sectionImage(to: cell, with: image) - } else { - SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) - } - } else { - SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) - } - - cell.configure(with: source!, filePath: filePath!) - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: true) - let filePath2 = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: false) - let appName = "\((source!.value(forKey: "name") as? String ?? ""))" - switch indexPath.section { - case 0: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet - - let hasUpdate = (source as? SignedApps)?.value(forKey: "hasUpdate") as? Bool ?? false - - if let signedApp = source as? SignedApps, - hasUpdate { - // Update available menu - let updateButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), - color: .tintColor.withAlphaComponent(0.9), - titleColor: .white - ) - updateButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - self.handleAppUpdate(for: signedApp) - } - } - - let clearButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE"), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - clearButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - CoreDataManager.shared.clearUpdateState(for: signedApp) - self.tableView.reloadRows(at: [indexPath], with: .none) - } - - popupVC.configureButtons([updateButton, clearButton]) - } else { - // Regular menu - let button1 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), - color: .tintColor.withAlphaComponent(0.9) - ) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - } - - let button4 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - button4.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - if let workspace = LSApplicationWorkspace.default() { - let success = workspace.openApplication(withBundleID: "\((source!.value(forKey: "bundleidentifier") as? String ?? ""))") - if !success { - Debug.shared.log(message: "Unable to open, do you have the app installed?", type: .warning) - } - } - } - - let button3 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - button3.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - if let cert = CoreDataManager.shared.getCurrentCertificate() { - self.present(self.loaderAlert!, animated: true) - - resignApp(certificate: cert, appPath: filePath2!) { success in - if success { - CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - Debug.shared.log(message: "Done action??") - self.tableView.reloadRows(at: [indexPath], with: .left) - } - } - } - } - } else { - let alert = UIAlertController( - title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), - message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) - self.present(alert, animated: true) - } - } - } - - let button2 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.shareFile(meow: source!, filePath: filePath?.path ?? "") - } - - popupVC.configureButtons([button1, button4, button3, button2]) - } - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: hasUpdate ? 150.0 : 270.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium() - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } - case 1: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet - - let singingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let button1 = PopupViewControllerButton( - title: singingData.signingOptions.installAfterSigned + self.startImporting() + }) + return headerWithButton + case 1: + + let headerWithButton = GroupedSectionHeader( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS"), + subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL", arguments: String(downloadedApps?.count ?? 0)) + ) + + return headerWithButton + default: + return nil + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = AppsTableViewCell(style: .subtitle, reuseIdentifier: "RoundedBackgroundCell") + cell.selectionStyle = .default + cell.accessoryType = .disclosureIndicator + cell.backgroundColor = .clear + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + + + if let iconURL = source!.value(forKey: "iconURL") as? String { + let imagePath = filePath!.appendingPathComponent(iconURL) + + if let image = CoreDataManager.shared.loadImage(from: imagePath) { + SectionIcons.sectionImage(to: cell, with: image) + } else { + SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) + } + } else { + SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) + } + + cell.configure(with: source!, filePath: filePath!) + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: true) + let filePath2 = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: false) + let appName = "\((source!.value(forKey: "name") as? String ?? ""))" + switch indexPath.section { + case 0: + if FileManager.default.fileExists(atPath: filePath2!.path) { + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet + + let hasUpdate = (source as? SignedApps)?.value(forKey: "hasUpdate") as? Bool ?? false + + if let signedApp = source as? SignedApps, + hasUpdate { + // Update available menu + let updateButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), + color: .tintColor.withAlphaComponent(0.9), + titleColor: .white + ) + updateButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + self.handleAppUpdate(for: signedApp) + } + } + + let clearButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE"), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + clearButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + CoreDataManager.shared.clearUpdateState(for: signedApp) + self.tableView.reloadRows(at: [indexPath], with: .none) + } + + popupVC.configureButtons([updateButton, clearButton]) + } else { + // Regular menu + let button1 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), + color: .tintColor.withAlphaComponent(0.9) + ) + button1.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + } + + let button4 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + button4.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + if let workspace = LSApplicationWorkspace.default() { + let success = workspace.openApplication(withBundleID: "\((source!.value(forKey: "bundleidentifier") as? String ?? ""))") + if !success { + Debug.shared.log(message: "Unable to open, do you have the app installed?", type: .warning) + } + } + } + + let button3 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + button3.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + if let cert = CoreDataManager.shared.getCurrentCertificate() { + self.present(self.loaderAlert!, animated: true) + + resignApp(certificate: cert, appPath: filePath2!) { success in + if success { + CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + Debug.shared.log(message: "Done action??") + self.tableView.reloadRows(at: [indexPath], with: .left) + } + } + } else { + // Handle resigning failure + self.loaderAlert?.dismiss(animated: true) + Debug.shared.log(message: "Failed to resign the app", type: .error) + } + } + } else { + let alert = UIAlertController( + title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), + message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) + self.present(alert, animated: true) + } + } + } + + let button2 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.shareFile(meow: source!, filePath: filePath?.path ?? "") + } + + popupVC.configureButtons([button1, button4, button3, button2]) + } + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: hasUpdate ? 150.0 : 270.0) + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium() + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } + case 1: + if FileManager.default.fileExists(atPath: filePath2!.path) { + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet + + let singingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let button1 = PopupViewControllerButton( + title: singingData.signingOptions.installAfterSigned ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: .tintColor.withAlphaComponent(0.9)) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startSigning(meow: source!) - } - - let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: .quaternarySystemFill, titleColor: .tintColor) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - let alertController = UIAlertController( + color: .tintColor.withAlphaComponent(0.9)) + button1.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startSigning(meow: source!) + } + + let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: .quaternarySystemFill, titleColor: .tintColor) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + let alertController = UIAlertController( title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), - preferredStyle: .alert - ) - + preferredStyle: .alert + ) + let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - - } - + self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + + } let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - self.present(alertController, animated: true, completion: nil) - } - } - - popupVC.configureButtons([button1, button2]) - - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium(), - - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } - default: - break - } - - tableView.deselectRow(at: indexPath, animated: true) - } - - @objc func startSigning(meow: NSManagedObject) { - if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for:(meow as! DownloadedApps)).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) - } - } - } - - override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - - let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break - } - completionHandler(true) - } - - deleteAction.backgroundColor = UIColor.red - let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) - configuration.performsFirstActionWithFullSwipe = true - - return configuration - } - - override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - - let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS"), image: UIImage(systemName: "info.circle"), handler: {_ in - - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] - } - } - - self.present(navigationController, animated: true) - - - }), - - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES"), image: UIImage(systemName: "folder"), handler: {_ in - - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") - - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") - } - } - }) - - ]) - }) - return configuration - } - - -} - -extension LibraryViewController { - @objc func afetch() { self.fetchSources() } - - func fetchSources() { - signedApps = CoreDataManager.shared.getDatedSignedApps() - downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - - DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() - } - } - } - - func getApplicationFilePath(with app: NSManagedObject, row: Int, section:Int, getuuidonly: Bool = false) -> URL? { - if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) - } - - if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) - } - return nil - } - - func getApplication(row: Int, section: Int) -> NSManagedObject? { - if isFiltering { - if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] - } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] - } - } - } else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] - } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] - } - } - } - return nil - } - -} - -extension LibraryViewController: UISearchResultsUpdating { - func updateSearchResults(for searchController: UISearchController) { - let searchText = searchController.searchBar.text ?? "" - filterContentForSearchText(searchText) - tableView.reloadData() - } - - private func filterContentForSearchText(_ searchText: String) { - let lowercasedSearchText = searchText.lowercased() - - filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] - - filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] - } -} - -extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { - func setupSearchController() { - searchController = UISearchController(searchResultsController: nil) - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = true - searchController.searchResultsUpdater = self - searchController.delegate = self - searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") - navigationItem.searchController = searchController - definesPresentationContext = true - navigationItem.hidesSearchBarWhenScrolling = false - } - - var isFiltering: Bool { - return searchController.isActive && !searchBarIsEmpty - } - - var searchBarIsEmpty: Bool { - return searchController.searchBar.text?.isEmpty ?? true - } -} - -/// https://stackoverflow.com/a/75310581 -func presentLoader() -> UIAlertController { - let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.isUserInteractionEnabled = false - activityIndicator.startAnimating() - - alert.view.addSubview(activityIndicator) - - NSLayoutConstraint.activate([ - alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant: 95), - activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) - ]) - - return alert -} - + + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true, completion: nil) + \ No newline at end of file From 33fd1d8828f118dbd51c99dd00bce5ec7643bec3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 10:06:31 -0400 Subject: [PATCH 174/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 232 ++++++++++++++++++++- 1 file changed, 231 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index e8692663..96a592a4 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -452,4 +452,234 @@ extension LibraryViewController { alertController.addAction(cancelAction) self.present(alertController, animated: true, completion: nil) - \ No newline at end of file + + let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) + + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true, completion: nil) + } + } + + popupVC.configureButtons([button1, button2]) + + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium(), + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } + default: + break + } + + tableView.deselectRow(at: indexPath, animated: true) + } + + @objc func startSigning(meow: NSManagedObject) { + if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for:(meow as! DownloadedApps)).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) + } + } + } + + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + + let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break + } + completionHandler(true) + } + + deleteAction.backgroundColor = UIColor.red + let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) + configuration.performsFirstActionWithFullSwipe = true + + return configuration + } + + override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + + let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS"), image: UIImage(systemName: "info.circle"), handler: {_ in + + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] + } + } + + self.present(navigationController, animated: true) + }), + + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES"), image: UIImage(systemName: "folder"), handler: {_ in + + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") + } + } + }) + ]) + }) + return configuration + } +} + +extension LibraryViewController { + @objc func afetch() { self.fetchSources() } + + func fetchSources() { + signedApps = CoreDataManager.shared.getDatedSignedApps() + downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + + DispatchQueue.main.async { + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() + } + } + } + + func getApplicationFilePath(with app: NSManagedObject, row: Int, section:Int, getuuidonly: Bool = false) -> URL? { + if section == 0 { + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) + } + + if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) + } + return nil + } + + func getApplication(row: Int, section: Int) -> NSManagedObject? { + if isFiltering { + if section == 0 { + if row < filteredSignedApps.count { + return filteredSignedApps[row] + } + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] + } + } + } else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] + } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] + } + } + } + return nil + } +} + +extension LibraryViewController: UISearchResultsUpdating { + func updateSearchResults(for searchController: UISearchController) { + let searchText = searchController.searchBar.text ?? "" + filterContentForSearchText(searchText) + tableView.reloadData() + } + + private func filterContentForSearchText(_ searchText: String) { + let lowercasedSearchText = searchText.lowercased() + + filteredSignedApps = signedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] + + filteredDownloadedApps = downloadedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] + } +} + +extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { + func setupSearchController() { + searchController = UISearchController(searchResultsController: nil) + searchController.obscuresBackgroundDuringPresentation = false + searchController.hidesNavigationBarDuringPresentation = true + searchController.searchResultsUpdater = self + searchController.delegate = self + searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") + navigationItem.searchController = searchController + definesPresentationContext = true + navigationItem.hidesSearchBarWhenScrolling = false + } + + var isFiltering: Bool { + return searchController.isActive && !searchBarIsEmpty + } + + var searchBarIsEmpty: Bool { + return searchController.searchBar.text?.isEmpty ?? true + } +} + +/// https://stackoverflow.com/a/75310581 +func presentLoader() -> UIAlertController { + let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.isUserInteractionEnabled = false + activityIndicator.startAnimating() + + alert.view.addSubview(activityIndicator) + + NSLayoutConstraint.activate([ + alert.view.heightAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant: 95), + activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) + ]) + + return alert +} \ No newline at end of file From cd2aee26c74c8bd1da65e8aaa0e49a0cc2ac38bb Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 10:14:57 -0400 Subject: [PATCH 175/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 445e2edc..9795b086 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -91,7 +91,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } catch { DispatchQueue.main.async { self?.activityIndicator.stopAnimating() - self?.utilities.handleError(error, withTitle: "Loading Files") + self?.utilities.handleError(on: self!, error: error, withTitle: "Loading Files") } } } @@ -126,10 +126,10 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe switch actionTitle { case "Select": self.selectFiles() case "Import": self.fileHandlers.importFile(viewController: self) - case "New Folder": self.showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in + case "New Folder": self.utilities.showInputAlert(on: self, title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) } - case "New File": self.showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in + case "New File": self.utilities.showInputAlert(on: self, title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in self.fileHandlers.createNewFile(viewController: self, fileName: fileName) } default: break @@ -169,7 +169,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe do { try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) } catch { - utilities.handleError(error, withTitle: "Creating Files Directory") + utilities.handleError(on: self, error: error, withTitle: "Creating Files Directory") } } From 46b31a3d9a0ba6f1f695ad76210cf0c814633ddc Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 10:17:37 -0400 Subject: [PATCH 176/391] Update SettingsAltIconView.swift --- .../SettingsAltIconView.swift | 214 +++++++++--------- 1 file changed, 103 insertions(+), 111 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SettingsAltIconView.swift b/iOS/Views/Signing/SigningViewController/SettingsAltIconView.swift index 0d458512..ab2784e8 100644 --- a/iOS/Views/Signing/SigningViewController/SettingsAltIconView.swift +++ b/iOS/Views/Signing/SigningViewController/SettingsAltIconView.swift @@ -1,120 +1,112 @@ -// -// SettingsAltIconView.swift -// feather -// -// Created by samara on 18.01.2025. -// - import SwiftUI struct SettingsAltIconView: View { - @Environment(\.dismiss) var dismiss - - private let mainOptions: SigningMainDataWrapper - private let applicationPath: URL - - init(mainOptions: SigningMainDataWrapper, app: URL) { - self.mainOptions = mainOptions - self.applicationPath = app - } - - var body: some View { - NavigationView { - ScrollView { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 100), spacing: 8)], spacing: 8) { - if let defaultIcon = loadDefaultIcon() { - IconButton( - iconPath: defaultIcon, - name: "Default", - applicationPath: applicationPath, - action: { - mainOptions.mainOptions.iconURL = nil - dismiss() - NotificationCenter.default.post(name: Notification.Name("reloadSigningController"), object: nil) - } - ) - } - - ForEach(loadAlternateIcons().sorted(by: { $0.key < $1.key }), id: \.key) { name, path in - IconButton( - iconPath: path, - name: name, - applicationPath: applicationPath, - action: { - mainOptions.mainOptions.iconURL = UIImage(contentsOfFile: applicationPath.appendingPathComponent(path).path) - dismiss() - NotificationCenter.default.post(name: Notification.Name("reloadSigningController"), object: nil) - } - ) - } - } - .padding() - } - .navigationTitle("Alt Icons") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - Button("Close") { dismiss() } - } - } - } + @Environment(\.dismiss) var dismiss + + private let mainOptions: SigningMainDataWrapper + private let applicationPath: URL + + init(mainOptions: SigningMainDataWrapper, app: URL) { + self.mainOptions = mainOptions + self.applicationPath = app + } + + var body: some View { + NavigationView { + ScrollView { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 100), spacing: 8)], spacing: 8) { + if let defaultIcon = loadDefaultIcon() { + IconButton( + iconPath: defaultIcon, + name: "Default", + applicationPath: applicationPath, + action: { + mainOptions.mainOptions.iconURL = nil + dismiss() + NotificationCenter.default.post(name: Notification.Name("reloadSigningController"), object: nil) + } + ) + } + + ForEach(loadAlternateIcons().sorted(by: { $0.key < $1.key }), id: \.key) { name, path in + IconButton( + iconPath: path, + name: name, + applicationPath: applicationPath, + action: { + mainOptions.mainOptions.iconURL = UIImage(contentsOfFile: applicationPath.appendingPathComponent(path).path) + dismiss() + NotificationCenter.default.post(name: Notification.Name("reloadSigningController"), object: nil) + } + ) + } + } + .padding() + } + .navigationTitle("Alt Icons") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + Button("Close") { dismiss() } + } + } + } } extension SettingsAltIconView { - // im not making this better, I may be reusing code but I dont carfe - private func loadDefaultIcon() -> String? { - guard let infoPlistPath = applicationPath.appendingPathComponent("Info.plist") as? URL, - let infoPlist = NSDictionary(contentsOf: infoPlistPath), - let iconDict = infoPlist["CFBundleIcons"] as? [String: Any], - let primaryIcon = iconDict["CFBundlePrimaryIcon"] as? [String: Any], - let files = primaryIcon["CFBundleIconFiles"] as? [String], - let iconPath = files.first else { - return nil - } - return iconPath - } - - private func loadAlternateIcons() -> [String: String] { - guard let infoPlistPath = applicationPath.appendingPathComponent("Info.plist") as? URL, - let infoPlist = NSDictionary(contentsOf: infoPlistPath), - let iconDict = infoPlist["CFBundleIcons"] as? [String: Any], - let alternateIcons = iconDict["CFBundleAlternateIcons"] as? [String: [String: Any]] else { - return [:] - } - - var icons: [String: String] = [:] - for (name, details) in alternateIcons { - if let files = details["CFBundleIconFiles"] as? [String], - let iconPath = files.first { - icons[name] = iconPath - } - } - return icons - } + private func loadDefaultIcon() -> String? { + guard let infoPlistPath = applicationPath.appendingPathComponent("Info.plist") as URL?, + let infoPlist = NSDictionary(contentsOf: infoPlistPath), + let iconDict = infoPlist["CFBundleIcons"] as? [String: Any], + let primaryIcon = iconDict["CFBundlePrimaryIcon"] as? [String: Any], + let files = primaryIcon["CFBundleIconFiles"] as? [String], + let iconPath = files.first else { + return nil + } + return iconPath + } + + private func loadAlternateIcons() -> [String: String] { + guard let infoPlistPath = applicationPath.appendingPathComponent("Info.plist") as URL?, + let infoPlist = NSDictionary(contentsOf: infoPlistPath), + let iconDict = infoPlist["CFBundleIcons"] as? [String: Any], + let alternateIcons = iconDict["CFBundleAlternateIcons"] as? [String: [String: Any]] else { + return [:] + } + + var icons: [String: String] = [:] + for (name, details) in alternateIcons { + if let files = details["CFBundleIconFiles"] as? [String], + let iconPath = files.first { + icons[name] = iconPath + } + } + return icons + } } private struct IconButton: View { - let iconPath: String - let name: String - let applicationPath: URL - let action: () -> Void - - var body: some View { - Button(action: action) { - VStack { - Image(uiImage: UIImage(contentsOfFile: applicationPath.appendingPathComponent(iconPath).path) ?? UIImage()) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 60, height: 60) - .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) - Text(name) - .font(.caption) - .fontWeight(.bold) - .lineLimit(2) - .multilineTextAlignment(.center) - .foregroundStyle(Color.primary) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .aspectRatio(1, contentMode: .fill) - } - } -} + let iconPath: String + let name: String + let applicationPath: URL + let action: () -> Void + + var body: some View { + Button(action: action) { + VStack { + Image(uiImage: UIImage(contentsOfFile: applicationPath.appendingPathComponent(iconPath).path) ?? UIImage()) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 60, height: 60) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) + Text(name) + .font(.caption) + .fontWeight(.bold) + .lineLimit(2) + .multilineTextAlignment(.center) + .foregroundStyle(Color.primary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .aspectRatio(1, contentMode: .fill) + } + } +} \ No newline at end of file From 8fd2d123d25ab43b98a041653e6f08e1488df044 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 10:19:05 -0400 Subject: [PATCH 177/391] Update SigningsDylibViewController.swift --- .../SigningsDylibViewController.swift | 200 +++++++++--------- 1 file changed, 97 insertions(+), 103 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift index 5ae961c8..909dce32 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift @@ -1,118 +1,112 @@ -// -// AppSigningDylibViewController.swift -// feather -// -// Created by samara on 29.08.2024. -// - import UIKit class SigningsDylibViewController: UITableViewController { - var applicationPath: URL - var groupedDylibs: [String: [String]] = [:] - var dylibSections: [String] = ["@rpath", "@executable_path", "/usr/lib", "/System/Library", "Other"] - var dylibstoremove: [String] = [] { - didSet { - self.mainOptions.mainOptions.removeInjectPaths = self.dylibstoremove - } - } - - var mainOptions: SigningMainDataWrapper - - init(mainOptions: SigningMainDataWrapper, app: URL) { - self.mainOptions = mainOptions - self.applicationPath = app - super.init(style: .insetGrouped) + var applicationPath: URL + var groupedDylibs: [String: [String]] = [:] + var dylibSections: [String] = ["@rpath", "@executable_path", "/usr/lib", "/System/Library", "Other"] + var dylibstoremove: [String] = [] { + didSet { + self.mainOptions.mainOptions.removeInjectPaths = self.dylibstoremove + } + } + + var mainOptions: SigningMainDataWrapper - do { - let balls = try TweakHandler.findExecutable(at: applicationPath) - if let dylibs = listDylibs(filePath: balls!.path) { - groupDylibs(dylibs) - } - } catch { - print(error) - } - } + init(mainOptions: SigningMainDataWrapper, app: URL) { + self.mainOptions = mainOptions + self.applicationPath = app + super.init(style: .insetGrouped) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + do { + if let balls = try TweakHandler.findExecutable(at: applicationPath) { + if let dylibs = listDylibs(filePath: balls.path) { + groupDylibs(dylibs) + } else { + print("Failed to list dylibs") + } + } else { + print("Failed to find executable") + } + } catch { + print("Error finding executable: \(error)") + } + } - override func viewDidLoad() { - super.viewDidLoad() - setupViews() - setupNavigation() - self.dylibstoremove = self.mainOptions.mainOptions.removeInjectPaths - - } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - fileprivate func setupViews() { - self.tableView.dataSource = self - self.tableView.delegate = self - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "dylibCell") - - let alertController = UIAlertController(title: "ADVANCED USERS ONLY", message: "This section can make installed applications UNUSABLE and potentially UNSTABLE. USE THIS SECTION WITH CAUTION, IF YOU HAVE NO IDEA WHAT YOU'RE DOING, PLEASE LEAVE.\n\nIF YOU MAKE AN ISSUE ON THIS, IT WILL IMMEDIATELY BE CLOSED AND IGNORED.", preferredStyle: .alert) - - let continueAction = UIAlertAction(title: "WHO CARES", style: .destructive, handler: nil) - - alertController.addAction(continueAction) - - present(alertController, animated: true, completion: nil) - - } + override func viewDidLoad() { + super.viewDidLoad() + setupViews() + setupNavigation() + self.dylibstoremove = self.mainOptions.mainOptions.removeInjectPaths + } - fileprivate func setupNavigation() { - title = "Remove Dylibs" - - } + fileprivate func setupViews() { + self.tableView.dataSource = self + self.tableView.delegate = self + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "dylibCell") + + let alertController = UIAlertController(title: "ADVANCED USERS ONLY", message: "This section can make installed applications UNUSABLE and potentially UNSTABLE. USE THIS SECTION WITH CAUTION, IF YOU PROCEED.", preferredStyle: .alert) + + let continueAction = UIAlertAction(title: "WHO CARES", style: .destructive, handler: nil) + + alertController.addAction(continueAction) + + present(alertController, animated: true, completion: nil) + } - fileprivate func groupDylibs(_ dylibs: [String]) { - groupedDylibs["@rpath"] = dylibs.filter { $0.hasPrefix("@rpath") } - groupedDylibs["@executable_path"] = dylibs.filter { $0.hasPrefix("@executable_path") } - groupedDylibs["/usr/lib"] = dylibs.filter { $0.hasPrefix("/usr/lib") } - groupedDylibs["/System/Library"] = dylibs.filter { $0.hasPrefix("/System/Library") } - groupedDylibs["Other"] = dylibs.filter { dylib in - !dylib.hasPrefix("@rpath") && - !dylib.hasPrefix("@executable_path") && - !dylib.hasPrefix("/usr/lib") && - !dylib.hasPrefix("/System/Library") - } - } + fileprivate func setupNavigation() { + title = "Remove Dylibs" + } - override func numberOfSections(in tableView: UITableView) -> Int { - return dylibSections.count - } + fileprivate func groupDylibs(_ dylibs: [String]) { + groupedDylibs["@rpath"] = dylibs.filter { $0.hasPrefix("@rpath") } + groupedDylibs["@executable_path"] = dylibs.filter { $0.hasPrefix("@executable_path") } + groupedDylibs["/usr/lib"] = dylibs.filter { $0.hasPrefix("/usr/lib") } + groupedDylibs["/System/Library"] = dylibs.filter { $0.hasPrefix("/System/Library") } + groupedDylibs["Other"] = dylibs.filter { dylib in + !dylib.hasPrefix("@rpath") && + !dylib.hasPrefix("@executable_path") && + !dylib.hasPrefix("/usr/lib") && + !dylib.hasPrefix("/System/Library") + } + } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let key = dylibSections[section] - return groupedDylibs[key]?.count ?? 0 - } + override func numberOfSections(in tableView: UITableView) -> Int { + return dylibSections.count + } - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return dylibSections[section] - } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + let key = dylibSections[section] + return groupedDylibs[key]?.count ?? 0 + } - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "dylibCell", for: indexPath) - let key = dylibSections[indexPath.section] - if let dylib = groupedDylibs[key]?[indexPath.row] { - cell.textLabel?.text = dylib - cell.textLabel?.textColor = dylibstoremove.contains(dylib) ? .systemRed : .label - } - return cell - } + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return dylibSections[section] + } - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == .delete { - let key = dylibSections[indexPath.section] - if let dylib = groupedDylibs[key]?[indexPath.row] { - if !dylibstoremove.contains(dylib) { - dylibstoremove.append(dylib) - } - tableView.reloadRows(at: [indexPath], with: .automatic) - print(dylibstoremove) - } - } - } + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "dylibCell", for: indexPath) + let key = dylibSections[indexPath.section] + if let dylib = groupedDylibs[key]?[indexPath.row] { + cell.textLabel?.text = dylib + cell.textLabel?.textColor = dylibstoremove.contains(dylib) ? .systemRed : .label + } + return cell + } -} + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + let key = dylibSections[indexPath.section] + if let dylib = groupedDylibs[key]?[indexPath.row] { + if !dylibstoremove.contains(dylib) { + dylibstoremove.append(dylib) + } + tableView.reloadRows(at: [indexPath], with: .automatic) + print(dylibstoremove) + } + } + } +} \ No newline at end of file From 84f954ef24892ebc6adf8d659383b89a184ff4c2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 10:20:44 -0400 Subject: [PATCH 178/391] Update AppSigner.swift --- Shared/Magic/AppSigner.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Shared/Magic/AppSigner.swift b/Shared/Magic/AppSigner.swift index 122f7379..7202a5cc 100644 --- a/Shared/Magic/AppSigner.swift +++ b/Shared/Magic/AppSigner.swift @@ -20,7 +20,7 @@ func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, try fileManager.createDirectory(at: tmpDir, withIntermediateDirectories: true) try fileManager.copyItem(at: appPath, to: tmpDirApp) - if let info = NSDictionary(contentsOf: tmpDirApp.appendingPathComponent("Info.plist"))!.mutableCopy() as? NSMutableDictionary { + if let info = NSDictionary(contentsOf: tmpDirApp.appendingPathComponent("Info.plist"))?.mutableCopy() as? NSMutableDictionary { try updateInfoPlist(infoDict: info, main: mainOptions, options: signingOptions, icon: mainOptions.mainOptions.iconURL, app: tmpDirApp) if let iconsDict = info["CFBundleIcons"] as? [String: Any], @@ -29,6 +29,8 @@ func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, let iconFileName = iconFiles.first { iconURL = iconFileName } + } else { + throw NSError(domain: "AppSigner", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to read Info.plist"]) } let handler = TweakHandler(urls: signingOptions.signingOptions.toInject, app: tmpDirApp) From 8158600692b04382e30d20fa8d5374123dc50b06 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 10:22:19 -0400 Subject: [PATCH 179/391] Update TweakHandler.swift --- Shared/Magic/TweakHandler.swift | 552 +++++++++++++++----------------- 1 file changed, 250 insertions(+), 302 deletions(-) diff --git a/Shared/Magic/TweakHandler.swift b/Shared/Magic/TweakHandler.swift index a18b1ea7..81066aab 100644 --- a/Shared/Magic/TweakHandler.swift +++ b/Shared/Magic/TweakHandler.swift @@ -1,324 +1,272 @@ -// -// DylibHandler.swift -// feather -// -// Created by samara on 8/17/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import Foundation import SWCompression enum FileProcessingError: Error { - case unsupportedFileExtension(String) - case decompressionFailed(String) - case missingFile(String) + case unsupportedFileExtension(String) + case decompressionFailed(String) + case missingFile(String) } class TweakHandler { - - let fileManager = FileManager.default - - private var urls: [String] - private let app: URL - private var urlsToInject: [URL] = [] - private var directoriesToCheck: [URL] = [] - - init(urls: [String], app: URL) { - self.urls = urls - self.app = app - } - - public func getInputFiles() throws { - guard !urls.isEmpty else { - Debug.shared.log(message: "No dylibs to inject, skipping!") - return - } - - let frameworksPath = app.appendingPathComponent("Frameworks").appendingPathComponent("CydiaSubstrate.framework") - if !fileManager.fileExists(atPath: frameworksPath.path) { - if let ellekitURL = Bundle.main.url(forResource: "ellekit", withExtension: "deb") { - self.urls.insert(ellekitURL.absoluteString, at: 0) - } else { - Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle ⁉️", type: .error) - return - } - } + + let fileManager = FileManager.default + + private var urls: [String] + private let app: URL + private var urlsToInject: [URL] = [] + private var directoriesToCheck: [URL] = [] + + init(urls: [String], app: URL) { + self.urls = urls + self.app = app + } + + public func getInputFiles() throws { + guard !urls.isEmpty else { + Debug.shared.log(message: "No dylibs to inject, skipping!") + return + } + + let frameworksPath = app.appendingPathComponent("Frameworks").appendingPathComponent("CydiaSubstrate.framework") + if !fileManager.fileExists(atPath: frameworksPath.path) { + if let ellekitURL = Bundle.main.url(forResource: "ellekit", withExtension: "deb") { + self.urls.insert(ellekitURL.absoluteString, at: 0) + } else { + Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle ⁉️", type: .error) + return + } + } - let baseTmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) - - do { - try TweakHandler.createDirectoryIfNeeded(at: app.appendingPathComponent("Frameworks")) - try TweakHandler.createDirectoryIfNeeded(at: baseTmpDir) - - // check for appropriate files, if theres debs - // it will extract then add a url, if theres no url, i.e. - // you haven't added a deb, it will skip - for url in urls { - let urlf = URL(string: url) - switch urlf!.pathExtension.lowercased() { - case "dylib": - try handleDylib(at: urlf!) - case "deb": - try handleDeb(at: urlf!, baseTmpDir: baseTmpDir) - default: - Debug.shared.log(message: "Unsupported file type: \(urlf!.lastPathComponent), skipping.") - } - } - - // check contents of data.tar's extracted from debs - if !directoriesToCheck.isEmpty { - try handleDirectories(at: directoriesToCheck) - if !urlsToInject.isEmpty { - try handleExtractedDirectoryContents(at: urlsToInject) - } - } - - } catch { - throw error - } - } - - // finally, handle extracted contents - private func handleExtractedDirectoryContents(at urls: [URL]) throws { - for url in urls { - switch url.pathExtension.lowercased() { - case "dylib": - try handleDylib(at: url) - case "framework": - let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - try handleDylib(framework: destinationURL) - case "bundle": - let destinationURL = app.appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - default: - Debug.shared.log(message: "Unsupported file type: \(url.lastPathComponent), skipping.") - } - } - } - - // Inject imported dylib file - private func handleDylib(at url: URL) throws { - do { - let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - - // change paths because some tweaks hardlink, which is not ideal. - // this is not a good solution, at most this would work for basic tweaks - // we recommend you use newer theos to compile, and make sure it works - // using the ellekit framework - _ = changeDylib( - filePath: destinationURL.path, - oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", - newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" - ) - - // inject if there's a valid app main executable - if let exe = try TweakHandler.findExecutable(at: app) { - _ = injectDylib( - filePath: exe.path, - dylibPath: "@executable_path/Frameworks/\(destinationURL.lastPathComponent)", - weakInject: true - ) - } - } catch { - throw error - } - } - - // Inject imported framework dir - private func handleDylib(framework: URL) throws { - do { - if let fexe = try TweakHandler.findExecutable(at: framework) { - - // change paths because some tweaks hardlink, which is not ideal. - // this is not a good solution, at most this would work for basic tweaks - // we recommend you use newer theos to compile, and make sure it works - // using the ellekit framework - _ = changeDylib( - filePath: fexe.path, - oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", - newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" - ) - - // inject if there's a valid app main executable - if let appexe = try TweakHandler.findExecutable(at: app) { - _ = injectDylib( - filePath: appexe.path, - dylibPath: "@executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent)", - weakInject: true - ) - } - } - - - } catch { - throw error - } - } - - // Extracy imported deb file - private func handleDeb(at url: URL, baseTmpDir: URL) throws { - let uniqueSubDir = baseTmpDir.appendingPathComponent(UUID().uuidString) - try TweakHandler.createDirectoryIfNeeded(at: uniqueSubDir) - - // I don't particularly like this code - // but it somehow works well enough, - // do note large lzma's are slow as hell - do { - let arFiles = try extractAR(try Data(contentsOf: url)) - - for arFile in arFiles { - let outputPath = uniqueSubDir.appendingPathComponent(arFile.name) - try arFile.content.write(to: outputPath) - - if ["data.tar.lzma", "data.tar.gz", "data.tar.xz", "data.tar.bz2"].contains(arFile.name) { - var fileToProcess = outputPath - try processFile(at: &fileToProcess) - try processFile(at: &fileToProcess) - directoriesToCheck.append(fileToProcess) - } - } - } catch { - Debug.shared.log(message: "Error handling file \(url): \(error)") - throw error - } - } - - // Read extracted deb file, locate all neccessary contents to copy over to the .app - private func handleDirectories(at urls: [URL]) throws { - let directoriesToCheck = [ - "Library/Frameworks/", "var/jb/Library/Frameworks/", - "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/", - "Library/Application Support/", "var/jb/Library/Application Support/" - ] - - let fileManager = FileManager.default - - for baseURL in urls { - for directory in directoriesToCheck { - let directoryURL = baseURL.appendingPathComponent(directory) - - guard fileManager.fileExists(atPath: directoryURL.path) else { - Debug.shared.log(message: "Directory does not exist: \(directoryURL.path). Skipping.") - continue - } - - switch directory { - case "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/": - let dylibFiles = try locateDylibFiles(in: directoryURL) - for fileURL in dylibFiles { - urlsToInject.append(fileURL) - } - - case "Library/Frameworks/", "var/jb/Library/Frameworks/": - let frameworkDirectories = try locateFrameworkDirectories(in: directoryURL) - for frameworkURL in frameworkDirectories { - urlsToInject.append(frameworkURL) - } - - case "Library/Application Support/", "var/jb/Library/Application Support/": - try searchForBundles(in: directoryURL) - - default: - Debug.shared.log(message: "Unexpected directory path: \(directoryURL.path)") - } - } - } - } + let baseTmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) + + do { + try TweakHandler.createDirectoryIfNeeded(at: app.appendingPathComponent("Frameworks")) + try TweakHandler.createDirectoryIfNeeded(at: baseTmpDir) + + for url in urls { + guard let urlf = URL(string: url) else { + Debug.shared.log(message: "Invalid URL: \(url), skipping.") + continue + } + switch urlf.pathExtension.lowercased() { + case "dylib": + try handleDylib(at: urlf) + case "deb": + try handleDeb(at: urlf, baseTmpDir: baseTmpDir) + default: + Debug.shared.log(message: "Unsupported file type: \(urlf.lastPathComponent), skipping.") + } + } + + if !directoriesToCheck.isEmpty { + try handleDirectories(at: directoriesToCheck) + if !urlsToInject.isEmpty { + try handleExtractedDirectoryContents(at: urlsToInject) + } + } + + } catch { + throw error + } + } + + private func handleExtractedDirectoryContents(at urls: [URL]) throws { + for url in urls { + switch url.pathExtension.lowercased() { + case "dylib": + try handleDylib(at: url) + case "framework": + let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + try handleDylib(framework: destinationURL) + case "bundle": + let destinationURL = app.appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + default: + Debug.shared.log(message: "Unsupported file type: \(url.lastPathComponent), skipping.") + } + } + } + + private func handleDylib(at url: URL) throws { + do { + let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + + _ = changeDylib( + filePath: destinationURL.path, + oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", + newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" + ) + + if let exe = try TweakHandler.findExecutable(at: app) { + _ = injectDylib( + filePath: exe.path, + dylibPath: "@executable_path/Frameworks/\(destinationURL.lastPathComponent)", + weakInject: true + ) + } + } catch { + throw error + } + } + + private func handleDylib(framework: URL) throws { + do { + if let fexe = try TweakHandler.findExecutable(at: framework) { + _ = changeDylib( + filePath: fexe.path, + oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", + newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" + ) + + if let appexe = try TweakHandler.findExecutable(at: app) { + _ = injectDylib( + filePath: appexe.path, + dylibPath: "@executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent)", + weakInject: true + ) + } + } + } catch { + throw error + } + } + + private func handleDeb(at url: URL, baseTmpDir: URL) throws { + let uniqueSubDir = baseTmpDir.appendingPathComponent(UUID().uuidString) + try TweakHandler.createDirectoryIfNeeded(at: uniqueSubDir) + + do { + let arFiles = try extractAR(try Data(contentsOf: url)) + + for arFile in arFiles { + let outputPath = uniqueSubDir.appendingPathComponent(arFile.name) + try arFile.content.write(to: outputPath) + + if ["data.tar.lzma", "data.tar.gz", "data.tar.xz", "data.tar.bz2"].contains(arFile.name) { + var fileToProcess = outputPath + try processFile(at: &fileToProcess) + try processFile(at: &fileToProcess) + directoriesToCheck.append(fileToProcess) + } + } + } catch { + Debug.shared.log(message: "Error handling file \(url): \(error)") + throw error + } + } + + private func handleDirectories(at urls: [URL]) throws { + let directoriesToCheck = [ + "Library/Frameworks/", "var/jb/Library/Frameworks/", + "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/", + "Library/Application Support/", "var/jb/Library/Application Support/" + ] + + for baseURL in urls { + for directory in directoriesToCheck { + let directoryURL = baseURL.appendingPathComponent(directory) + + guard fileManager.fileExists(atPath: directoryURL.path) else { + Debug.shared.log(message: "Directory does not exist: \(directoryURL.path). Skipping.") + continue + } + + switch directory { + case "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/": + let dylibFiles = try locateDylibFiles(in: directoryURL) + urlsToInject.append(contentsOf: dylibFiles) + + case "Library/Frameworks/", "var/jb/Library/Frameworks/": + let frameworkDirectories = try locateFrameworkDirectories(in: directoryURL) + urlsToInject.append(contentsOf: frameworkDirectories) + + case "Library/Application Support/", "var/jb/Library/Application Support/": + try searchForBundles(in: directoryURL) + + default: + Debug.shared.log(message: "Unexpected directory path: \(directoryURL.path)") + } + } + } + } } - - - // MARK: - Find correct files in debs extension TweakHandler { - private func searchForBundles(in directory: URL) throws { - let fileManager = FileManager.default - let allFiles = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) + private func searchForBundles(in directory: URL) throws { + let allFiles = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) - let bundleDirectories = allFiles.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "bundle" && url.hasDirectoryPath && !isSymlink - } - - for bundleURL in bundleDirectories { - urlsToInject.append(bundleURL) - } - - let directoriesToSearch = allFiles.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.hasDirectoryPath && !bundleDirectories.contains(url) && !isSymlink - } - - for dirURL in directoriesToSearch { - try searchForBundles(in: dirURL) - } - } + let bundleDirectories = allFiles.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "bundle" && url.hasDirectoryPath && !isSymlink + } + + urlsToInject.append(contentsOf: bundleDirectories) + + let directoriesToSearch = allFiles.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.hasDirectoryPath && !bundleDirectories.contains(url) && !isSymlink + } + + for dirURL in directoriesToSearch { + try searchForBundles(in: dirURL) + } + } - private func locateDylibFiles(in directory: URL) throws -> [URL] { - let fileManager = FileManager.default - let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) + private func locateDylibFiles(in directory: URL) throws -> [URL] { + let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) - let dylibFiles = files.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "dylib" && !isSymlink - } - - return dylibFiles - } + return files.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "dylib" && !isSymlink + } + } - private func locateFrameworkDirectories(in directory: URL) throws -> [URL] { - let fileManager = FileManager.default - let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) + private func locateFrameworkDirectories(in directory: URL) throws -> [URL] { + let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) - let frameworkDirectories = files.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "framework" && url.hasDirectoryPath && !isSymlink - } - - return frameworkDirectories - } + return files.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "framework" && url.hasDirectoryPath && !isSymlink + } + } } - - // MARK: - File management extension TweakHandler { - private static func createDirectoryIfNeeded(at url: URL) throws { - let fileManager = FileManager.default - if !fileManager.fileExists(atPath: url.path) { - try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) - } - } - - public static func findExecutable(at frameworkURL: URL) throws -> URL? { - - let infoPlistURL = frameworkURL.appendingPathComponent("Info.plist") - - let plistData = try Data(contentsOf: infoPlistURL) - if let plist = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any], - let executableName = plist["CFBundleExecutable"] as? String { - let executableURL = frameworkURL.appendingPathComponent(executableName) - return executableURL - } else { - Debug.shared.log(message: "CFBundleExecutable not found in Info.plist") - return nil - } - } - - private static func moveFile(from sourceURL: URL, to destinationURL: URL) throws { - let fileManager = FileManager.default - if fileManager.fileExists(atPath: destinationURL.path) { - Debug.shared.log(message: "File already exists at destination: \(destinationURL)") - } else { - try fileManager.moveItem(at: sourceURL, to: destinationURL) - } - } -} + private static func createDirectoryIfNeeded(at url: URL) throws { + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: url.path) { + try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + } + } + + public static func findExecutable(at frameworkURL: URL) throws -> URL? { + let infoPlistURL = frameworkURL.appendingPathComponent("Info.plist") + + let plistData = try Data(contentsOf: infoPlistURL) + if let plist = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any], + let executableName = plist["CFBundleExecutable"] as? String { + let executableURL = frameworkURL.appendingPathComponent(executableName) + return executableURL + } else { + Debug.shared.log(message: "CFBundleExecutable not found in Info.plist") + return nil + } + } + private static func moveFile(from sourceURL: URL, to destinationURL: URL) throws { + let fileManager = FileManager.default + if fileManager.fileExists(atPath: destinationURL.path) { + Debug.shared.log(message: "File already exists at destination: \(destinationURL)") + } else { + try fileManager.moveItem(at: sourceURL, to: destinationURL) + } + } +} \ No newline at end of file From 420934fd3cd80128a551384eaded494fe23684a5 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 10:36:19 -0400 Subject: [PATCH 180/391] Update Localizable.strings --- .../en.lproj/Localizable.strings | 86 +------------------ 1 file changed, 1 insertion(+), 85 deletions(-) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index a71fd365..f622e41b 100644 --- a/Shared/Localizations/en.lproj/Localizable.strings +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -1,50 +1,3 @@ -import UIKit - -class PlistEditorViewController: UIViewController { - private var fileURL: URL - private var textView: UITextView! - - init(fileURL: URL) { - self.fileURL = fileURL - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - setupUI() - loadFileContent() - } - - private func setupUI() { - textView = UITextView(frame: view.bounds) - view.addSubview(textView) - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveChanges)) - } - - private func loadFileContent() { - if let data = try? Data(contentsOf: fileURL), - let plist = try? PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil), - let plistData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0), - let plistString = String(data: plistData, encoding: .utf8) { - textView.text = plistString - } - } - - @objc private func saveChanges() { - if let newText = textView.text, - let data = newText.data(using: .utf8), - let plist = try? PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil), - let plistData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0) { - try? plistData.write(to: fileURL) - } - navigationController?.popViewController(animated: true) - } -} - /* Localizable.strings feather @@ -302,41 +255,4 @@ class PlistEditorViewController: UIViewController { "DISPLAY_VIEW_CONTROLLER_CELL_BIG_DESCRIPTION" = "Big Description"; "DISPLAY_VIEW_CONTROLLER_CELL_BIG_DESCRIPTION_DESCRIPTION" = "Replaces screenshots with the app description."; "DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME" = "Use Team Name"; -"DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME_DESCRIPTION" = "Replaces the certificate name with your team name, could be better to distinguish between certificates if providers happen to always use the same App ID name."; - -// MARK: - SettingsViewController -> LanguageViewController -"LANGUAGE_VIEW_TITLE" = "Language"; -"LANGUAGE_VIEW_CELL_USE_SYSTEM_LANGUAGE" = "Use System Language"; - -// MARK: - SettingsViewController -> CertificatesViewController -> CertImportingViewController -"CERT_IMPORTING_VIEWCONTROLLER_TITLE" = "Import"; -"CERT_IMPORTING_VIEWCONTROLLER_SECTION_PROVISIONING" = ""; -"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_TITLE" = "Bad Password"; -"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_DESCRIPTION" = "Please check the password and try again."; -"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PROV" = "Import Provisioning File"; -"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_CERT" = "Import Certificate File"; -"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_ENTER_PW" = "Enter Password"; -"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PW" = "Password"; -"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PROV" = "Import a provisioning file to be able to sideload to your device."; -"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_CERT" = "Import a file containing a valid certificate."; -"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PASS" = "Enter the password associated with the private key, leave it blank if there's no password required."; - -// MARK: - SettingsViewController -> ResetViewController -"RESET_VIEW_CONTROLLER_CLEAR_CACHE" = "Clear Network Cache"; -"RESET_VIEW_CONTROLLER_CLEAR_CACHE_ALERT_TITLE" = "This action is irreversible. Cached network requests and images will be cleared."; - -// MARK: - Donation -"DONATION_TITLE" = "Donate"; -"DONATION_DONATIONS" = "Donations"; -"DONATION_CELL_1_TITLE" = "Secret Repo"; -"DONATION_CELL_1_DESCRIPTION" = "Get access to our secret repo by donating, will provide you with beta access to Feather."; -"DONATION_CELL_2_TITLE" = "Show Your Support"; -"DONATION_CELL_2_DESCRIPTION" = "Show your support by donating! If you're unable to donate, spreading the word about Feather works too!"; - -// MARK: - SettingsViewController -> LogsViewController.swift -"LOGS_VIEW_SECTION_TITLE_ERROR" = "%@ Critical Errors"; -"LOGS_VIEW_SECTION_TITLE_SHARE" = "Share Logs"; -"LOGS_VIEW_SECTION_TITLE_COPY" = "Copy Logs"; -"LOGS_VIEW_SUCCESS_DESCRIPTION" = "Log contents have been copied to clipboard."; -"LOGS_VIEW_FAIL_DESCRIPTION" = "Failed to copy log contents."; -"LOGS_VIEW_TITLE" = "Backdoor Logs"; \ No newline at end of file +"DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME_DESCRIPTION" = "Replaces the certificate name with your team name, could be better to distinguish between certificates if providers happen to always use the same \ No newline at end of file From c2be9ec411d00948f2e93dc166c000bd5d0cf88d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 10:38:17 -0400 Subject: [PATCH 181/391] Update Localizable.strings --- .../en.lproj/Localizable.strings | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index f622e41b..15b3aec9 100644 --- a/Shared/Localizations/en.lproj/Localizable.strings +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -255,4 +255,41 @@ "DISPLAY_VIEW_CONTROLLER_CELL_BIG_DESCRIPTION" = "Big Description"; "DISPLAY_VIEW_CONTROLLER_CELL_BIG_DESCRIPTION_DESCRIPTION" = "Replaces screenshots with the app description."; "DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME" = "Use Team Name"; -"DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME_DESCRIPTION" = "Replaces the certificate name with your team name, could be better to distinguish between certificates if providers happen to always use the same \ No newline at end of file +""DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME_DESCRIPTION" = "Replaces the certificate name with your team name, could be better to distinguish between certificates if providers happen to always use the same name."; + +// MARK: - SettingsViewController -> LanguageViewController +"LANGUAGE_VIEW_TITLE" = "Language"; +"LANGUAGE_VIEW_CELL_USE_SYSTEM_LANGUAGE" = "Use System Language"; + +// MARK: - SettingsViewController -> CertificatesViewController -> CertImportingViewController +"CERT_IMPORTING_VIEWCONTROLLER_TITLE" = "Import"; +"CERT_IMPORTING_VIEWCONTROLLER_SECTION_PROVISIONING" = ""; +"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_TITLE" = "Bad Password"; +"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_DESCRIPTION" = "Please check the password and try again."; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PROV" = "Import Provisioning File"; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_CERT" = "Import Certificate File"; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_ENTER_PW" = "Enter Password"; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PW" = "Password"; +"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PROV" = "Import a provisioning file to be able to sideload to your device."; +"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_CERT" = "Import a file containing a valid certificate."; +"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PASS" = "Enter the password associated with the private key, leave it blank if there's no password required."; + +// MARK: - SettingsViewController -> ResetViewController +"RESET_VIEW_CONTROLLER_CLEAR_CACHE" = "Clear Network Cache"; +"RESET_VIEW_CONTROLLER_CLEAR_CACHE_ALERT_TITLE" = "This action is irreversible. Cached network requests and images will be cleared."; + +// MARK: - Donation +"DONATION_TITLE" = "Donate"; +"DONATION_DONATIONS" = "Donations"; +"DONATION_CELL_1_TITLE" = "Secret Repo"; +"DONATION_CELL_1_DESCRIPTION" = "Get access to our secret repo by donating, will provide you with beta access to Feather."; +"DONATION_CELL_2_TITLE" = "Show Your Support"; +"DONATION_CELL_2_DESCRIPTION" = "Show your support by donating! If you're unable to donate, spreading the word about Feather works too!"; + +// MARK: - SettingsViewController -> LogsViewController.swift +"LOGS_VIEW_SECTION_TITLE_ERROR" = "%@ Critical Errors"; +"LOGS_VIEW_SECTION_TITLE_SHARE" = "Share Logs"; +"LOGS_VIEW_SECTION_TITLE_COPY" = "Copy Logs"; +"LOGS_VIEW_SUCCESS_DESCRIPTION" = "Log contents have been copied to clipboard."; +"LOGS_VIEW_FAIL_DESCRIPTION" = "Failed to copy log contents."; +"LOGS_VIEW_TITLE" = "Backdoor Logs"; \ No newline at end of file From b36f704a77026ebd3ea4b87a7e69b02470a734e0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:03:25 -0400 Subject: [PATCH 182/391] Add files via upload --- .../en.lproj/Localizable.strings | Bin 19508 -> 19744 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings index 15b3aec90e649ccc133075d72dec4063b510f12e..56fc6b63b186bd44cf752fda4113d7438cd15fa8 100644 GIT binary patch literal 19744 zcmb_j31A#&b^iY(auO%8$MP*Z_Q;1MThiK=yyCZ4h)$V3z zSBk>bln{ske( z*xH?W|NGzn&hNb$kLNR{RXlw7JIYTPXIHQ**;TBLtzlQQYuL4HExV3g&u(Dr*m|b0 z4XmDRWSiJ#wuNnF+t_y2z;>{m>_)bW?Pe-#WP8|NwvX*+O{|%>t_LW6T6uOS%`&M zghknT7GrUiU`aN>2H6lBW+UtZ8)X;S7}MBqF`bRG6iYLMO|VHe#Y}b!yTmeVnq^sz z<=L&Qz${i|B{sum*==l&-OgUbUd&#?Udmp^ew*FFeuurB-N|0T?qaWGuVSxeuVKH- zUdvv`eviGL-OYZVy@9=v{Q-Lu`$KjQyO+J0{Smv5y@kD%y^Y<^-p<~^9$@cef6U&+ z-p$^_{)9cq{*=9!{TX{7dp~=KeSm$CeTaRSeT03KeT;paeS&?GeTsdW{W*J>eTF^4 zKFj`seU5#eeS!TY`yzXk{T2HXdyIXVeT99MeT_ZNo?uV1zh-~KzRv!Z{T=%T`+N3H z_7Ch^>?!t-?Az=+?4Q_o**~-IvG21Vuzz9y%6`b6WLl7 z%YM%Oll>R_Z}uGf1$&!>{Jo@N4;6ejUG_-@w=L z^<3c_cs<|9H}TDU3*XAO@$I~U@8CQ6jeHm1%~jsW_wc=ZAK%ZLcr$Oorvv;TZ{=;g zox8yC5I@Y1@D6^Ick*NWIA~7rll&C>B|pu(_!)kdck^?+hr4+%_i!)w@jmY7{XD>L z;y3dk5AiUM@F+jeV?53iJjn<6ARpqxe1u=%qx>Qt;~M`huJdu8;%RR12|mfExQTVQ z@Jl?yr+Jp=@GZ}8F@FhvDSsLNZGH#;9sY85AHS2o zg5Sko$zR1^js0K4f0w_OzmESNe?5Ng=D*M1z~9LKfWHYpf5`9Q_wqMmpFiUF@wf1| z^0(of_w%>o=N%1le;&POlSiE-=OA+gcGqyFzAbE9$zr1xua2Sz!!@LBH`r;y}p5f$EUeto_o>Ry|Kt(oTT(dJV`s?LcczbDw)o%l3yT^ z4Ey3*yx$!o$&|pax4udiU(LLv>ub>r^uo6W-3jD8;-H-W&G4y`?NrS?h4W~A z$zTw%=7Z-dwV!Lj+A0}Nu2Kd+C96=xi>UCv;H6a(tX2!rSOfydbhBD5RIII%;c(4I zi3XGYKp3%mX4Oloc05-5@>pabKvi1AM(y#Av(B%p+Pkafy_11>G8&DTI%?+>gX8xDAZSYW3s_XO>*2#G3ls{W7Xi2q4g>qe698ITUn~;R;*lgUpQeod##vU`uqr3jW(2&Q_%gP?IN*7hYB}wpE4XHPS}iaio*l_)3viPz=Oe#HuTtv%JW*)(U$#fRO~6$Lf2Rx0ql( zL^cnElfDFIOv8g-U!OY}ObkeO90aTh1xO!lHo#dtIC}%==PSoBCs##YwX}WNrM7%p zJlSh&=G5xwtM)py>{@$Rg4mtkt!uR_s`hJIc6Gw%4%u_Ijv5K8xOXnK$ng%TF)BIu zQ*J3*Yg>i_6a$txTpKl9il&yq2o1=*k+7UQicEf_+Eq4Re%H3^Wve=tUDaU^bCvos zH|Ilq%=si~68MU9>=R>Ctm`{DtHikP@+(y~sF!y~ zsKVLI2V@o=)IA_a4_Pg2{1em zX{BqW74o>3uS-M#asdZwTWupux#Nk|ud#RQexWNdvWbznOWI60i)}Ga5P_*BqheUD zv@W?P`Io)K!Tie6)lehv_XT1h^iVHkRh84X)WSgwyq7Uq#eoEAusj<-SPT1@PiRp& zPzy0ldXWQal3TK^$##OYY>k@4b_`t;e~|#lF# zf|W-r#nO}oD~~#LqoGn78xd)CdQ&!3+;sYcEeo#&17Vzdd&P}GU(gc?`Q*8d;K>-g z6#4k_K1#;C$BhXilJ3eh5>5)PiMV?J5B$JX*^9pk$uHGFi6^m&YXvH_S%ezP&6sjv zg$0zy`{d&arD;W4O*~vv4lYkf63laLUV*qDmDH!vFy~N>l=Bbo&aAK^8N=WeNM>Fo zS~wEN<3|rYrBwDWzudt}$_ht7kBBZz9AV$`9Q}brf3g?P%a#b3l%gW}Q7h%MmA2*A#C`E>ra&wuu zeFfgh-cSIz$>;O-;{5}1MCW;V?F%PEy%ffMc1Lhz6~qp(ZeDJU z?QIhM?{g>#kw73}a8#N=kOo%0cwR*Bs33yGsuHGZ|B%g4V-DPje+b6g@+vx zJl=Ac*1pE+M{Tf(f?#WSc@}nODYq^A(GqK_4qi6Q62!L7m*#0_fp8=tX4X{g+m;l1 zo_GN7*LZJ=(>rbZS0Wd*RBS7CQ89z9v<7h45>)dM1>X7DvS1MngwQHA zF1Qi*g+*5ra)$$bIFzQSOVUK$!!+<1Oor(#!|(-nKCccK1MtahiHnK%)&{HtdX3-jultb}+E^FlqR@|LV4|sBU(?}0_ z3TDwv>6xUjP{zFp2A$4Y8KUW&al*MuBcAwGX^Loute3fSZjc( zQBY^iOhypsnM`h0oy(QfVoo))RuLD#!KDU@&6Zj)Qn_iI6Wa@#v=BiZpHr<_GjC=m z)m&BuLp5d4GWi+GwA7+L?t=M9RtRCK#)aCUhvJkiwhkYdox<)=BCUZwQd4?%lJu8_Xliak92Imp**3ROTyG&a zYZ0|KmnsQ|$SH5eOX?Z?vS)VJ6Ulji`UXs zd`sh{^ddgVRVrRoH^I#itLWAA95@k`itDt}q4VNS3NNlt3ywuWKY?o*@w$5fFW9Si zF`iy|mr~`i(!_nadj?lYBHBr=E#V4DP1N+MQYLW@b-TDHU#8cwb4JiuIOPoJi5E$v zSL`Dtjtg$IdloO@TMYW#B=u<~N?EvWd`d4stC})qo0fcx3!vI$Yr?{T9zOYUbg7ja<1rwiD3r;I8!nn5s50js#YnV&siXYpyNWyg3+et3x)+ZC%?uaEKp$q zf?_yVN|Zo0U9!wnn`#tOkVuhU-dD5wxZ+t0NFCwS3JvfoF_TQWJUEY_60^BjK08ZBAx)D+iq- zO{4HoL8?4DdErDU3xz@Md?5!7W#%B#Hd`bde#WF@kcuheYKq$t%r%{ZYZ+Jt5-gu6 zi~!WfTqJIjTMZkAbBpi4WLsx7p)0K?;Wg!bVm{9Q?4uKP@XOBBNL^g@je2 zXHYOnY_L-tc6i$_HHEUPDV+`%Jv*l&;4G7JI$9h!Em2r%i|I04YV(Awcc%{>YCbT6 zh=%kA{6kYVhRC@}(-h`Tc^`p3pdyN?L_mtx*1 z!jH09DTE_ zMz}ag_0|xHAdSRLxe;k5o5|^fI0UUFXeqrx2{`q1z9^~yyiH^Pgo ztGpSP9;zuDWb{`l{wZ;d0--{+VpE6(1OyaNK;=LXki&uuv1RB8$pw0+j47cRNNi>z z8Nt=yr2#KsRR&rSMM4BoikxnOJRX^g;%wZY9VRmRv21 zDJqEic&-G^(9VSBkbYGh7v?53hXiaNUXA3*(X5M}l2yzhyNW0qEUPAmngknEg2zhf z2x+DuI%-@M>j?o_5ZDCWT0wITfy&IR-@GhD?^h!tF&0qlebVz z-=n7WIiOQxqB!WgEsvN>YsxLqb2FCDgH0P->$XZ^_llk2Qvk5~B+9Ewz5@hLp+lCI z5FcQl3VFp6ihJPKG>qRMjY_^kD2Bv^iOE+QM?Le3gp#)gP)A^oimRe9jQJ=-s8QXr zW^)C|zs)8rtCAd+O32VWO}kBF_aDeD1P_Q%x0P5SF2o#{n@~y2+ZzETS-BMD5FjsL zbrDA>`ZE+NmkgjixFK2`^gQDkJ$s1)gA{?ZmPPP6To!J{QAogcL(bxDfk>_l!+HkE6%7ZKqoBsw6s9`ZmDX0~J`&>zkXdW{LalqsTt zr1owQWnYZLJU9nU1ytFo@ z6!mg3FD8*j5{5o4N+=rJ@;Y*a3J;N8m^iKoup+IY&a4e(i(nCKN9O_DCtoEeNBx7l zl}rn1+HL|W7y_tHP=cPpxeFG8g%Ulj(=)RavFNLXaVa?`g~$YNC{c6fQtnq4=vV5=B-hg*JxHEOaG$nxc{POi^vu zkn)TmSGa^Eoy#?%Vv3EBMhdX7z;wf2aR8K(0rAO5;1?WhZ=a4(619!^qY0mfk;tzi zK@Zz?*?^{Y&(n+a8IK1mjZT5!l&%TE*)?ddP6S~KAUX1%_?%TVrqv+&^^!hm3^^Yl zxgGDKu8OGwI96U0@6@dK&chBE*1CwSJ%8x}Nq{raonhROUq?O20tGdhPeY$04%FD+ zgJu>HK4BK79n>RgERAf+lLga8Ei6}$m zGv+12RW#(Wgmkd>Vj~;6iKRN+5%9xC)v!VVJjM;g3zcgDq2LOxK{0>X-!mLD=#c9E zo+0YR4SWz70l&!^=vI?+I$Rr;O2DVl0h53(oY&02c01>-n?kZf^E62qvk1IsxrQnK zVU9Bi!i`X?GbdBUoXm2K$gL$oD}I%i%AzM`N??l_8##6=W9G+mdV#Y1nYdGqg*qvs z2p$1906>kb{g3v}>BBRKXO$n{MJd(v>XaH3Tdz@*>h1vfS(x@*50FwGr`1b(92m z;0xsg+GYZi2Y_8#juwkb1OO2drp*3n@)>B@buow#$Vz!!u8~0_0|is{ghgd|gpqg2 zfjz*=P-0Z{9@N7l7Z6G=6sWNl4V+67rB!qyVhxpT^oP`}F-|hpD=~EPZM%tB# zqy7)M1^OlDTz?sIL=_Z1?WVBm69b+hd{$`$2^b^pRiGV*2DdzN`WM6IfaHO4Uxiy? zcv}T-DnmA^-qa0(1W1p7)FP`l_|0N}X=CMu0+p%(9#g#S?u z5>bQ*Dm$YM7^)jD*V?s0CK|UGHk#FEas;P}cx{Y9y=ytHNyf606AT8q{Psa5$%1pW>AaMrsxT0`68v6;atBL~w zWhnj8DZsa2v}4|4%;JI^!Ou~}#Jx*~k#~+^L(cgmL$OaH!m-4}3e69-e$?8Imx@Is z4sZaLLiz;sC)JCJSjx8-bL~`&1<1fSiCSrbEM+|)-?|3M#0E~@|OiDCd`RTR)U_>3)kV`fTEQcut$Yp>PG#^ zWh#L+RJ_%VNye-p=6G;6r$*z>Mkk3lc7*(Rtc9{kB`;QSl{DgU>K_)`0H=}!NrqA* zijYN9AhDcIC1zaCL`Ib@m@Z~%;0a&=&k`N4Fgt{53Sh8H9WXskEzbdDD^E)#nK6;^JU3OOF$??3ybd zzw)`O8m|gnb>~$dy6QW1>+0(3cGtP;PS^FlSO4cVzH44_&6C$|zjpfC2e19<+P1aU+K1Nu_jL!aGp~Ecbw9X% z@AcEyKXCo`ZcuNSxZ#~QeD#L^S=YR7Xx&}wzPSF{^+W65w*Du|A>~%(kquXDNN)JZ zhUe=~)!$kFaQ!nIn>G$^ynEy4H$JoRzc%gK6x(#iriV6tYxBm*3edK>x;MEvGvtk@7{Xv)Cj$+}-d%!)F`*w&AIU?>9WXqkc!rjte_px8va*&+Kg9sqegR z=TkSXzwzvi){P&%@t<~W+U4Fgz3bjxU*7fH?$+J?yC-+Qa`$_7Kf3#|-9J*VQunFd z>X>?)`X=>3^>gaC8?R{WXgtx_)u=bVq46_~|GH<>p6)&QJrC{q#$LX+W$*dDuiX2- zz2De(-M-WNCimU5?}`2O`@Q?K`|scX*#765jx>!o-QV;?GjBfCJkoqe^MlP_Z{aP+ zTC|qCT0YS7jh0^?Xgd%+aO;709r)qFod*L4^9SF0@GA#@-rCUG*E-kw_SP@8e!unE zwkz7!we4tYZu7Q{w#~J@q3vUB-)#GC+rPIx-+pcT&h~xnt?ixdC)>}p_qT`J2iiy5 zQ|-C-7q#Ej{zlhH*Ll|k*M#emYtHpj*Ill+9jZUnet7)w+~I#X{EH)*BQNW4cf>m$ z>3H(!nWN{AK7RB^ovzNCI$zRxcju#>f8F`R&SyKHKUQ~a-Laj=4j$`0mOOUL@w(#; z$D58HJ3e^)fe)Y(%vor;}$H5>{r;neWIQ`|W^SJ%h89y_!9Oy`-gGnum+&Z=jd z&K^1Y+Ozj|^X|>vyStmZTf5J9U+8{G_v^dw>3&Q1hq^!A{do6xyPrMB&)slt>$!dB z4xQ^e_v3TVp8HkLnx1t%dwSY>PW24+jQ8Bu^YWe#_WZu3@ArJx^DWOao?rNm`1*Z= zKFjxf-`D#7uI~qZKkoaP{~G^#|5pEAe~Z7v@AqHy&-!2I|A7CC{%`xA?!UhOSpUua ziT<&Evwx=l75$&;|C|1Q>;HNGuL5;}^?{v%Ljhl4go*#IxI+HskIUcA-+}S}2TZYQ Aga7~l literal 19508 zcmb_k?{4GBk^k$ptv%a6u@EvUwwk3Q5^FivR>AQ8rmv_p7RIi6SkD^4^D?oe|m9)z#Hizxvbs^2aNsM)@L2lV75F8sC&vkz|i0eqF^; z^%NIZ_-ntw-{Ym4zo{}>M@6LaOnv>!&7c1D=Ifun{>#nPAHQ5(efgrsmiO(i>a)6y zv$#kWSD$8+Zxi=n@=5)>`ZUX)v-~yt^lw+62KLaJjRO985U-+bTG7vaclw=w>*t#{ zd2eI~wtg**tC-)iCVhJ(??u@nPWerDqTkCierw?PW5@SpY*Mb1vZVJ$)>z&fMQeV~ zx9;T!KYEcLY~jm~nJ4!flfeI&!AamuW-_3kXH}AId5|-nx}J=4)|Hof-PojSJg3+X2@v0Ueksi`%Xh#q6`$n%7H zO}5OaR~7TDjTgd!?SqAB;JBg=!zf9iQdO?fJX$K>AE^bFpRB-F6_+=}n$Y;kcWx)) z&~iqaK|guSprmTWBx-NUJ7M64o^1`nvD?F#spsB1etTNz^ir+!IoME}B7aHB1X{hq zKZ#;}?OOV^)$6?pi8I5LY|(0)rZ^?w5R3cm-f_`+Kra6VPE*{D@ssKry1VP{C7RrDOIc(sZbRdXqBHqWDC z3F%#Zawk34@&?Z2Hhi#0eRpgJPB5~=ADP34I9=px%#jmUwRds&M&XYkzbk7=mND<4 zo2fzkl88FCM>I9SH{-B`QoSbCQ@*WKq%iGfTgAn-!VytR9B8#F^0j))w}o2r1T5C( z3}0(J^&zBuo%|9j3&h`B!Pk~Xj-w2^t)gtH7Q5j*DKkhy=6_{zycGRxa`5LG2NuYH z;+q=hMXZ7+va0X#@#hvFR$77iBED8O>BY737f}K02YG-dku2g(UJ^6v306zJ=Ed`k z62qgMsa2{tDwFi>TCHVC^@9c7TyN+Z5nFE;PdEbwt9*TvhprpwQ;jXf_HM-d_Xf8& zSk@9-03xWLXmwN(LOVeicDlE#w!RKBY1^o^QEi)Z{wuMyL4 z?0vR47%IsY>2`@{Nk+t*J1>leBWEIpdK5=RCQG_$zT0=je22YR5NI=0v*Pt5A4GE% z!9rYp3ann}yE6~AShO8l2wO~BNsmTO&$B#nTcc!NLT)|t23edwl z95<$SheHNp2a^|3R<7b=T0q(3S4`wt6MtxX;ncJ59s41iTK#XG-5?=HO>Nm3F z*6U;qg+&iW?~OKU?}6IXeS5-IVQfvDA*_$AJI=^#kWMG7xU7!NWd={J?<3a|kK*gG z)98&1#j&A-rW0>11V(B~kia-9XfjYi(^vVjMfZ>$ZEumMaICPbvWp>0kaWRwNAR>T zuv6D}0@ri&7JGEO5*)!q_W9UAIQRqAfTLG5mUQFTn8|G1!<_E$VB~NAu7v4}hKAG8 z%+nryM}al-*zCS-dHuVCseK$67y&J_h9ODuECg&hM4mTDfy8FYndu@UGne+wA{MFS9)uv|c%DPmEw3I(uiQXx(NSj_TD zaA(AU9An&}MqVOezf-qXb z@FO$$&QG$vK4PR3v`H^RI27$T2GCA&{tUT}sZeh=8Q{e37TeoqOB~fRYu2NtR&dAb z)}y9KLY?5x0gl`2s+Y1~PyvETp+uRPLKQJ$26~h$^&1Z;m$+E}pz!}1@en*5RoR9NmvU*d4 zw=|h|V;-raDN=DJE|?YwgOQ@@tR-;BlN>&IpVk6|#lP74n)S7`XX1u^%<8xB^FE_@ zV$T|*n_l}nnY6raC~L_9MOJlzfNF=|&r?UvnM`LvOLYE$>w|J4-XXPT9l`{Nbx84>=cWs>u`_YT zvvEioU$@WWBum!YHL-8d$VbTkdR41SkPql7@$rFfiEob%cPG5QdwP3Hm(gB%ivGJ6 zQCqOkMnmp#tqreWF-J3&wfC{7uhHk%_&1#(aZ!*77_f`V@CN!z?TmRYw#Uz z+UeJ}f{)7JEsNI4LjB6EXj5m5Qu2M1p{HO`~HYAerS7%-K}f zy+74dly;p>fxizxJbUck+muT}s$VRM7q4?vd?;d_rrSrb`VmsCy%DmAUtw4^UA2U@ zT)=?7k=Wt+48Ft8U8Clcz%9P!+jNOod<%ZwB=F{h5>@a36f;<3DiaY^j3RHk+GYY4 zvAe+1>08Uto)pE*@n=)Yz1j##?;TT<(Ne+F?S?Y3@lxuLmkFm${HUCy3JD)iI6?AQ z^_7}D%Y}>cMt=Ve=29C=-f5(0eI!w(S)m?notBd!b#w((V?&jrOel*+dEk;~Db??j z8@S9Lboa+!zkK=O50@6pJL;I+nx+m18rYr2dJjUsY}zBqp+ z`W9m7x0FB;^B67v-~av}7&d8YLAMMJYl`+74v;_^T_s!w{D?@MU^YQNfPZIsCJ!Lu zOoDBO1^7=9L70mSf{5~va!ZWEoGPAXfm|`bRNQ`$kQvr{xVIQwxzAr|hf84!`M>Iw zQh=IQvgX4CNw(rLNg&JFheh;UG z4TM4!YppVD7@q{a6ADF^5o!l0&lzempFjTP=4Xfz@O=%`Lb=E}8a1+f`}p(`G28+O zK@I~k7`#jB771sx94tG=xZ1^t6n6BE3SmdRweB^A!O1VQ<#UvyAB!9zlZs#)Uy=$) z9{<=B2}-J!yaeOV8Er)4b<6n?F$`xYa`Zjh){RB14MX(dpfCZI`+z#^DYhh`&jN=h z)V(8fysHz*6b(jXb*^zTO1Q|w6G4_okLI?Ay_LFAE?Fo9G!&))h{|i%d;)Q*0DM?x zc!k_5k7ljXsTXJ$A{_jO02L+*Vd4ui@Fg<6mx)(u0l*&s@l=jM8RfwWP(OwrVjm^7 z0U!pT`X@G%>LIEC3@j;_b@o@MW@>GL0V~^#x=vuSUwKZBEi@M!cxJd2GUl`as-!*_ z$N<@|V2d+1-j>OtqcHevj9j$N=`5~zGC6Lhb+WtNX_IR$Q0o}^4Qk6|-+}zVn)8=* z!YDuR#(qV&ZJKEK&>8`y!Q!GoLSq2Fq(zLi2;qGl&~+4r0i>p*&bT3szhHr-i;m{2 zrQvs(&Xbr0MqT>!7&SDy1u)}!}4{{PAfI0%d@q!z$ZXgX=|N7x*_Nt0`;n#gd22_M4Ko+fF|SIFci9&2znmT zT*@p_CkRs`!}mc#jrw(WdI#MW%+9_a_8cz*bP7|;kd3;pk;`BoAzmh%2C+l=?5UoT zk!~3=JsEel9n?xq+~zA`t0^2EmG*m-sC$LlqIDw*$G`O3sZB$Sx@!^Hb&_JQ(P(t~ddNFCp1zfz-3w^<8LcqFu$vCt-&s6;5Rdda+`?%S3SD>G5 z&?FOIz-@noB1BlD&_`=bp@dG?ZR5=Mzz)z3AqImC7rJfX<*Jdg(x-pIHPuJ!9MtY?gFjNT%SMO3?V-q3CxXzm0PN^Q zOLv{&{>4mZY8|xuBIAHt7+VBQ`C@@qZVgs?2p5()(nPvttUw1P+R+f3(>5a`s9xha z+UnvP#VN#cJ6|UpMH5D5iq{lJm1q~{*p#31bZP!5joN2duQ`@wqp5f71YEJ#-Z@%Q z$MkZ`6Ql@qD%NlydvriUDhF2=urTU0p)O~DE^t=Fp9|cGdBqQc+}_9l;MsE=Zy4%k8-K5f}b_|#@3%bz2!*xO7{FD@(QU45&0=iPvK68H9$eP^FtXtWM5iXr< zackoIu>Q>RkOt(|1~e=6sjO@SH`lg{9%@VaTDZ)10j&PnjHK|b3gr+RVmRg2u z%@^mXu$R--Y1u}&0Xl(Qv{ZC~4~VI73ytoI&Lr5?k6e9kfc^XkcI-U&1e(Jk$LTyV zV;n-wYRIwqc?!~Y(j-w6VRur3cS1gZ^_^kPG=}0cdD#vfBP;mI#G_1~_U!{R0M_ao zfF5!PT&O&1WO%=L0D5%<&hsF2`S8`cE&~}w-J*|wm(COtjHn*B#STiHCN}eJ-D_6& z-7~acOTSqc_sz=@GiySG5SPO4TqzEV)in@LjbmKc;uf;goattHU0gM-^3rB;SZ_W} zr+){-W1AvvpRcJ1%SCn#&^&PF~B}Y>| z3H2#~8~uTJTGM?(;4#!Z2u=S0oVAr!9XyQ6GG9<>nE?pU zGLfQJ3}-~0ncH3Dqs%Ay8)?rtX_gQXw{}c#y$RxX_drg1$es2a^va&lqI$CEeL&Am zYlIS-IG;hpnFJ8>)dNQt#c;hEr6|_AE<^n(`rutpLsJx5%e?43Sjwhsg{4MH;_osm zkPWVK;|lIAH*{zZyP!MWxX1g*T^%R5VA~>-AGFU269=7h1i@O;tB%j%7c z6M{k$9bKy@^dRUE36+c`Sxs~C1i>C|lv2l-zAZ`_f8mM4oRduH)IPF~2lrzbI`BlD zm)EA$TA*%$+Vdx-k<4aoJ?EM3IjF2Ra^$@P1@*m9&hBZ0MvLB_SFWR#PdSQ7Y{|)h zU(9k~TjTwu0751~9IbcP0#57SZt!s~iK$xjpt!`r?6PmGXfpckTEWSoZ3Tfjiqh0b z1TTgH9o6NxNr`4pU8=4TLm_QNVfYJl5I3Hmuz0Gh!26}I(HnOGu=~T;F>t>bf6xwg zGb$#6YFZT&W{FwZ$Ia$_+TO!=_@ELF^uPOoJr2!Nf!vU{|5jG;wjrB~*91|E*%Eb54M$a(g?BLcZI47Vk#<$sf{IO5Ng;4H2LWF5MNtS5d%p_J}BwlFiyR zkzR|3h7XHs6!aZPR>ngn>mN<=(Y!=05{1%zlxIz?CI1Cw? zdHCTH!EVGKl^}g4Bdl*Y?5=D=b118v)0c6&`6^149qQ{1E^V%G<%&{j;!KSb;cX80 zUra*fystlur1-8CZ|bpe%_^ix(j9yM;Okb%Oi4?1%3Ufx+OK>Hh;$?Y<2F From 2260b5860ddb39c89a2fb702bbab34d4f8473521 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:05:16 -0400 Subject: [PATCH 183/391] Delete Shared/Localizations/en.lproj/Localizable.strings --- .../Localizations/en.lproj/Localizable.strings | Bin 19744 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Shared/Localizations/en.lproj/Localizable.strings diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings deleted file mode 100644 index 56fc6b63b186bd44cf752fda4113d7438cd15fa8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19744 zcmb_j31A#&b^iY(auO%8$MP*Z_Q;1MThiK=yyCZ4h)$V3z zSBk>bln{ske( z*xH?W|NGzn&hNb$kLNR{RXlw7JIYTPXIHQ**;TBLtzlQQYuL4HExV3g&u(Dr*m|b0 z4XmDRWSiJ#wuNnF+t_y2z;>{m>_)bW?Pe-#WP8|NwvX*+O{|%>t_LW6T6uOS%`&M zghknT7GrUiU`aN>2H6lBW+UtZ8)X;S7}MBqF`bRG6iYLMO|VHe#Y}b!yTmeVnq^sz z<=L&Qz${i|B{sum*==l&-OgUbUd&#?Udmp^ew*FFeuurB-N|0T?qaWGuVSxeuVKH- zUdvv`eviGL-OYZVy@9=v{Q-Lu`$KjQyO+J0{Smv5y@kD%y^Y<^-p<~^9$@cef6U&+ z-p$^_{)9cq{*=9!{TX{7dp~=KeSm$CeTaRSeT03KeT;paeS&?GeTsdW{W*J>eTF^4 zKFj`seU5#eeS!TY`yzXk{T2HXdyIXVeT99MeT_ZNo?uV1zh-~KzRv!Z{T=%T`+N3H z_7Ch^>?!t-?Az=+?4Q_o**~-IvG21Vuzz9y%6`b6WLl7 z%YM%Oll>R_Z}uGf1$&!>{Jo@N4;6ejUG_-@w=L z^<3c_cs<|9H}TDU3*XAO@$I~U@8CQ6jeHm1%~jsW_wc=ZAK%ZLcr$Oorvv;TZ{=;g zox8yC5I@Y1@D6^Ick*NWIA~7rll&C>B|pu(_!)kdck^?+hr4+%_i!)w@jmY7{XD>L z;y3dk5AiUM@F+jeV?53iJjn<6ARpqxe1u=%qx>Qt;~M`huJdu8;%RR12|mfExQTVQ z@Jl?yr+Jp=@GZ}8F@FhvDSsLNZGH#;9sY85AHS2o zg5Sko$zR1^js0K4f0w_OzmESNe?5Ng=D*M1z~9LKfWHYpf5`9Q_wqMmpFiUF@wf1| z^0(of_w%>o=N%1le;&POlSiE-=OA+gcGqyFzAbE9$zr1xua2Sz!!@LBH`r;y}p5f$EUeto_o>Ry|Kt(oTT(dJV`s?LcczbDw)o%l3yT^ z4Ey3*yx$!o$&|pax4udiU(LLv>ub>r^uo6W-3jD8;-H-W&G4y`?NrS?h4W~A z$zTw%=7Z-dwV!Lj+A0}Nu2Kd+C96=xi>UCv;H6a(tX2!rSOfydbhBD5RIII%;c(4I zi3XGYKp3%mX4Oloc05-5@>pabKvi1AM(y#Av(B%p+Pkafy_11>G8&DTI%?+>gX8xDAZSYW3s_XO>*2#G3ls{W7Xi2q4g>qe698ITUn~;R;*lgUpQeod##vU`uqr3jW(2&Q_%gP?IN*7hYB}wpE4XHPS}iaio*l_)3viPz=Oe#HuTtv%JW*)(U$#fRO~6$Lf2Rx0ql( zL^cnElfDFIOv8g-U!OY}ObkeO90aTh1xO!lHo#dtIC}%==PSoBCs##YwX}WNrM7%p zJlSh&=G5xwtM)py>{@$Rg4mtkt!uR_s`hJIc6Gw%4%u_Ijv5K8xOXnK$ng%TF)BIu zQ*J3*Yg>i_6a$txTpKl9il&yq2o1=*k+7UQicEf_+Eq4Re%H3^Wve=tUDaU^bCvos zH|Ilq%=si~68MU9>=R>Ctm`{DtHikP@+(y~sF!y~ zsKVLI2V@o=)IA_a4_Pg2{1em zX{BqW74o>3uS-M#asdZwTWupux#Nk|ud#RQexWNdvWbznOWI60i)}Ga5P_*BqheUD zv@W?P`Io)K!Tie6)lehv_XT1h^iVHkRh84X)WSgwyq7Uq#eoEAusj<-SPT1@PiRp& zPzy0ldXWQal3TK^$##OYY>k@4b_`t;e~|#lF# zf|W-r#nO}oD~~#LqoGn78xd)CdQ&!3+;sYcEeo#&17Vzdd&P}GU(gc?`Q*8d;K>-g z6#4k_K1#;C$BhXilJ3eh5>5)PiMV?J5B$JX*^9pk$uHGFi6^m&YXvH_S%ezP&6sjv zg$0zy`{d&arD;W4O*~vv4lYkf63laLUV*qDmDH!vFy~N>l=Bbo&aAK^8N=WeNM>Fo zS~wEN<3|rYrBwDWzudt}$_ht7kBBZz9AV$`9Q}brf3g?P%a#b3l%gW}Q7h%MmA2*A#C`E>ra&wuu zeFfgh-cSIz$>;O-;{5}1MCW;V?F%PEy%ffMc1Lhz6~qp(ZeDJU z?QIhM?{g>#kw73}a8#N=kOo%0cwR*Bs33yGsuHGZ|B%g4V-DPje+b6g@+vx zJl=Ac*1pE+M{Tf(f?#WSc@}nODYq^A(GqK_4qi6Q62!L7m*#0_fp8=tX4X{g+m;l1 zo_GN7*LZJ=(>rbZS0Wd*RBS7CQ89z9v<7h45>)dM1>X7DvS1MngwQHA zF1Qi*g+*5ra)$$bIFzQSOVUK$!!+<1Oor(#!|(-nKCccK1MtahiHnK%)&{HtdX3-jultb}+E^FlqR@|LV4|sBU(?}0_ z3TDwv>6xUjP{zFp2A$4Y8KUW&al*MuBcAwGX^Loute3fSZjc( zQBY^iOhypsnM`h0oy(QfVoo))RuLD#!KDU@&6Zj)Qn_iI6Wa@#v=BiZpHr<_GjC=m z)m&BuLp5d4GWi+GwA7+L?t=M9RtRCK#)aCUhvJkiwhkYdox<)=BCUZwQd4?%lJu8_Xliak92Imp**3ROTyG&a zYZ0|KmnsQ|$SH5eOX?Z?vS)VJ6Ulji`UXs zd`sh{^ddgVRVrRoH^I#itLWAA95@k`itDt}q4VNS3NNlt3ywuWKY?o*@w$5fFW9Si zF`iy|mr~`i(!_nadj?lYBHBr=E#V4DP1N+MQYLW@b-TDHU#8cwb4JiuIOPoJi5E$v zSL`Dtjtg$IdloO@TMYW#B=u<~N?EvWd`d4stC})qo0fcx3!vI$Yr?{T9zOYUbg7ja<1rwiD3r;I8!nn5s50js#YnV&siXYpyNWyg3+et3x)+ZC%?uaEKp$q zf?_yVN|Zo0U9!wnn`#tOkVuhU-dD5wxZ+t0NFCwS3JvfoF_TQWJUEY_60^BjK08ZBAx)D+iq- zO{4HoL8?4DdErDU3xz@Md?5!7W#%B#Hd`bde#WF@kcuheYKq$t%r%{ZYZ+Jt5-gu6 zi~!WfTqJIjTMZkAbBpi4WLsx7p)0K?;Wg!bVm{9Q?4uKP@XOBBNL^g@je2 zXHYOnY_L-tc6i$_HHEUPDV+`%Jv*l&;4G7JI$9h!Em2r%i|I04YV(Awcc%{>YCbT6 zh=%kA{6kYVhRC@}(-h`Tc^`p3pdyN?L_mtx*1 z!jH09DTE_ zMz}ag_0|xHAdSRLxe;k5o5|^fI0UUFXeqrx2{`q1z9^~yyiH^Pgo ztGpSP9;zuDWb{`l{wZ;d0--{+VpE6(1OyaNK;=LXki&uuv1RB8$pw0+j47cRNNi>z z8Nt=yr2#KsRR&rSMM4BoikxnOJRX^g;%wZY9VRmRv21 zDJqEic&-G^(9VSBkbYGh7v?53hXiaNUXA3*(X5M}l2yzhyNW0qEUPAmngknEg2zhf z2x+DuI%-@M>j?o_5ZDCWT0wITfy&IR-@GhD?^h!tF&0qlebVz z-=n7WIiOQxqB!WgEsvN>YsxLqb2FCDgH0P->$XZ^_llk2Qvk5~B+9Ewz5@hLp+lCI z5FcQl3VFp6ihJPKG>qRMjY_^kD2Bv^iOE+QM?Le3gp#)gP)A^oimRe9jQJ=-s8QXr zW^)C|zs)8rtCAd+O32VWO}kBF_aDeD1P_Q%x0P5SF2o#{n@~y2+ZzETS-BMD5FjsL zbrDA>`ZE+NmkgjixFK2`^gQDkJ$s1)gA{?ZmPPP6To!J{QAogcL(bxDfk>_l!+HkE6%7ZKqoBsw6s9`ZmDX0~J`&>zkXdW{LalqsTt zr1owQWnYZLJU9nU1ytFo@ z6!mg3FD8*j5{5o4N+=rJ@;Y*a3J;N8m^iKoup+IY&a4e(i(nCKN9O_DCtoEeNBx7l zl}rn1+HL|W7y_tHP=cPpxeFG8g%Ulj(=)RavFNLXaVa?`g~$YNC{c6fQtnq4=vV5=B-hg*JxHEOaG$nxc{POi^vu zkn)TmSGa^Eoy#?%Vv3EBMhdX7z;wf2aR8K(0rAO5;1?WhZ=a4(619!^qY0mfk;tzi zK@Zz?*?^{Y&(n+a8IK1mjZT5!l&%TE*)?ddP6S~KAUX1%_?%TVrqv+&^^!hm3^^Yl zxgGDKu8OGwI96U0@6@dK&chBE*1CwSJ%8x}Nq{raonhROUq?O20tGdhPeY$04%FD+ zgJu>HK4BK79n>RgERAf+lLga8Ei6}$m zGv+12RW#(Wgmkd>Vj~;6iKRN+5%9xC)v!VVJjM;g3zcgDq2LOxK{0>X-!mLD=#c9E zo+0YR4SWz70l&!^=vI?+I$Rr;O2DVl0h53(oY&02c01>-n?kZf^E62qvk1IsxrQnK zVU9Bi!i`X?GbdBUoXm2K$gL$oD}I%i%AzM`N??l_8##6=W9G+mdV#Y1nYdGqg*qvs z2p$1906>kb{g3v}>BBRKXO$n{MJd(v>XaH3Tdz@*>h1vfS(x@*50FwGr`1b(92m z;0xsg+GYZi2Y_8#juwkb1OO2drp*3n@)>B@buow#$Vz!!u8~0_0|is{ghgd|gpqg2 zfjz*=P-0Z{9@N7l7Z6G=6sWNl4V+67rB!qyVhxpT^oP`}F-|hpD=~EPZM%tB# zqy7)M1^OlDTz?sIL=_Z1?WVBm69b+hd{$`$2^b^pRiGV*2DdzN`WM6IfaHO4Uxiy? zcv}T-DnmA^-qa0(1W1p7)FP`l_|0N}X=CMu0+p%(9#g#S?u z5>bQ*Dm$YM7^)jD*V?s0CK|UGHk#FEas;P}cx{Y9y=ytHNyf606AT8q{Psa5$%1pW>AaMrsxT0`68v6;atBL~w zWhnj8DZsa2v}4|4%;JI^!Ou~}#Jx*~k#~+^L(cgmL$OaH!m-4}3e69-e$?8Imx@Is z4sZaLLiz;sC)JCJSjx8-bL~`&1<1fSiCSrbEM+|)-?|3M#0E~@|OiDCd`RTR)U_>3)kV`fTEQcut$Yp>PG#^ zWh#L+RJ_%VNye-p=6G;6r$*z>Mkk3lc7*(Rtc9{kB`;QSl{DgU>K_)`0H=}!NrqA* zijYN9AhDcIC1zaCL`Ib@m@Z~%;0a&=&k`N4Fgt{53Sh8H9WXskEzbdDD^E)#nK6;^JU3OOF$??3ybd zzw)`O8m|gnb>~$dy6QW1>+0(3cGtP;PS^FlSO4cVzH44_&6C$|zjpfC2e19<+P1aU+K1Nu_jL!aGp~Ecbw9X% z@AcEyKXCo`ZcuNSxZ#~QeD#L^S=YR7Xx&}wzPSF{^+W65w*Du|A>~%(kquXDNN)JZ zhUe=~)!$kFaQ!nIn>G$^ynEy4H$JoRzc%gK6x(#iriV6tYxBm*3edK>x;MEvGvtk@7{Xv)Cj$+}-d%!)F`*w&AIU?>9WXqkc!rjte_px8va*&+Kg9sqegR z=TkSXzwzvi){P&%@t<~W+U4Fgz3bjxU*7fH?$+J?yC-+Qa`$_7Kf3#|-9J*VQunFd z>X>?)`X=>3^>gaC8?R{WXgtx_)u=bVq46_~|GH<>p6)&QJrC{q#$LX+W$*dDuiX2- zz2De(-M-WNCimU5?}`2O`@Q?K`|scX*#765jx>!o-QV;?GjBfCJkoqe^MlP_Z{aP+ zTC|qCT0YS7jh0^?Xgd%+aO;709r)qFod*L4^9SF0@GA#@-rCUG*E-kw_SP@8e!unE zwkz7!we4tYZu7Q{w#~J@q3vUB-)#GC+rPIx-+pcT&h~xnt?ixdC)>}p_qT`J2iiy5 zQ|-C-7q#Ej{zlhH*Ll|k*M#emYtHpj*Ill+9jZUnet7)w+~I#X{EH)*BQNW4cf>m$ z>3H(!nWN{AK7RB^ovzNCI$zRxcju#>f8F`R&SyKHKUQ~a-Laj=4j$`0mOOUL@w(#; z$D58HJ3e^)fe)Y(%vor;}$H5>{r;neWIQ`|W^SJ%h89y_!9Oy`-gGnum+&Z=jd z&K^1Y+Ozj|^X|>vyStmZTf5J9U+8{G_v^dw>3&Q1hq^!A{do6xyPrMB&)slt>$!dB z4xQ^e_v3TVp8HkLnx1t%dwSY>PW24+jQ8Bu^YWe#_WZu3@ArJx^DWOao?rNm`1*Z= zKFjxf-`D#7uI~qZKkoaP{~G^#|5pEAe~Z7v@AqHy&-!2I|A7CC{%`xA?!UhOSpUua ziT<&Evwx=l75$&;|C|1Q>;HNGuL5;}^?{v%Ljhl4go*#IxI+HskIUcA-+}S}2TZYQ Aga7~l From 4bddb85f31f4ba770a6b3eac0327888eac30738b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:06:05 -0400 Subject: [PATCH 184/391] Add files via upload --- .../en.lproj/Localizable.strings | 422 ++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 Shared/Localizations/en.lproj/Localizable.strings diff --git a/Shared/Localizations/en.lproj/Localizable.strings b/Shared/Localizations/en.lproj/Localizable.strings new file mode 100644 index 00000000..d5dbf2ed --- /dev/null +++ b/Shared/Localizations/en.lproj/Localizable.strings @@ -0,0 +1,422 @@ +/* + Localizable.strings + feather + + Created by samara on 25.08.2024. + +*/ + +// MARK: - Generic +// Unknown, default string for applications +"UNKNOWN" = "Unknown"; +// Default string +"DEFAULT" = "Default"; +// Copy provided string in a menu +"COPY" = "Copy"; +// Delete item +"DELETE" = "Delete"; +// Cancel alert action +"CANCEL" = "Cancel"; +// Done action +"DONE" = "Done"; +// Dismiss action +"DISMISS" = "Dismiss"; +// Dismiss 2 action +"LAME" = "Lame"; +// Save action +"SAVE" = "Save"; +// Set action +"SET" = "Save"; +// OK action +"OK" = "OK"; +// Continue action +"CONTINUE" = "Continue"; +// Import Action +"IMPORT" = "Import"; +// Add Action +"ADD" = "Add"; +// Install Action +"INSTALL" = "Install"; + +// MARK: - Alerts +// Alert titles +"ALERT_SUCCESS" = "Success"; +"ALERT_TRACE" = "Trace"; +"ALERT_ERROR" = "Error"; +"ALERT_CRITICAL" = "Critical"; +"ALERT_COPIED" = "Copied"; + +// MARK: - Error messages +// Installer Error Title +"ERROR_INSTALLER" = "Installer"; +// Installer Error Description +"ERROR_INSTALLER_DESCRIPTION" = "Failed to load SSL certificates"; +"ERROR_ZSIGN_FAILED" = "Signing failed."; +"ERROR_FAILED_TO_READ_MOBILEPROVISION" = "Failed to read mobileprovision file"; + +// MARK: - Success messages +// Successfully signed an application +"SUCCESS_SIGNED" = "Successfully signed %@"; +// Successfully resigned an application +"SUCCESS_RESIGN" = "Successfully resigned!"; +// Success message requiring user to restart app +"SUCCESS_REQUIRES_RESTART" = "You must close the app for changes to take effect."; + +// MARK: - Onboarding +// Welcome to Feather!!!!!!!! +"ONBOARDING_WELCOMETITLE_1" = "Welcome to"; +// First feature inside of onboarding +"ONBOARDING_CELL_1_TITLE" = "Sideload On Device"; +"ONBOARDING_CELL_1_DESCRIPTION" = "Sideload apps without a computer, all done from your device."; +// Second feature inside of onboarding +"ONBOARDING_CELL_2_TITLE" = "Customize Apps"; +"ONBOARDING_CELL_2_DESCRIPTION" = "Manage and customize your apps for your needs."; +// Third feature inside of onboarding +"ONBOARDING_CELL_3_TITLE" = "And Much Much More!"; +"ONBOARDING_CELL_3_DESCRIPTION" = "AltStore, Esign, Scarlet, trollstore any repositories, import IPA's, easy certificate management, and much much more."; +// "Developed by BDG. Made for users who are passionate for sideloading and freedom. Many features included inside. Learn more..." +"ONBOARDING_FOOTER" = "Developed by BDG. Made for users who are passionate for sideloading and freedom. Many features included inside."; +"ONBOARDING_FOOTER_LINK" = "Learn more..."; +// Continue button to exit onboarding +"ONBOARDING_CONTINUE_BUTTON" = "Continue"; + +// MARK: - Tab area +// Sources tab +"TAB_SOURCES" = "Sources"; +// Library tab +"TAB_LIBRARY" = "Library"; +// Settings tab +"TAB_SETTINGS" = "Settings"; + +// MARK: - TransferPreview +// Packaging application to get ready for install +"TRANSFER_PREVIEW_PACKAGING" = "Packaging..."; +// Ready to install package +"TRANSFER_PREVIEW_READY" = "Ready To Install"; +// Opening manifest.plist for iOS +"TRANSFER_PREVIEW_SENDING_MANIFEST" = "Sending Manifest..."; +// iOS is retrieving IPA file +"TRANSFER_PREVIEW_SENDING_PAYLOAD" = "Sending Payload..."; +// Done transferring IPA file +"TRANSFER_PREVIEW_DONE" = "Done"; +// Completed packaging so you can share +"TRANSFER_PREVIEW_COMPLETED" = "Completed"; + +// MARK: - SourcesViewController +// Repositories title +"SOURCES_VIEW_CONTROLLER_REPOSITORIES" = "Repositories"; +// Add repo button +"SOURCES_VIEW_CONTROLLER_ADD_SOURCES" = "Add Repo"; +// Number of sources, i.e. "100 Sources" +"SOURCES_VIEW_CONTROLLER_NUMBER_OF_SOURCES" = "%@ Source"; +"SOURCES_VIEW_CONTROLLER_NUMBER_OF_SOURCES_PLURAL" = "%@ Sources"; +// Search sources in search bar +"SOURCES_VIEW_CONTROLLER_SEARCH_SOURCES" = "Search Sources"; + +// MARK: - SourcesViewController - Add Sources +"SOURCES_VIEW_ADD_SOURCES_ALERT_TITLE" = "Add Source"; +"SOURCES_VIEW_ADD_SOURCES_ALERT_DESCRIPTION" = "Add Altstore Repo URL"; +"SOURCES_VIEW_ADD_SOURCES_ALERT_BUTTON_IMPORT_REPO" = "Import Repositories"; +"SOURCES_VIEW_ADD_SOURCES_ALERT_BUTTON_EXPORT_REPO" = "Export Repositories"; +"SOURCES_VIEW_ADD_SOURCES_ALERT_BUTTON_EXPORT_REPO_ACTION_SUCCESS" = "Copied Repositories to Clipboard"; +//Footer Validation +"SOURCES_VIEW_ADD_SOURCES_FOOTER_NOTSTARTED" = "Enter a URL to start validation."; +"SOURCES_VIEW_ADD_SOURCES_FOOTER_NOTVALIDJSON" = "Invalid JSON, please check your input."; +"SOURCES_VIEW_ADD_SOURCES_FOOTER_VALID" = "Valid JSON entered."; + +// MARK: - SourcesViewController -> SourcesAppViewController +// Number of apps, i.e. "444 Apps" +"SOURCES_APP_VIEW_CONTROLLER_NUMBER_OF_APPS" = "%@ App"; +"SOURCES_APP_VIEW_CONTROLLER_NUMBER_OF_APPS_PLURAL" = "%@ Apps"; +// Search apps in search bar +"SOURCES_APP_VIEW_CONTROLLER_SEARCH_APPS" = "Search Apps"; + +// MARK: - SourcesViewController ... - Cells +// Default subtitle string +"SOURCES_CELLS_DEFAULT_SUBTITLE" = "An awesome application."; +// Default description string +"SOURCES_CELLS_DEFAULT_DESCRIPTION" = "A cool description."; + +// MARK: - SourceAppViewController ... - Actions +// Available Versions Alert Title +"SOURCES_CELLS_ACTIONS_HOLD_AVAILABLE_VERSIONS" = "Available Versions"; +//Filter Menu +"SOURCES_CELLS_ACTIONS_FILTER_TITLE" = "Filter by"; +"SOURCES_CELLS_ACTIONS_FILTER_BY_DEFAULT" = "Default"; +"SOURCES_CELLS_ACTIONS_FILTER_BY_NAME" = "Name"; +"SOURCES_CELLS_ACTIONS_FILTER_BY_DATE" = "Date"; + +// MARK: - AppsInformationViewController +// Application Section +"APPS_INFORMATION_SECTION_TITLE_NAME" = "Application"; +// Bundle Section +"APPS_INFORMATION_SECTION_TITLE_NAME" = "Bundle"; + +// +"APPS_INFORMATION_TITLE_DELETED_FILE" = "Deleted File"; +"APPS_INFORMATION_TITLE_DELETED_FILE_TITLE" = "File has been deleted."; +"APPS_INFORMATION_TITLE_DELETED_FILE_DESCRIPTION" = "This is a useless entry, it does not have a file and Feather will not allow you to install it. It's recommended you delete by swiping on the cell in the Apps tab."; +// Application Section +"APPS_INFORMATION_TITLE_NAME" = "Name"; +"APPS_INFORMATION_TITLE_VERSION" = "Version"; +"APPS_INFORMATION_TITLE_IDENTIFIER" = "Identifier"; +"APPS_INFORMATION_TITLE_SIZE" = "Size"; +// +"APPS_INFORMATION_TITLE_DATE_ADDED" = "Date Added"; +// Bundle Section +"APPS_INFORMATION_TITLE_BUNDLE_NAME" = "Bundle Name"; +"APPS_INFORMATION_TITLE_BUNDLE_PATH" = "Bundle Path"; +"APPS_INFORMATION_TITLE_ICON_FILE" = "Icon File"; +// +"APPS_INFORMATION_TITLE_OPEN_IN_FILES" = "Open in Files"; + +// MARK: - AppSigningViewController +"APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_CUSTOMIZATION" = "Customization"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_SIGNING" = "Signing"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_ADVANCED" = "Advanced"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS" = "Add Tweaks"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_ADVANCED" = "Advanced"; +"APP_SIGNING_VIEW_CONTROLLER_START_SIGNING" = "Start Signing"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS" = "Modify Dylibs"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES" = "Properties"; + +// MARK: - AppSigningViewController - No Certificates Alert +"APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE" = "Error"; +"APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION" = "You do not have a certificate selected, please select or upload one in the signing section of the settings tab."; + +// MARK: - AppSigningViewController -> AppSigningAdvancedViewController +"APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_APPEARENCE" = "Appearence"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_MINIMUM_APP_VERSION" = "Minimum App Version"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_PROPERTIES" = "Properties"; + +// MARK: - AppSigningViewController -> AppSigningTweakViewController +"APP_SIGNING_TWEAK_VIEW_CONTROLLER_TITLE" = "Tweaks"; + +// MARK: - SigningsOptionsViewController + +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_PROTECTIONS" = "Enable Protection"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_PROTECTIONS_DESCRIPTION" = "Enabling protection will append a random string to each bundle identifier, this is to protect the Apple ID related to your certificate from being flagged by Apple. However, if you don't care about this you can ignore it."; + +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_DYNAMIC_PROTECTION" = "Dynamic Protection"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_DYNAMIC_PROTECTION_DESCRIPTION" = "Dynamic protection will only apply PPQ protection if the bundle identifier exists on the App Store. This requires an internet connection during signing."; + +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_IDENTIFIERS" = "Bundle Identifiers"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_IDENTIFIERS_NEW" = "New Identifier"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_IDENTIFIERS_ID" = "Identifier"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_IDENTIFIERS_ID_REPLACEMENT" = "Replacement"; + +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_DISPLAYNAMES" = "Display Names"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_DISPLAYNAMES_ID" = "Original Display Name"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_DISPLAYNAMES_ID_REPLACEMENT" = "New Display Name"; + +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_IMMEDIATELY_INSTALL_FROM_SOURCE" = "Immediately Install from Source"; +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_IMMEDIATELY_INSTALL_FROM_SOURCE_DESCRIPTION" = "When enabled, apps downloaded from sources will prompt to sign and install after downloading."; + +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_INSTALLAFTERSIGNED" = "Install after Signing"; + +"APP_SIGNING_VIEW_CONTROLLER_CELL_SIGNING_OPTIONS_TITLE" = "Signing Options"; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_PLUGINS" = "Remove all PlugIns"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_PLUGINS_DESCRIPTION" = "Removes the PlugIns directory inside of the app, which would usually have some components for the app to function properly."; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_UISUPPORTEDDEVICES" = "Remove UISupportedDevices"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_UISUPPORTEDDEVICES_DESCRIPTION" = "Removes device restrictions for the application."; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_URLSCHEME" = "Remove URLScheme"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_URLSCHEME_DESCRIPTION" = "Removes any possible URL schemes (i.e. 'feather://')"; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_ALLOW_BROWSING_DOCUMENTS" = "Allow Browsing Documents"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_ALLOW_BROWSING_DOCUMENTS_DESCRIPTION" = "Allows other apps to open and edit the files stored in the Documents folder. This option also lets users set the app’s default save location in Settings."; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_ALLOW_ITUNES_SHARING" = "Allow iTunes Sharing"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_ALLOW_ITUNES_SHARING_DESCRIPTION" = "Forces the app to share their documents directory, allowing sharing between iTunes and Finder."; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_PRO_MOTION" = "Force ProMotion"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_PRO_MOTION_DESCRIPTION" = "Enables ProMotion capabilities within the app, however on lower versions of 15.x this may not be enough."; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_GAME_MODE" = "Force Game Mode"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_GAME_MODE_DESCRIPTION" = "Enables Game Mode within the app, minimizing background activity and prioritized performance for the app."; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_FULLSCREEN" = "Force Fullscreen"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_FORCE_FULLSCREEN_DESCRIPTION" = "Forces only fullscreen capabilities within iPad apps, disallowing sharing the screen with other apps. On an external screen, the window for an app with this setting maintains its canvas size."; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_DELETE_PLACEHOLDER_WATCH_APP" = "Delete Placeholder Watch App"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_DELETE_PLACEHOLDER_WATCH_APP_DESCRIPTION" = "Removes unwanted watch placeholder which isn't supposed to be there, present in apps such as YouTube music, etc."; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_FORCELOCALIZATIONS" = "Force Try To Localize"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_FORCELOCALIZATIONS_DESCRIPTION" = "Forces localization by modifying every localizable bundle within the app when trying to change a name of the app."; + +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_PROVISIONING" = "Remove Provisioning File"; +"APP_SIGNING_INPUT_VIEW_CONTROLLER_REMOVE_PROVISIONING_DESCRIPTION" = "Removes .mobileprovison from appearing in your app after signing."; + +// MARK: - LibraryViewController +//Search Library placeholder +"SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER" = "Search Library"; +//Headers +"LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS" = "Signed Apps"; +"LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL" = "%@ Signed"; +"LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL_PLURAL" = "%@ Signed"; +"LIBRARY_VIEW_CONTROLLER_SECTION_BUTTON_IMPORT" = "Import"; +"LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS" = "Downloaded Apps"; +"LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL" = "%@ Downloaded"; +//Import Action Sheet +"LIBRARY_VIEW_CONTROLLER_IMPORT_ACTION_SHEET_FILE" = "Import from Files"; +"LIBRARY_VIEW_CONTROLLER_IMPORT_ACTION_SHEET_URL" = "Import from URL"; +//Sign Action Sheet +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN" = "Sign %@"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL" = "Sign & Install %@"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN" = "ReSign %@"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL" = "Install %@"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE" = "Share %@"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN" = "Open %@"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE" = "Update %@"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE" = "Clear Update"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS" = "View Details"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES" = "Open in Files"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM" = "Confirm Installation"; +"LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION" = "Trying to install via the downloaded apps tab may not work as they are most likely not signed! It's recommended you sign that application first before installing."; + +// MARK: - SettingsViewController +// Headers +"SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_GENERAL" = "General"; +"SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_SIGNING" = "Signing"; +"SETTINGS_VIEW_CONTROLLER_SECTION_TITLE_SIGNING_SERVER" = "Signing Server"; + +// Footers +"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_ISSUES" = "If any issues occur within Backdoor please report it via my website. When submitting an issue, be sure to submit any logs."; +"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_SERVER_LIMITATIONS" = "Sadly due to limitations server certificates will need to be re-renewed every year to keep Feathers local features working properly, tap this button to retrieve the most up-to-date files from our repositories."; +"SETTINGS_VIEW_CONTROLLER_SECTION_FOOTER_DEFAULT_SERVER" = "Default server goes to %@"; + +"SETTINGS_VIEW_CONTROLLER_TITLE" = "Server Options"; +"SETTINGS_VIEW_CONTROLLER_TITLE_ONLINE" = "Online"; +"SETTINGS_VIEW_CONTROLLER_TITLE_LOCAL" = "Local"; + +// Cell Titles +// About Feather +"SETTINGS_VIEW_CONTROLLER_CELL_ABOUT" = "About %@"; +"SETTINGS_VIEW_CONTROLLER_CELL_SUBMIT_FEEDBACK" = "Submit Feedback"; +"SETTINGS_VIEW_CONTROLLER_CELL_GITHUB" = "GitHub Repository"; +"SETTINGS_VIEW_CONTROLLER_CELL_DISPLAY" = "Display"; +"SETTINGS_VIEW_CONTROLLER_CELL_APP_ICON" = "App Icon"; +"SETTINGS_VIEW_CONTROLLER_CELL_LANGUAGE" = "Language"; +"SETTINGS_VIEW_CONTROLLER_CELL_CURRENT_CERTIFICATE_NOSELECTED" = "No certificates selected"; +"SETTINGS_VIEW_CONTROLLER_CELL_ADD_CERTIFICATES" = "Add Certificate"; + + + +"SETTINGS_VIEW_CONTROLLER_CELL_SIGN_OPTIONS" = "Signing Options"; +"SETTINGS_VIEW_CONTROLLER_CELL_SERVER_OPTIONS" = "Server Options"; +"SETTINGS_VIEW_CONTROLLER_CELL_VIEW_LOGS" = "View Logs"; +"SETTINGS_VIEW_CONTROLLER_CELL_APPS_FOLDER" = "Open Apps Folder"; +"SETTINGS_VIEW_CONTROLLER_CELL_CERTS_FOLDER" = "Open Certificates Folder"; + + +"SETTINGS_VIEW_CONTROLLER_CELL_UPDATE_LOCAL_CERTIFICATE" = "Update Local Certificate"; +"SETTINGS_VIEW_CONTROLLER_CELL_UPDATE_LOCAL_CERTIFICATE_UPDATING" = "Update Local Certificate"; +"SETTINGS_VIEW_CONTROLLER_CELL_RESET" = "Reset"; +"SETTINGS_VIEW_CONTROLLER_CELL_RESET_ALL" = "Reset All"; + +"SETTINGS_VIEW_CONTROLLER_CELL_RESET_CONFIGURATION" = "Reset Configuration"; +"SETTINGS_VIEW_CONTROLLER_CELL_USE_CUSTOM_SERVER" = "Use Custom Server"; + +"SETTINGS_VIEW_CONTROLLER_CELL_ONLINE_INSTALL_METHOD" = "Online Install Method"; + + +"SETTINGS_VIEW_CONTROLLER_CELL_EXPORT_ID" = "Export Random Identifier"; +"SETTINGS_VIEW_CONTROLLER_CELL_CHANGE_ID" = "Change Random Identifier"; + +// +"SETTINGS_VIEW_CONTROLLER_PPQ_ALERT_TITLE" = "PPQCheck Protections"; +"SETTINGS_VIEW_CONTROLLER_PPQ_ALERT_DESCRIPTION" = "This setting enables the PPQCheck protections, which is designed to prepend each bundle identifier for the apps you sideload with a random string.\n\nThis is meant to avoid apple flagging your account by (trying) to make it so they're unable to associate the app you're sideloading with one from the App Store."; + +"SETTINGS_VIEW_CONTROLLER_URL_ALERT_TITLE" = "Change Download URL"; +"SETTINGS_VIEW_CONTROLLER_CELL_CHANGE_IDENTIFIER" = "Change Random Identifier"; + +// MARK: - SettingsViewController -> AboutViewController +"ABOUT_VIEW_CONTROLLER_SECTION_TITLE_CREDITS" = "Credits"; +"ABOUT_VIEW_CONTROLLER_SECTION_TITLE_SPONSORS" = "Sponsors"; + +"ABOUT_VIEW_CONTROLLER_SECTION_TITLE_DEVICE" = "Device"; +"ABOUT_VIEW_CONTROLLER_SECTION_TITLE_ACKNOWLEDGEMENTS" = "Acknowledgements"; + + +"ABOUT_VIEW_CONTROLLER_CELL_DEVICE_VERSION" = "Device Version"; +"ABOUT_VIEW_CONTROLLER_CELL_DEVICE_ARCH" = "Architecture"; +"ABOUT_VIEW_CONTROLLER_CELL_APP_VERSION" = "App Version"; + +// MARK: - SettingsViewController -> DisplayViewController +"DISPLAY_VIEW_CONTROLLER_SECTION_TITLE_TINT_COLOR" = "Tint Color"; +"DISPLAY_VIEW_CONTROLLER_SECTION_TITLE_APP_APPEARENCE" = "App Appearence"; +"DISPLAY_VIEW_CONTROLLER_SECTION_TITLE_STORE" = "Store"; + +"DISPLAY_VIEW_CONTROLLER_CELL_DEFAULT_SUBTITLE" = "Default Subtitle"; +"DISPLAY_VIEW_CONTROLLER_CELL_DEFAULT_SUBTITLE_DESCRIPTION" = "Default style for backdoor, hides localized description and only includes subtitle."; + +"DISPLAY_VIEW_CONTROLLER_CELL_LOCALIZED_SUBTITLE" = "Localized Subtitle"; +"DISPLAY_VIEW_CONTROLLER_CELL_LOCALIZED_SUBTITLE_DESCRIPTION" = "Replaces subtitle with app description."; + +"DISPLAY_VIEW_CONTROLLER_CELL_BIG_DESCRIPTION" = "Big Description"; +"DISPLAY_VIEW_CONTROLLER_CELL_BIG_DESCRIPTION_DESCRIPTION" = "Replaces screenshots with the app description."; + +"DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME" = "Use Team Name"; +"DISPLAY_VIEW_CONTROLLER_CELL_TEAM_NAME_DESCRIPTION" = "Replaces the certificate name with your team name, could be for better to distinguish between certificates if providers happen to always use the same App ID name."; + +// MARK: - SettingsViewController -> LanguageViewController.swift +"LANGUAGE_VIEW_TITLE" = "Language"; +"LANGUAGE_VIEW_CELL_USE_SYSTEM_LANGUAGE" = "Use System Language"; + +// MARK: - SettingsViewController -> CertificatesViewController +"CERTIFICATES_VIEW_CONTROLLER_TITLE" = "Certificates"; + +"CERTIFICATES_VIEW_CONTROLLER_CELL_ADD_FOOTER" = "Supported file formats:\n\n- P12 (.p12)\n- Mobile Provision (.mobileprovision)\n\nMake sure your certificates are valid and are able to sideload to your device!"; + +"CERTIFICATES_VIEW_CONTROLLER_CELL_ADD" = "Add Certificates"; +"CERTIFICATES_VIEW_CONTROLLER_CELL_ADD_DESCRIPTION" = "Tap to add a certificate"; + +// MARK: - SettingsViewController -> CertificatesViewController -> Delete Alert +"CERTIFICATES_VIEW_CONTROLLER_DELETE_ALERT_TITLE" = "You don't want to do this!"; +"CERTIFICATES_VIEW_CONTROLLER_DELETE_ALERT_DESCRIPTION" = "You're trying to delete a selected certificate, try again later when you have another certificate on hand."; + +// MARK: - SettingsViewController -> CertificatesViewController -> Cell +"CERTIFICATES_VIEW_CONTROLLER_CELL_EXPIRED" = "Expired"; +"CERTIFICATES_VIEW_CONTROLLER_CELL_DAYS_LEFT" = "%@ days left"; + +// MARK: - SettingsViewController -> CertificatesViewController -> CertImportingViewController +"CERT_IMPORTING_VIEWCONTROLLER_TITLE" = "Import"; +"CERT_IMPORTING_VIEWCONTROLLER_SECTION_PROVISIONING" = ""; + +// MARK: - SettingsViewController -> CertificatesViewController -> CertImportingViewController -> Password Alert + +"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_TITLE" = "Bad Password"; +"CERT_IMPORTING_VIEWCONTROLLER_PW_ALERT_DESCRIPTION" = "Please check the password and try again."; + +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PROV" = "Import Provisioning File"; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_CERT" = "Import Certificate File"; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_ENTER_PW" = "Enter Password"; +"CERT_IMPORTING_VIEWCONTROLLER_CELL_IMPORT_PW" = "Password"; + +"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PROV" = "Import a provisioning file to be able to sideload to your device."; +"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_CERT" = "Import a file containing a valid certificate."; +"CERT_IMPORTING_VIEWCONTROLLER_FOOTER_PASS" = "Enter the password associated with the private key, leave it blank if theres no password required."; + +// MARK: - SettingsViewController -> ResetViewController +"RESET_VIEW_CONTROLLER_CLEAR_CACHE" = "Clear Network Cache"; +"RESET_VIEW_CONTROLLER_CLEAR_CACHE_ALERT_TITLE" = "This action is irreversible. Cached network requests and images will be cleared."; + +// MARK: - Donation +"DONATION_TITLE" = "Donate"; +"DONATION_DONATIONS" = "Donations"; + +"DONATION_CELL_1_TITLE" = "Secret Repo"; +"DONATION_CELL_1_DESCRIPTION" = "Get access to our secret repo by donating, will provide you with beta access to Feather."; + +"DONATION_CELL_2_TITLE" = "Show Your Support"; +"DONATION_CELL_2_DESCRIPTION" = "Show your support by donating! If you're unable to donate, spreading the word about Feather works too!"; + +// MARK: - SettingsViewController -> LogsViewController.swift +"LOGS_VIEW_SECTION_TITLE_ERROR" = "%@ Critical Errors."; +"LOGS_VIEW_SECTION_TITLE_SHARE" = "Share Logs"; +"LOGS_VIEW_SECTION_TITLE_COPY" = "Copy Logs"; +"LOGS_VIEW_SUCCESS_DESCRIPTION" = "Log contents have been copied to clipboard."; +"LOGS_VIEW_FAIL_DESCRIPTION" = "Failed to copy log contents."; +"LOGS_VIEW_TITLE" = "Backdoor Logs"; From b24e1a62ac33714cbada084abe2b44dd22a5e2bd Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:20:20 -0400 Subject: [PATCH 185/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 221 +-------------------- 1 file changed, 2 insertions(+), 219 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 96a592a4..eb35792a 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -364,7 +364,7 @@ extension LibraryViewController { resignApp(certificate: cert, appPath: filePath2!) { success in if success { - CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in + CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { [...] DispatchQueue.main.async { self.loaderAlert?.dismiss(animated: true) Debug.shared.log(message: "Done action??") @@ -444,20 +444,12 @@ extension LibraryViewController { let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - } let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) alertController.addAction(confirmAction) alertController.addAction(cancelAction) - self.present(alertController, animated: true, completion: nil) - - let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - self.present(alertController, animated: true, completion: nil) } } @@ -473,213 +465,4 @@ extension LibraryViewController { presentationController.prefersGrabberVisible = true } - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } - default: - break - } - - tableView.deselectRow(at: indexPath, animated: true) - } - - @objc func startSigning(meow: NSManagedObject) { - if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for:(meow as! DownloadedApps)).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) - } - } - } - - override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - - let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break - } - completionHandler(true) - } - - deleteAction.backgroundColor = UIColor.red - let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) - configuration.performsFirstActionWithFullSwipe = true - - return configuration - } - - override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - - let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS"), image: UIImage(systemName: "info.circle"), handler: {_ in - - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] - } - } - - self.present(navigationController, animated: true) - }), - - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES"), image: UIImage(systemName: "folder"), handler: {_ in - - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") - - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") - } - } - }) - ]) - }) - return configuration - } -} - -extension LibraryViewController { - @objc func afetch() { self.fetchSources() } - - func fetchSources() { - signedApps = CoreDataManager.shared.getDatedSignedApps() - downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - - DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() - } - } - } - - func getApplicationFilePath(with app: NSManagedObject, row: Int, section:Int, getuuidonly: Bool = false) -> URL? { - if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) - } - - if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) - } - return nil - } - - func getApplication(row: Int, section: Int) -> NSManagedObject? { - if isFiltering { - if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] - } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] - } - } - } else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] - } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] - } - } - } - return nil - } -} - -extension LibraryViewController: UISearchResultsUpdating { - func updateSearchResults(for searchController: UISearchController) { - let searchText = searchController.searchBar.text ?? "" - filterContentForSearchText(searchText) - tableView.reloadData() - } - - private func filterContentForSearchText(_ searchText: String) { - let lowercasedSearchText = searchText.lowercased() - - filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] - - filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] - } -} - -extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { - func setupSearchController() { - searchController = UISearchController(searchResultsController: nil) - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = true - searchController.searchResultsUpdater = self - searchController.delegate = self - searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") - navigationItem.searchController = searchController - definesPresentationContext = true - navigationItem.hidesSearchBarWhenScrolling = false - } - - var isFiltering: Bool { - return searchController.isActive && !searchBarIsEmpty - } - - var searchBarIsEmpty: Bool { - return searchController.searchBar.text?.isEmpty ?? true - } -} - -/// https://stackoverflow.com/a/75310581 -func presentLoader() -> UIAlertController { - let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.isUserInteractionEnabled = false - activityIndicator.startAnimating() - - alert.view.addSubview(activityIndicator) - - NSLayoutConstraint.activate([ - alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant: 95), - activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) - ]) - - return alert -} \ No newline at end of file + self.present(p \ No newline at end of file From 07c774df4453f9ea9695205dd3b1b47e6cd7f663 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:21:51 -0400 Subject: [PATCH 186/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 265 ++++++++++++++++++++- 1 file changed, 264 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index eb35792a..6af683fd 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -465,4 +465,267 @@ extension LibraryViewController { presentationController.prefersGrabberVisible = true } - self.present(p \ No newline at end of file + self.present(popupVC, animated: true) +} else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) +} +case 1: + if FileManager.default.fileExists(atPath: filePath2!.path) { + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet + + let singingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let button1 = PopupViewControllerButton( + title: singingData.signingOptions.installAfterSigned + ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) + : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), + color: .tintColor.withAlphaComponent(0.9)) + button1.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startSigning(meow: source!) + } + + let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: .quaternarySystemFill, titleColor: .tintColor) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + let alertController = UIAlertController( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), + message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), + preferredStyle: .alert + ) + + let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in + self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + } + let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) + + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true, completion: nil) + } + } + + popupVC.configureButtons([button1, button2]) + + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium(), + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } +default: + break +} + +tableView.deselectRow(at: indexPath, animated: true) +} + +@objc func startSigning(meow: NSManagedObject) { +if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: (meow as! DownloadedApps)).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) + } +} +} + +override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { +let source = getApplication(row: indexPath.row, section: indexPath.section) + +let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break + } + completionHandler(true) +} + +deleteAction.backgroundColor = UIColor.red +let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) +configuration.performsFirstActionWithFullSwipe = true + +return configuration +} + +override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { +let source = getApplication(row: indexPath.row, section: indexPath.section) +let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + +let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] + } + } + + self.present(navigationController, animated: true) + }), + + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") + } + } + }) + ]) +}) +return configuration +} +} + +extension LibraryViewController { +@objc func afetch() { self.fetchSources() } + +func fetchSources() { +signedApps = CoreDataManager.shared.getDatedSignedApps() +downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + +DispatchQueue.main.async { + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() + } +} +} + +func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { +if section == 0 { + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) +} + +if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) +} +return nil +} + +func getApplication(row: Int, section: Int) -> NSManagedObject? { +if isFiltering { + if section == 0 { + if row < filteredSignedApps.count { + return filteredSignedApps[row] + } + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] + } + } +} else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] + } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] + } + } +} +return nil +} +} + +extension LibraryViewController: UISearchResultsUpdating { +func updateSearchResults(for searchController: UISearchController) { +let searchText = searchController.searchBar.text ?? "" +filterContentForSearchText(searchText) +tableView.reloadData() +} + +private func filterContentForSearchText(_ searchText: String) { +let lowercasedSearchText = searchText.lowercased() + +filteredSignedApps = signedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) +} ?? [] + +filteredDownloadedApps = downloadedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) +} ?? [] +} +} + +extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { +func setupSearchController() { +searchController = UISearchController(searchResultsController: nil) +searchController.obscuresBackgroundDuringPresentation = false +searchController.hidesNavigationBarDuringPresentation = true +searchController.searchResultsUpdater = self +searchController.delegate = self +searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") +navigationItem.searchController = searchController +definesPresentationContext = true +navigationItem.hidesSearchBarWhenScrolling = false +} + +var isFiltering: Bool { +return searchController.isActive && !searchBarIsEmpty +} + +var searchBarIsEmpty: Bool { +return searchController.searchBar.text?.isEmpty ?? true +} +} + +/// https://stackoverflow.com/a/75310581 +func presentLoader() -> UIAlertController { +let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) +let activityIndicator = UIActivityIndicatorView(style: .large) +activityIndicator.translatesAutoresizingMaskIntoConstraints = false +activityIndicator.isUserInteractionEnabled = false +activityIndicator.startAnimating() + +alert.view.addSubview(activityIndicator) + +NSLayoutConstraint.activate([ + alert.view.heightAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant: 95), + activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) +]) + +return alert +} \ No newline at end of file From 6fbc78f8dd0febbfaba0d25cbc8669993c7a6858 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:23:24 -0400 Subject: [PATCH 187/391] Update PopupViewController.swift --- iOS/Views/Extra/PopupViewController.swift | 192 +++++++++++----------- 1 file changed, 95 insertions(+), 97 deletions(-) diff --git a/iOS/Views/Extra/PopupViewController.swift b/iOS/Views/Extra/PopupViewController.swift index 9f64d0ba..12ebc5cb 100644 --- a/iOS/Views/Extra/PopupViewController.swift +++ b/iOS/Views/Extra/PopupViewController.swift @@ -1,107 +1,105 @@ -// -// PopupViewController.swift -// feather -// -// Created by samara on 8/10/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import Foundation import UIKit class PopupViewController: UIViewController { - - private let stackView = UIStackView() - - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = .systemBackground - setupStackView() - } - - private func setupStackView() { - stackView.axis = .vertical - stackView.spacing = 10 - stackView.alignment = .fill - stackView.distribution = .fillEqually - stackView.translatesAutoresizingMaskIntoConstraints = false - - view.addSubview(stackView) - - NSLayoutConstraint.activate([ - stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), - stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), - stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20) - ]) - } + + private let stackView = UIStackView() + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemBackground + setupStackView() + } + + private func setupStackView() { + stackView.axis = .vertical + stackView.spacing = 10 + stackView.alignment = .fill + stackView.distribution = .fillEqually + stackView.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(stackView) + + NSLayoutConstraint.activate([ + stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20) + ]) + } - - func configureButtons(_ buttons: [UIButton]) { - stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - buttons.forEach { button in - stackView.addArrangedSubview(button) - } - } + + func configureButtons(_ buttons: [UIButton]) { + stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } + buttons.forEach { button in + stackView.addArrangedSubview(button) + } + } } class PopupViewControllerButton: UIButton { - var onTap: (() -> Void)? - private var originalBackgroundColor: UIColor? - - init(title: String, color: UIColor, titleColor: UIColor? = .white) { - super.init(frame: .zero) - setupButton(title: title, color: color, titlecolor: titleColor!) - addTarget(self, action: #selector(buttonPressed), for: .touchDown) - addTarget(self, action: #selector(buttonReleased), for: .touchUpInside) - addTarget(self, action: #selector(buttonReleased), for: .touchUpOutside) - addTarget(self, action: #selector(buttonCancelled), for: .touchCancel) - addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + var onTap: (() -> Void)? + private var originalBackgroundColor: UIColor? + + init(title: String, color: UIColor, titleColor: UIColor? = .white) { + super.init(frame: .zero) + setupButton(title: title, color: color, titlecolor: titleColor!) + addTarget(self, action: #selector(buttonPressed), for: .touchDown) + addTarget(self, action: #selector(buttonReleased), for: .touchUpInside) + addTarget(self, action: #selector(buttonReleased), for: .touchUpOutside) + addTarget(self, action: #selector(buttonCancelled), for: .touchCancel) + addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - setupButton(title: String.localized("DEFAULT"), color: .systemBlue, titlecolor: .white) - addTarget(self, action: #selector(buttonPressed), for: .touchDown) - addTarget(self, action: #selector(buttonReleased), for: .touchUpInside) - addTarget(self, action: #selector(buttonReleased), for: .touchUpOutside) - addTarget(self, action: #selector(buttonCancelled), for: .touchCancel) - addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupButton(title: String.localized("DEFAULT"), color: .systemBlue, titlecolor: .white) + addTarget(self, action: #selector(buttonPressed), for: .touchDown) + addTarget(self, action: #selector(buttonReleased), for: .touchUpInside) + addTarget(self, action: #selector(buttonReleased), for: .touchUpOutside) + addTarget(self, action: #selector(buttonCancelled), for: .touchCancel) + addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) - } - - private func setupButton(title: String, color: UIColor, titlecolor: UIColor) { - setTitle(title, for: .normal) - originalBackgroundColor = color - backgroundColor = color - setTitleColor(titlecolor, for: .normal) - titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) - layer.cornerRadius = 12 - layer.cornerCurve = .continuous - layer.masksToBounds = true - contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - - @objc private func buttonPressed() { - UIView.animate(withDuration: 0.1) { - self.backgroundColor = self.originalBackgroundColor?.withAlphaComponent(0.6) - } - } - - @objc private func buttonReleased() { - UIView.animate(withDuration: 0.1) { - self.backgroundColor = self.originalBackgroundColor - } - } - - @objc private func buttonCancelled() { - UIView.animate(withDuration: 0.1) { - self.backgroundColor = self.originalBackgroundColor - } - } - - @objc private func buttonTapped() { - onTap?() - } - -} + } + + private func setupButton(title: String, color: UIColor, titlecolor: UIColor) { + setTitle(title, for: .normal) + originalBackgroundColor = color + backgroundColor = color + setTitleColor(titlecolor, for: .normal) + titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) + layer.cornerRadius = 12 + layer.cornerCurve = .continuous + layer.masksToBounds = true + if #available(iOS 15.0, *) { + var config = UIButton.Configuration.filled() + config.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + self.configuration = config + } else { + // Fallback on earlier versions + contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } + + @objc private func buttonPressed() { + UIView.animate(withDuration: 0.1) { + self.backgroundColor = self.originalBackgroundColor?.withAlphaComponent(0.6) + } + } + + @objc private func buttonReleased() { + UIView.animate(withDuration: 0.1) { + self.backgroundColor = self.originalBackgroundColor + } + } + + @objc private func buttonCancelled() { + UIView.animate(withDuration: 0.1) { + self.backgroundColor = self.originalBackgroundColor + } + } + + @objc private func buttonTapped() { + onTap?() + } +} \ No newline at end of file From 0174caf17e30e7ee0dbb1faefbb3c976aa59762b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:30:37 -0400 Subject: [PATCH 188/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 9795b086..58e27d73 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -9,7 +9,6 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe private let fileManager = FileManager.default private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name - let utilities = HomeViewUtilities() let fileHandlers = HomeViewFileHandlers() var documentsDirectory: URL { @@ -91,7 +90,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } catch { DispatchQueue.main.async { self?.activityIndicator.stopAnimating() - self?.utilities.handleError(on: self!, error: error, withTitle: "Loading Files") + self?.handleError(error, withTitle: "Loading Files") } } } @@ -126,10 +125,10 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe switch actionTitle { case "Select": self.selectFiles() case "Import": self.fileHandlers.importFile(viewController: self) - case "New Folder": self.utilities.showInputAlert(on: self, title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in + case "New Folder": self.showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) } - case "New File": self.utilities.showInputAlert(on: self, title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in + case "New File": self.showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in self.fileHandlers.createNewFile(viewController: self, fileName: fileName) } default: break @@ -169,7 +168,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe do { try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) } catch { - utilities.handleError(on: self, error: error, withTitle: "Creating Files Directory") + handleError(error, withTitle: "Creating Files Directory") } } From 27949a3288e0300282f6bd81e43d853400fb2fac Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:40:37 -0400 Subject: [PATCH 189/391] Update HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 104 ++++++++++++++++++++----- 1 file changed, 84 insertions(+), 20 deletions(-) diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift index cdbe45eb..48a195bb 100644 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -1,33 +1,97 @@ import UIKit +import ZIPFoundation -extension HomeViewController { - func presentAlert(title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) - alert.addAction(action) +class HomeViewFileHandlers { + private let fileManager = FileManager.default - if handler != nil { - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + func uploadFile(viewController: UIViewController) { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = viewController as? UIDocumentPickerDelegate + documentPicker.modalPresentationStyle = .formSheet + viewController.present(documentPicker, animated: true, completion: nil) + } + + func importFile(viewController: UIViewController) { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = viewController as? UIDocumentPickerDelegate + documentPicker.modalPresentationStyle = .formSheet + viewController.present(documentPicker, animated: true, completion: nil) + } + + func createNewFolder(viewController: HomeViewController, folderName: String) { + let folderURL = viewController.documentsDirectory.appendingPathComponent(folderName) + do { + try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) + viewController.loadFiles() + } catch { + viewController.utilities.handleError(error, withTitle: "Creating Folder") } + } - present(alert, animated: true, completion: nil) + func createNewFile(viewController: HomeViewController, fileName: String) { + let fileURL = viewController.documentsDirectory.appendingPathComponent(fileName) + fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + viewController.loadFiles() } - func showInputAlert(title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = initialText + func renameFile(viewController: HomeViewController, fileURL: URL, newName: String) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + viewController.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + } + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.utilities.handleError(error, withTitle: "Renaming File") + } + } } - let confirmAction = UIAlertAction(title: actionTitle, style: .default) { [weak alertController] _ in - guard let textField = alertController?.textFields?.first, let text = textField.text else { return } - completion(text) + } + + func deleteFile(viewController: HomeViewController, fileURL: URL) { + viewController.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.removeItem(at: fileURL) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + } + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.utilities.handleError(error, withTitle: "Deleting File") + } + } + } + } + + func unzipFile(viewController: HomeViewController, fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + viewController.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + } + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.utilities.handleError(error, withTitle: "Unzipping File") + } + } } - alertController.addAction(confirmAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) } - func handleError(_ error: Error, withTitle title: String = "Error") { - presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") + func shareFile(viewController: UIViewController, fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + viewController.present(activityController, animated: true, completion: nil) } } \ No newline at end of file From 243559d0c8b66bf559391482eb4d22cd4a67e67d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:42:36 -0400 Subject: [PATCH 190/391] Update HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 104 +++++-------------------- 1 file changed, 20 insertions(+), 84 deletions(-) diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift index 48a195bb..cdbe45eb 100644 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -1,97 +1,33 @@ import UIKit -import ZIPFoundation -class HomeViewFileHandlers { - private let fileManager = FileManager.default +extension HomeViewController { + func presentAlert(title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) + alert.addAction(action) - func uploadFile(viewController: UIViewController) { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController as? UIDocumentPickerDelegate - documentPicker.modalPresentationStyle = .formSheet - viewController.present(documentPicker, animated: true, completion: nil) - } - - func importFile(viewController: UIViewController) { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController as? UIDocumentPickerDelegate - documentPicker.modalPresentationStyle = .formSheet - viewController.present(documentPicker, animated: true, completion: nil) - } - - func createNewFolder(viewController: HomeViewController, folderName: String) { - let folderURL = viewController.documentsDirectory.appendingPathComponent(folderName) - do { - try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) - viewController.loadFiles() - } catch { - viewController.utilities.handleError(error, withTitle: "Creating Folder") + if handler != nil { + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) } - } - func createNewFile(viewController: HomeViewController, fileName: String) { - let fileURL = viewController.documentsDirectory.appendingPathComponent(fileName) - fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) - viewController.loadFiles() + present(alert, animated: true, completion: nil) } - func renameFile(viewController: HomeViewController, fileURL: URL, newName: String) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.loadFiles() - } - } catch { - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(error, withTitle: "Renaming File") - } - } + func showInputAlert(title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = initialText } - } - - func deleteFile(viewController: HomeViewController, fileURL: URL) { - viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.removeItem(at: fileURL) - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.loadFiles() - } - } catch { - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(error, withTitle: "Deleting File") - } - } - } - } - - func unzipFile(viewController: HomeViewController, fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") - viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.loadFiles() - } - } catch { - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(error, withTitle: "Unzipping File") - } - } + let confirmAction = UIAlertAction(title: actionTitle, style: .default) { [weak alertController] _ in + guard let textField = alertController?.textFields?.first, let text = textField.text else { return } + completion(text) } + alertController.addAction(confirmAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) } - func shareFile(viewController: UIViewController, fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - viewController.present(activityController, animated: true, completion: nil) + func handleError(_ error: Error, withTitle title: String = "Error") { + presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") } } \ No newline at end of file From d9938b8fb17cc947115e1ba2d315957e1cf772fc Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:45:11 -0400 Subject: [PATCH 191/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 58e27d73..a59b2a6c 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -10,6 +10,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name let fileHandlers = HomeViewFileHandlers() + let utilities = HomeViewUtilities() // Add this line var documentsDirectory: URL { let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") From bc1438ad1ccdb44688e6047b2825ed88d5a3db10 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:50:14 -0400 Subject: [PATCH 192/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 279 +-------------------- 1 file changed, 7 insertions(+), 272 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 6af683fd..39f2c1d0 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -218,6 +218,10 @@ class LibraryViewController: UITableViewController { }()) return isDebug } + + private func resignApp(certificate: Certificate, appPath: URL, completion: @escaping (Bool) -> Void) { + // Implement resignApp functionality here + } } extension LibraryViewController { @@ -362,9 +366,9 @@ extension LibraryViewController { if let cert = CoreDataManager.shared.getCurrentCertificate() { self.present(self.loaderAlert!, animated: true) - resignApp(certificate: cert, appPath: filePath2!) { success in + self.resignApp(certificate: cert, appPath: filePath2!) { success in if success { - CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { [...] + CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in DispatchQueue.main.async { self.loaderAlert?.dismiss(animated: true) Debug.shared.log(message: "Done action??") @@ -459,273 +463,4 @@ extension LibraryViewController { let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) if let presentationController = popupVC.presentationController as? UISheetPresentationController { presentationController.detents = [ - detent2, - .medium(), - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) -} else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) -} -case 1: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet - - let singingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let button1 = PopupViewControllerButton( - title: singingData.signingOptions.installAfterSigned - ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) - : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: .tintColor.withAlphaComponent(0.9)) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startSigning(meow: source!) - } - - let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: .quaternarySystemFill, titleColor: .tintColor) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - let alertController = UIAlertController( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), - message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), - preferredStyle: .alert - ) - - let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - } - let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - self.present(alertController, animated: true, completion: nil) - } - } - - popupVC.configureButtons([button1, button2]) - - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium(), - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } -default: - break -} - -tableView.deselectRow(at: indexPath, animated: true) -} - -@objc func startSigning(meow: NSManagedObject) { -if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: (meow as! DownloadedApps)).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) - } -} -} - -override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { -let source = getApplication(row: indexPath.row, section: indexPath.section) - -let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break - } - completionHandler(true) -} - -deleteAction.backgroundColor = UIColor.red -let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) -configuration.performsFirstActionWithFullSwipe = true - -return configuration -} - -override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { -let source = getApplication(row: indexPath.row, section: indexPath.section) -let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - -let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS"), image: UIImage(systemName: "info.circle"), handler: { _ in - - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] - } - } - - self.present(navigationController, animated: true) - }), - - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in - - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") - - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") - } - } - }) - ]) -}) -return configuration -} -} - -extension LibraryViewController { -@objc func afetch() { self.fetchSources() } - -func fetchSources() { -signedApps = CoreDataManager.shared.getDatedSignedApps() -downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - -DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() - } -} -} - -func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { -if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) -} - -if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) -} -return nil -} - -func getApplication(row: Int, section: Int) -> NSManagedObject? { -if isFiltering { - if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] - } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] - } - } -} else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] - } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] - } - } -} -return nil -} -} - -extension LibraryViewController: UISearchResultsUpdating { -func updateSearchResults(for searchController: UISearchController) { -let searchText = searchController.searchBar.text ?? "" -filterContentForSearchText(searchText) -tableView.reloadData() -} - -private func filterContentForSearchText(_ searchText: String) { -let lowercasedSearchText = searchText.lowercased() - -filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) -} ?? [] - -filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) -} ?? [] -} -} - -extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { -func setupSearchController() { -searchController = UISearchController(searchResultsController: nil) -searchController.obscuresBackgroundDuringPresentation = false -searchController.hidesNavigationBarDuringPresentation = true -searchController.searchResultsUpdater = self -searchController.delegate = self -searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") -navigationItem.searchController = searchController -definesPresentationContext = true -navigationItem.hidesSearchBarWhenScrolling = false -} - -var isFiltering: Bool { -return searchController.isActive && !searchBarIsEmpty -} - -var searchBarIsEmpty: Bool { -return searchController.searchBar.text?.isEmpty ?? true -} -} - -/// https://stackoverflow.com/a/75310581 -func presentLoader() -> UIAlertController { -let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) -let activityIndicator = UIActivityIndicatorView(style: .large) -activityIndicator.translatesAutoresizingMaskIntoConstraints = false -activityIndicator.isUserInteractionEnabled = false -activityIndicator.startAnimating() - -alert.view.addSubview(activityIndicator) - -NSLayoutConstraint.activate([ - alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant: 95), - activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) -]) - -return alert -} \ No newline at end of file + det \ No newline at end of file From c82927bb68d054c18cf9a51ecf2c31ae05bcc3c5 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 11:50:53 -0400 Subject: [PATCH 193/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 39f2c1d0..1c2def51 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -463,4 +463,13 @@ extension LibraryViewController { let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) if let presentationController = popupVC.presentationController as? UISheetPresentationController { presentationController.detents = [ - det \ No newline at end of file + det + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + .medium(), + .large() + ] + presentationController.prefersGrabberVisible = true +} + +self.present(popupVC, animated: true) \ No newline at end of file From 7c6c05271e356a230094fefcb32e8cee84444ad7 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 15:59:16 -0400 Subject: [PATCH 194/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index a59b2a6c..14eafed0 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -10,7 +10,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name let fileHandlers = HomeViewFileHandlers() - let utilities = HomeViewUtilities() // Add this line + let utilities = HomeViewUtilities() var documentsDirectory: URL { let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") From e42b2a2cb60b1a8a8efa03722eeae97db212555d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 16:06:21 -0400 Subject: [PATCH 195/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 14eafed0..4fd0e6bb 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,6 +1,9 @@ import UIKit import ZIPFoundation +// Add the import statement for HomeViewUtilities +import HomeViewUtilities + class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { // MARK: - Properties @@ -10,7 +13,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name let fileHandlers = HomeViewFileHandlers() - let utilities = HomeViewUtilities() + let utilities = HomeViewUtilities() // Add this line var documentsDirectory: URL { let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") From a87fd6d4c50fbf89124f29094688e268abfa6364 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 16:21:06 -0400 Subject: [PATCH 196/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 51 ++++++++++++++++++++----- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 4fd0e6bb..7659de8b 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,7 +1,5 @@ import UIKit import ZIPFoundation - -// Add the import statement for HomeViewUtilities import HomeViewUtilities class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { @@ -13,7 +11,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name let fileHandlers = HomeViewFileHandlers() - let utilities = HomeViewUtilities() // Add this line + let utilities = HomeViewUtilities() var documentsDirectory: URL { let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") @@ -129,12 +127,14 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe switch actionTitle { case "Select": self.selectFiles() case "Import": self.fileHandlers.importFile(viewController: self) - case "New Folder": self.showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in - self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) - } - case "New File": self.showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in - self.fileHandlers.createNewFile(viewController: self, fileName: fileName) - } + case "New Folder": + self.showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in + self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) + } + case "New File": + self.showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in + self.fileHandlers.createNewFile(viewController: self, fileName: fileName) + } default: break } }) @@ -218,4 +218,37 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { return UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) } +} + +// Extension methods for HomeViewController +extension HomeViewController { + func presentAlert(title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) + alert.addAction(action) + + if handler != nil { + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + } + + present(alert, animated: true, completion: nil) + } + + func showInputAlert(title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = initialText + } + let confirmAction = UIAlertAction(title: actionTitle, style: .default) { [weak alertController] _ in + guard let textField = alertController?.textFields?.first, let text = textField.text else { return } + completion(text) + } + alertController.addAction(confirmAction) + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + func handleError(_ error: Error, withTitle title: String = "Error") { + presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") + } } \ No newline at end of file From ec7b13a7a6e84fc40a9cfee966b4fa62feb31c91 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 16:22:50 -0400 Subject: [PATCH 197/391] Update HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift index cdbe45eb..0a8a4323 100644 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -1,7 +1,9 @@ import UIKit -extension HomeViewController { - func presentAlert(title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { +class HomeViewUtilities { + + // Method to present a simple alert + func presentAlert(in viewController: UIViewController, title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) alert.addAction(action) @@ -10,10 +12,11 @@ extension HomeViewController { alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) } - present(alert, animated: true, completion: nil) + viewController.present(alert, animated: true, completion: nil) } - func showInputAlert(title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { + // Method to show an input alert + func showInputAlert(in viewController: UIViewController, title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addTextField { textField in textField.text = initialText @@ -24,10 +27,11 @@ extension HomeViewController { } alertController.addAction(confirmAction) alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) + viewController.present(alertController, animated: true, completion: nil) } - func handleError(_ error: Error, withTitle title: String = "Error") { - presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") + // Method to handle errors + func handleError(in viewController: UIViewController, error: Error, withTitle title: String = "Error") { + presentAlert(in: viewController, title: title, message: "An error occurred: \(error.localizedDescription)") } } \ No newline at end of file From 061c96f6e1ffd5df9c328c9fe18ca17cbb7da702 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 16:27:09 -0400 Subject: [PATCH 198/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 51 ++++++++++++++-------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 1c2def51..1d444ab3 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -245,11 +245,11 @@ extension LibraryViewController { subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL", arguments: String(signedApps?.count ?? 0)), buttonTitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_BUTTON_IMPORT"), buttonAction: { - self.startImporting() - }) + self.startImporting() + } + ) return headerWithButton case 1: - let headerWithButton = GroupedSectionHeader( title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS"), subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL", arguments: String(downloadedApps?.count ?? 0)) @@ -269,7 +269,6 @@ extension LibraryViewController { let source = getApplication(row: indexPath.row, section: indexPath.section) let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - if let iconURL = source!.value(forKey: "iconURL") as? String { let imagePath = filePath!.appendingPathComponent(iconURL) @@ -424,19 +423,24 @@ extension LibraryViewController { popupVC = PopupViewController() popupVC.modalPresentationStyle = .pageSheet - let singingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let signingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) let button1 = PopupViewControllerButton( - title: singingData.signingOptions.installAfterSigned + title: signingData.signingOptions.installAfterSigned ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: .tintColor.withAlphaComponent(0.9)) + color: .tintColor.withAlphaComponent(0.9) + ) button1.onTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) self.startSigning(meow: source!) } - let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: .quaternarySystemFill, titleColor: .tintColor) + let button2 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) button2.onTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) { @@ -463,13 +467,24 @@ extension LibraryViewController { let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) if let presentationController = popupVC.presentationController as? UISheetPresentationController { presentationController.detents = [ - det - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - .medium(), - .large() - ] - presentationController.prefersGrabberVisible = true -} - -self.present(popupVC, animated: true) \ No newline at end of file + .medium(), + .large() + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) + } + } + } + + // Placeholder methods for undefined functions in this context + func getApplication(row: Int, section: Int) -> Any? { return nil } + func getApplicationFilePath(with source: Any, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { return nil } + func startImporting() {} + func startInstallProcess(meow: Any, filePath: String) {} + func shareFile(meow: Any, filePath: String) {} + func startSigning(meow: Any) {} + @objc func afetch() {} + func presentLoader() -> UIAlertController? { return nil } +} \ No newline at end of file From c882610b5b12b10299b902be8fe90ead5ad6f431 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 16:28:58 -0400 Subject: [PATCH 199/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 7659de8b..fabbd02d 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,6 +1,5 @@ import UIKit import ZIPFoundation -import HomeViewUtilities class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { From 55be65e90b14e04646d0eee9bd1c9c52002dfe6c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 16:42:03 -0400 Subject: [PATCH 200/391] Update HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift index 0a8a4323..750e597c 100644 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -3,7 +3,7 @@ import UIKit class HomeViewUtilities { // Method to present a simple alert - func presentAlert(in viewController: UIViewController, title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { + func presentAlert(in viewController: HomeViewController, title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) alert.addAction(action) @@ -16,7 +16,7 @@ class HomeViewUtilities { } // Method to show an input alert - func showInputAlert(in viewController: UIViewController, title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { + func showInputAlert(in viewController: HomeViewController, title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addTextField { textField in textField.text = initialText @@ -31,7 +31,7 @@ class HomeViewUtilities { } // Method to handle errors - func handleError(in viewController: UIViewController, error: Error, withTitle title: String = "Error") { + func handleError(in viewController: HomeViewController, error: Error, withTitle title: String = "Error") { presentAlert(in: viewController, title: title, message: "An error occurred: \(error.localizedDescription)") } } \ No newline at end of file From 57d3c710332f24b9d31e807bb20afa32bcf05754 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 16:42:39 -0400 Subject: [PATCH 201/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 41 +++---------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index fabbd02d..793de65f 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -91,7 +91,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe } catch { DispatchQueue.main.async { self?.activityIndicator.stopAnimating() - self?.handleError(error, withTitle: "Loading Files") + self?.utilities.handleError(in: self!, error: error, withTitle: "Loading Files") } } } @@ -127,11 +127,11 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe case "Select": self.selectFiles() case "Import": self.fileHandlers.importFile(viewController: self) case "New Folder": - self.showInputAlert(title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in + self.utilities.showInputAlert(in: self, title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) } case "New File": - self.showInputAlert(title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in + self.utilities.showInputAlert(in: self, title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in self.fileHandlers.createNewFile(viewController: self, fileName: fileName) } default: break @@ -171,7 +171,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe do { try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) } catch { - handleError(error, withTitle: "Creating Files Directory") + utilities.handleError(in: self, error: error, withTitle: "Creating Files Directory") } } @@ -217,37 +217,4 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { return UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) } -} - -// Extension methods for HomeViewController -extension HomeViewController { - func presentAlert(title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) - alert.addAction(action) - - if handler != nil { - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - } - - present(alert, animated: true, completion: nil) - } - - func showInputAlert(title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = initialText - } - let confirmAction = UIAlertAction(title: actionTitle, style: .default) { [weak alertController] _ in - guard let textField = alertController?.textFields?.first, let text = textField.text else { return } - completion(text) - } - alertController.addAction(confirmAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - func handleError(_ error: Error, withTitle title: String = "Error") { - presentAlert(title: title, message: "An error occurred: \(error.localizedDescription)") - } } \ No newline at end of file From 69c179ddbaf13f7fcaa7553128b1fba7efae847e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 17:06:13 -0400 Subject: [PATCH 202/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 48a195bb..eaf67a4d 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -24,7 +24,7 @@ class HomeViewFileHandlers { try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) viewController.loadFiles() } catch { - viewController.utilities.handleError(error, withTitle: "Creating Folder") + viewController.utilities.handleError(in: viewController, error: error, withTitle: "Creating Folder") } } @@ -47,7 +47,7 @@ class HomeViewFileHandlers { } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(error, withTitle: "Renaming File") + viewController.utilities.handleError(in: viewController, error: error, withTitle: "Renaming File") } } } @@ -65,7 +65,7 @@ class HomeViewFileHandlers { } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(error, withTitle: "Deleting File") + viewController.utilities.handleError(in: viewController, error: error, withTitle: "Deleting File") } } } @@ -84,7 +84,7 @@ class HomeViewFileHandlers { } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(error, withTitle: "Unzipping File") + viewController.utilities.handleError(in: viewController, error: error, withTitle: "Unzipping File") } } } From ec67fa6c9ce6d433485720bada901e36ef633eed Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 17:08:27 -0400 Subject: [PATCH 203/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index eaf67a4d..44490ae0 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -24,7 +24,7 @@ class HomeViewFileHandlers { try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) viewController.loadFiles() } catch { - viewController.utilities.handleError(in: viewController, error: error, withTitle: "Creating Folder") + viewController.utilities.handleError(viewController, error: error, withTitle: "Creating Folder") } } @@ -47,7 +47,7 @@ class HomeViewFileHandlers { } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(in: viewController, error: error, withTitle: "Renaming File") + viewController.utilities.handleError(viewController, error: error, withTitle: "Renaming File") } } } @@ -65,7 +65,7 @@ class HomeViewFileHandlers { } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(in: viewController, error: error, withTitle: "Deleting File") + viewController.utilities.handleError(viewController, error: error, withTitle: "Deleting File") } } } @@ -84,7 +84,7 @@ class HomeViewFileHandlers { } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(in: viewController, error: error, withTitle: "Unzipping File") + viewController.utilities.handleError(viewController, error: error, withTitle: "Unzipping File") } } } From 0f2fad9a6790225881119d7eb2d396591fb69602 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 17:10:37 -0400 Subject: [PATCH 204/391] Update HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 107 +++++++++++++++++++------ 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift index 750e597c..62845378 100644 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -1,37 +1,98 @@ import UIKit +import ZIPFoundation -class HomeViewUtilities { +class HomeViewFileHandlers { + private let fileManager = FileManager.default + private let utilities = HomeViewUtilities() - // Method to present a simple alert - func presentAlert(in viewController: HomeViewController, title: String, message: String, buttonTitle: String = "OK", handler: ((UIAlertAction) -> Void)? = nil) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - let action = UIAlertAction(title: buttonTitle, style: .default, handler: handler) - alert.addAction(action) + func uploadFile(viewController: UIViewController) { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = viewController as? UIDocumentPickerDelegate + documentPicker.modalPresentationStyle = .formSheet + viewController.present(documentPicker, animated: true, completion: nil) + } + + func importFile(viewController: UIViewController) { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = viewController as? UIDocumentPickerDelegate + documentPicker.modalPresentationStyle = .formSheet + viewController.present(documentPicker, animated: true, completion: nil) + } - if handler != nil { - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + func createNewFolder(viewController: HomeViewController, folderName: String) { + let folderURL = viewController.documentsDirectory.appendingPathComponent(folderName) + do { + try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) + viewController.loadFiles() + } catch { + utilities.handleError(in: viewController, error: error, withTitle: "Creating Folder") } + } - viewController.present(alert, animated: true, completion: nil) + func createNewFile(viewController: HomeViewController, fileName: String) { + let fileURL = viewController.documentsDirectory.appendingPathComponent(fileName) + fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + viewController.loadFiles() } - // Method to show an input alert - func showInputAlert(in viewController: HomeViewController, title: String, message: String, actionTitle: String, initialText: String = "", completion: @escaping (String) -> Void) { - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = initialText + func renameFile(viewController: HomeViewController, fileURL: URL, newName: String) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + viewController.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + } + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + self.utilities.handleError(in: viewController, error: error, withTitle: "Renaming File") + } + } } - let confirmAction = UIAlertAction(title: actionTitle, style: .default) { [weak alertController] _ in - guard let textField = alertController?.textFields?.first, let text = textField.text else { return } - completion(text) + } + + func deleteFile(viewController: HomeViewController, fileURL: URL) { + viewController.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.removeItem(at: fileURL) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + } + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + self.utilities.handleError(in: viewController, error: error, withTitle: "Deleting File") + } + } + } + } + + func unzipFile(viewController: HomeViewController, fileURL: URL) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + viewController.activityIndicator.startAnimating() + DispatchQueue.global().async { + do { + try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + } + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + self.utilities.handleError(in: viewController, error: error, withTitle: "Unzipping File") + } + } } - alertController.addAction(confirmAction) - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - viewController.present(alertController, animated: true, completion: nil) } - // Method to handle errors - func handleError(in viewController: HomeViewController, error: Error, withTitle title: String = "Error") { - presentAlert(in: viewController, title: title, message: "An error occurred: \(error.localizedDescription)") + func shareFile(viewController: UIViewController, fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + viewController.present(activityController, animated: true, completion: nil) } } \ No newline at end of file From 87153189d779f53cb7d9f9d61705960c175f99e8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 17:24:59 -0400 Subject: [PATCH 205/391] Update SigningsDylibViewController.swift --- .../SigningsDylibViewController.swift | 84 ++++++++++++++----- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift index 909dce32..2ca47d90 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift @@ -9,7 +9,7 @@ class SigningsDylibViewController: UITableViewController { self.mainOptions.mainOptions.removeInjectPaths = self.dylibstoremove } } - + var mainOptions: SigningMainDataWrapper init(mainOptions: SigningMainDataWrapper, app: URL) { @@ -18,8 +18,8 @@ class SigningsDylibViewController: UITableViewController { super.init(style: .insetGrouped) do { - if let balls = try TweakHandler.findExecutable(at: applicationPath) { - if let dylibs = listDylibs(filePath: balls.path) { + if let executable = try TweakHandler.findExecutable(at: applicationPath) { + if let dylibs = try listDylibs(filePath: executable.path) { groupDylibs(dylibs) } else { print("Failed to list dylibs") @@ -47,13 +47,13 @@ class SigningsDylibViewController: UITableViewController { self.tableView.dataSource = self self.tableView.delegate = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: "dylibCell") - - let alertController = UIAlertController(title: "ADVANCED USERS ONLY", message: "This section can make installed applications UNUSABLE and potentially UNSTABLE. USE THIS SECTION WITH CAUTION, IF YOU PROCEED.", preferredStyle: .alert) - + + let alertController = UIAlertController(title: "ADVANCED USERS ONLY", message: "This section can make installed applications UNUSABLE and potentially UNSTABLE. USE THIS SECTION WITH CAUTION.", preferredStyle: .alert) + let continueAction = UIAlertAction(title: "WHO CARES", style: .destructive, handler: nil) - + alertController.addAction(continueAction) - + present(alertController, animated: true, completion: nil) } @@ -62,16 +62,16 @@ class SigningsDylibViewController: UITableViewController { } fileprivate func groupDylibs(_ dylibs: [String]) { - groupedDylibs["@rpath"] = dylibs.filter { $0.hasPrefix("@rpath") } - groupedDylibs["@executable_path"] = dylibs.filter { $0.hasPrefix("@executable_path") } - groupedDylibs["/usr/lib"] = dylibs.filter { $0.hasPrefix("/usr/lib") } - groupedDylibs["/System/Library"] = dylibs.filter { $0.hasPrefix("/System/Library") } + groupedDylibs["@rpath"] = dylibs.filter { $0.hasPrefix("@rpath") }.sorted() + groupedDylibs["@executable_path"] = dylibs.filter { $0.hasPrefix("@executable_path") }.sorted() + groupedDylibs["/usr/lib"] = dylibs.filter { $0.hasPrefix("/usr/lib") }.sorted() + groupedDylibs["/System/Library"] = dylibs.filter { $0.hasPrefix("/System/Library") }.sorted() groupedDylibs["Other"] = dylibs.filter { dylib in !dylib.hasPrefix("@rpath") && !dylib.hasPrefix("@executable_path") && !dylib.hasPrefix("/usr/lib") && !dylib.hasPrefix("/System/Library") - } + }.sorted() } override func numberOfSections(in tableView: UITableView) -> Int { @@ -97,16 +97,58 @@ class SigningsDylibViewController: UITableViewController { return cell } - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == .delete { - let key = dylibSections[indexPath.section] - if let dylib = groupedDylibs[key]?[indexPath.row] { - if !dylibstoremove.contains(dylib) { - dylibstoremove.append(dylib) + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let key = dylibSections[indexPath.section] + if let dylib = groupedDylibs[key]?[indexPath.row] { + if dylibstoremove.contains(dylib) { + if let index = dylibstoremove.firstIndex(of: dylib) { + dylibstoremove.remove(at: index) } - tableView.reloadRows(at: [indexPath], with: .automatic) - print(dylibstoremove) + } else { + dylibstoremove.append(dylib) } + tableView.reloadRows(at: [indexPath], with: .automatic) + print(dylibstoremove) + } + tableView.deselectRow(at: indexPath, animated: true) + } + + // Add the missing functions here + func listDylibs(filePath: String) throws -> [String]? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/bin/otool") + task.arguments = ["-L", filePath] + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = Pipe() // Capture standard error too + + try task.run() + task.waitUntilExit() + + if task.terminationStatus == 0 { + let data = pipe.fileHandleForReading.readDataToEndOfFile() + if let output = String(data: data, encoding: .utf8) { + let lines = output.components(separatedBy: .newlines) + var dylibs: [String] = [] + + for line in lines { + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + if trimmedLine.hasPrefix("\t") { + if let dylib = trimmedLine.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) { + dylibs.append(dylib) + } + } + } + return dylibs + } + } else { + let errorData = task.standardError as! Pipe + let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) + print("otool error: \(errorString ?? "Unknown error")") + throw NSError(domain: "otool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "otool failed with status \(task.terminationStatus)"]) + } + return nil } } \ No newline at end of file From b058ce0d3794d58f023b9989c3c6bfc73283687a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 17:30:47 -0400 Subject: [PATCH 206/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 44490ae0..62845378 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -3,6 +3,7 @@ import ZIPFoundation class HomeViewFileHandlers { private let fileManager = FileManager.default + private let utilities = HomeViewUtilities() func uploadFile(viewController: UIViewController) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) @@ -24,7 +25,7 @@ class HomeViewFileHandlers { try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) viewController.loadFiles() } catch { - viewController.utilities.handleError(viewController, error: error, withTitle: "Creating Folder") + utilities.handleError(in: viewController, error: error, withTitle: "Creating Folder") } } @@ -47,7 +48,7 @@ class HomeViewFileHandlers { } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(viewController, error: error, withTitle: "Renaming File") + self.utilities.handleError(in: viewController, error: error, withTitle: "Renaming File") } } } @@ -65,7 +66,7 @@ class HomeViewFileHandlers { } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(viewController, error: error, withTitle: "Deleting File") + self.utilities.handleError(in: viewController, error: error, withTitle: "Deleting File") } } } @@ -84,7 +85,7 @@ class HomeViewFileHandlers { } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - viewController.utilities.handleError(viewController, error: error, withTitle: "Unzipping File") + self.utilities.handleError(in: viewController, error: error, withTitle: "Unzipping File") } } } From e337bf8504ee1466c5eb2fe673023cce73f7446c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 17:50:48 -0400 Subject: [PATCH 207/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 198 ++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 62845378..c0123c41 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -5,6 +5,8 @@ class HomeViewFileHandlers { private let fileManager = FileManager.default private let utilities = HomeViewUtilities() + // Existing functions... + func uploadFile(viewController: UIViewController) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = viewController as? UIDocumentPickerDelegate @@ -95,4 +97,200 @@ class HomeViewFileHandlers { let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) viewController.present(activityController, animated: true, completion: nil) } + + // New functions + + func listDylibs(filePath: String) throws -> [String]? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/bin/otool") + task.arguments = ["-L", filePath] + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = Pipe() // Capture standard error too + + try task.run() + task.waitUntilExit() + + if task.terminationStatus == 0 { + let data = pipe.fileHandleForReading.readDataToEndOfFile() + if let output = String(data: data, encoding: .utf8) { + let lines = output.components(separatedBy: .newlines) + var dylibs: [String] = [] + + for line in lines { + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + if trimmedLine.hasPrefix("\t") { + if let dylib = trimmedLine.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) { + dylibs.append(dylib) + } + } + } + return dylibs + } + } else { + let errorData = task.standardError as! Pipe + let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) + print("otool error: \(errorString ?? "Unknown error")") + throw NSError(domain: "otool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "otool failed with status \(task.terminationStatus)"]) + } + return nil + } + + func fetchSources() { + sources = CoreDataManager.shared.getAZSources() + searchResultsTableViewController.sources = sources + DispatchQueue.main.async { + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + } + } + + func uninstallDylibs(filePath: String, dylibPaths: [String]) throws { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/bin/otool") + task.arguments = ["-L", filePath] + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = Pipe() // Capture standard error too + + try task.run() + task.waitUntilExit() + + if task.terminationStatus == 0 { + let data = pipe.fileHandleForReading.readDataToEndOfFile() + if let output = String(data: data, encoding: .utf8) { + let lines = output.components(separatedBy: .newlines) + var dylibs: [String] = [] + + for line in lines { + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + if trimmedLine.hasPrefix("\t") { + if let dylib = trimmedLine.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) { + dylibs.append(dylib) + } + } + } + return dylibs + } + } else { + let errorData = task.standardError as! Pipe + let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) + print("otool error: \(errorString ?? "Unknown error")") + throw NSError(domain: "otool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "otool failed with status \(task.terminationStatus)"]) + } + return nil + } + + func updatePlugIns(options: SigningOptions, app: URL) throws { + let plugInsPath = app.appendingPathComponent("PlugIns") + let plugIns = try fileManager.contentsOfDirectory(atPath: plugInsPath.path) + + for plugIn in plugIns { + let plugInPath = plugInsPath.appendingPathComponent(plugIn) + let infoPlistPath = plugInPath.appendingPathComponent("Info.plist") + + if fileManager.fileExists(atPath: infoPlistPath.path) { + if let info = NSDictionary(contentsOf: infoPlistPath)?.mutableCopy() as? NSMutableDictionary { + try updateInfoPlist(infoDict: info, main: options.mainOptions, options: options, icon: options.mainOptions.iconURL, app: plugInPath) + } + } + + let handler = TweakHandler(urls: options.signingOptions.toInject, app: plugInPath) + try handler.getInputFiles() + + if !options.mainOptions.removeInjectPaths.isEmpty { + if let appexe = try? TweakHandler.findExecutable(at: plugInPath) { + _ = uninstallDylibs(filePath: appexe.path, dylibPaths: options.mainOptions.removeInjectPaths) + } + } + } + } + + func removeDumbAssPlaceHolderExtension(options: SigningOptions, app: URL) throws { + let extensionsPath = app.appendingPathComponent("Extensions") + let extensions = try fileManager.contentsOfDirectory(atPath: extensionsPath.path) + + for extensionItem in extensions { + let extensionPath = extensionsPath.appendingPathComponent(extensionItem) + + if extensionPath.lastPathComponent == "DumbAssPlaceHolderExtension.appex" { + try fileManager.removeItem(at: extensionPath) + } + } + } + + func updateMobileProvision(app: URL) throws { + let provisioningPath = app.appendingPathComponent("embedded.mobileprovision") + + if fileManager.fileExists(atPath: provisioningPath.path) { + try fileManager.removeItem(at: provisioningPath) + } + + let certPath = try CoreDataManager.shared.getCertifcatePath(source: mainOptions.mainOptions.certificate) + let provisionPath = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.provisionPath ?? "")").path + + try fileManager.copyItem(atPath: provisionPath, toPath: provisioningPath.path) + } + + func signAppWithZSign(tmpDirApp: URL, certPaths: (String, String), password: String, main: MainOptions, options: SigningOptions) throws { + let zsignCmd = "/usr/local/bin/zsign" + let args = [ + "-k", certPaths.1, + "-m", certPaths.0, + "-p", password, + "-o", tmpDirApp.path, + tmpDirApp.path + ] + + let task = Process() + task.executableURL = URL(fileURLWithPath: zsignCmd) + task.arguments = args + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = Pipe() + + try task.run() + task.waitUntilExit() + + if task.terminationStatus != 0 { + let errorData = task.standardError as! Pipe + let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) + print("zsign error: \(errorString ?? "Unknown error")") + throw NSError(domain: "zsign", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "zsign failed with status \(task.terminationStatus)"]) + } + } + + func changeDylib(oldPath: String, newPath: String, appPath: String) throws { + let task = Process() + task.launchPath = "/usr/bin/install_name_tool" + task.arguments = ["-change", oldPath, newPath, appPath] + + try task.run() + task.waitUntilExit() + + if task.terminationStatus != 0 { + let errorData = task.standardError as! Pipe + let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) + print("install_name_tool error: \(errorString ?? "Unknown error")") + throw NSError(domain: "install_name_tool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "install_name_tool failed with status \(task.terminationStatus)"]) + } + } + + func injectDylib(dylibPath: String, appPath: String) throws { + let task = Process() + task.launchPath = "/usr/bin/install_name_tool" + task.arguments = ["-add_rpath", dylibPath, appPath] + + try task.run() + task.waitUntilExit() + + if task.terminationStatus != 0 { + let errorData = task.standardError as! Pipe + let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) + print("install_name_tool error: \(errorString ?? "Unknown error")") + throw NSError(domain: "install_name_tool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "install_name_tool failed with status \(task.terminationStatus)"]) + } + } } \ No newline at end of file From 82b529345817863e4a3de7d69a93f3f59e07fa39 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 18:25:43 -0400 Subject: [PATCH 208/391] Update SettingsViewController.swift --- iOS/Views/Settings/SettingsViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/Views/Settings/SettingsViewController.swift b/iOS/Views/Settings/SettingsViewController.swift index e2993e78..4a4bc610 100644 --- a/iOS/Views/Settings/SettingsViewController.swift +++ b/iOS/Views/Settings/SettingsViewController.swift @@ -89,7 +89,7 @@ extension SettingsViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let reuseIdentifier = "Cell" - var cell = UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifier) + let cell = UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifier) cell.accessoryType = .none cell.selectionStyle = .none @@ -222,4 +222,4 @@ extension SettingsViewController { } } } -} +} \ No newline at end of file From 3c094e9945acaa67d5c2a9f3d185ac970098db61 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 18:29:10 -0400 Subject: [PATCH 209/391] Update SigningsDylibViewController.swift --- .../SigningViewController/SigningsDylibViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift index 2ca47d90..7cde502a 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift @@ -147,7 +147,6 @@ class SigningsDylibViewController: UITableViewController { let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) print("otool error: \(errorString ?? "Unknown error")") throw NSError(domain: "otool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "otool failed with status \(task.terminationStatus)"]) - } return nil } From 26c078cf3feeaad0beb1dd5926640d4e215a58e1 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 18:31:53 -0400 Subject: [PATCH 210/391] Update SigningsViewController.swift --- .../SigningsViewController.swift | 815 +++++++++--------- 1 file changed, 407 insertions(+), 408 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift index 983cb819..18e47b0e 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift @@ -9,425 +9,424 @@ import UIKit import CoreData struct BundleOptions { - var name: String? - var bundleId: String? - var version: String? - var sourceURL: URL? + var name: String? + var bundleId: String? + var version: String? + var sourceURL: URL? } class SigningsViewController: UIViewController { - - var tableData = [ - [ - "AppIcon", - String.localized("APPS_INFORMATION_TITLE_NAME"), - String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"), - String.localized("APPS_INFORMATION_TITLE_VERSION"), - ], - [ - "Signing", - ], - [ - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), - ], - [ String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES") ], - ] + + var tableData = [ + [ + "AppIcon", + String.localized("APPS_INFORMATION_TITLE_NAME"), + String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"), + String.localized("APPS_INFORMATION_TITLE_VERSION"), + ], + [ + "Signing", + ], + [ + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), + ], + [ String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES") ], + ] - var sectionTitles = [ - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_CUSTOMIZATION"), - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_SIGNING"), - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_ADVANCED"), - "", - ] - - public var application: NSManagedObject? - private var appsViewController: LibraryViewController? - - var signingDataWrapper: SigningDataWrapper - var mainOptions = SigningMainDataWrapper(mainOptions: MainSigningOptions()) - - var bundle: BundleOptions? - - var tableView: UITableView! - private var variableBlurView: UIVariableBlurView? - private var largeButton = ActivityIndicatorButton() - private var iconCell = IconImageViewCell() - var signingCompletionHandler: ((Bool) -> Void)? - - init(signingDataWrapper: SigningDataWrapper, application: NSManagedObject, appsViewController: LibraryViewController) { - self.signingDataWrapper = signingDataWrapper - self.application = application - self.appsViewController = appsViewController - super.init(nibName: nil, bundle: nil) - - if let name = application.value(forKey: "name") as? String, - let bundleId = application.value(forKey: "bundleidentifier") as? String, - let version = application.value(forKey: "version") as? String { - let sourceLocation = application.value(forKey: "oSU") as? String - let sourceURL = sourceLocation != nil ? URL(string: sourceLocation!) : nil - self.bundle = BundleOptions( - name: name, - bundleId: bundleId, - version: version, - sourceURL: sourceURL - ) - } - - if let hasGotCert = CoreDataManager.shared.getCurrentCertificate() { self.mainOptions.mainOptions.certificate = hasGotCert } - if let uuid = application.value(forKey: "uuid") as? String { self.mainOptions.mainOptions.uuid = uuid } - - if signingDataWrapper.signingOptions.ppqCheckProtection && - mainOptions.mainOptions.certificate?.certData?.pPQCheck == true { - - if !signingDataWrapper.signingOptions.dynamicProtection { - mainOptions.mainOptions.bundleId = (bundle?.bundleId)!+"."+Preferences.pPQCheckString - } - } - - if let currentBundleId = bundle?.bundleId, - let newBundleId = signingDataWrapper.signingOptions.bundleIdConfig[currentBundleId] { - mainOptions.mainOptions.bundleId = newBundleId - } - - if let currentName = bundle?.name, - let newName = signingDataWrapper.signingOptions.displayNameConfig[currentName] { - mainOptions.mainOptions.name = newName - } - - if signingDataWrapper.signingOptions.dynamicProtection { - Task { - await checkDynamicProtection() - } - } - } - - private func checkDynamicProtection() async { - guard signingDataWrapper.signingOptions.ppqCheckProtection, - mainOptions.mainOptions.certificate?.certData?.pPQCheck == true, - let bundleId = bundle?.bundleId else { - return - } - - let shouldModify = await BundleIdChecker.shouldModifyBundleId(originalBundleId: bundleId) - if shouldModify { - mainOptions.mainOptions.bundleId = bundleId+"."+Preferences.pPQCheckString - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - setupNavigation() - setupViews() - setupToolbar() - #if !targetEnvironment(simulator) - certAlert() - #endif - - let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) - swipeLeft.direction = .left - let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) - swipeRight.direction = .right - tableView.addGestureRecognizer(swipeLeft) - tableView.addGestureRecognizer(swipeRight) - NotificationCenter.default.addObserver(self, selector: #selector(fetch), name: Notification.Name("reloadSigningController"), object: nil) - } - - deinit { - NotificationCenter.default.removeObserver(self, name: Notification.Name("reloadSigningController"), object: nil) - } - - @objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) { - let location = gesture.location(in: tableView) - if let indexPath = tableView.indexPathForRow(at: location), - indexPath.section == 1 && indexPath.row == 0 { - let certificates = CoreDataManager.shared.getDatedCertificate() - guard certificates.count > 1 else { return } - - let currentIndex = certificates.firstIndex { $0 == mainOptions.mainOptions.certificate } ?? 0 - var newIndex = currentIndex - - switch gesture.direction { - case .left: - newIndex = (currentIndex + 1) % certificates.count - case .right: - newIndex = (currentIndex - 1 + certificates.count) % certificates.count - default: - break - } - - let feedbackGenerator = UISelectionFeedbackGenerator() - feedbackGenerator.prepare() - feedbackGenerator.selectionChanged() - - Preferences.selectedCert = newIndex - mainOptions.mainOptions.certificate = certificates[newIndex] - tableView.reloadRows(at: [indexPath], with: gesture.direction == .left ? .left : .right) - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - self.tableView.reloadData() - } - - fileprivate func setupNavigation() { - let logoImageView = UIImageView(image: UIImage(named: "feather_glyph")) - logoImageView.contentMode = .scaleAspectFit - navigationItem.titleView = logoImageView - self.navigationController?.navigationBar.prefersLargeTitles = false - - self.isModalInPresentation = true - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: String.localized("DISMISS"), style: .done, target: self, action: #selector(closeSheet)) - } - - fileprivate func setupViews() { - self.tableView = UITableView(frame: .zero, style: .insetGrouped) - self.tableView.translatesAutoresizingMaskIntoConstraints = false - self.tableView.dataSource = self - self.tableView.delegate = self - self.tableView.showsHorizontalScrollIndicator = false - self.tableView.showsVerticalScrollIndicator = false - self.tableView.contentInset.bottom = 70 - - self.view.addSubview(tableView) - self.tableView.constraintCompletely(to: view) - } - - fileprivate func setupToolbar() { - largeButton.translatesAutoresizingMaskIntoConstraints = false - largeButton.addTarget(self, action: #selector(startSign), for: .touchUpInside) - - let gradientMask = VariableBlurViewConstants.defaultGradientMask - variableBlurView = UIVariableBlurView(frame: .zero) - variableBlurView?.gradientMask = gradientMask - variableBlurView?.transform = CGAffineTransform(rotationAngle: CGFloat.pi) - variableBlurView?.translatesAutoresizingMaskIntoConstraints = false - - view.addSubview(variableBlurView!) - view.addSubview(largeButton) - - var height = 80.0 - if UIDevice.current.userInterfaceIdiom == .pad { height = 65.0 } - - NSLayoutConstraint.activate([ - variableBlurView!.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), - variableBlurView!.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), - variableBlurView!.bottomAnchor.constraint(equalTo: view.bottomAnchor), - variableBlurView!.heightAnchor.constraint(equalToConstant: height), - - largeButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), - largeButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), - largeButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -17), - largeButton.heightAnchor.constraint(equalToConstant: 50) - ]) - - variableBlurView?.layer.zPosition = 3 - largeButton.layer.zPosition = 4 - } - - fileprivate func certAlert() { - if (mainOptions.mainOptions.certificate == nil) { - DispatchQueue.main.async { - let alert = UIAlertController( - title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), - message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default) { _ in - self.dismiss(animated: true) - } - ) - self.present(alert, animated: true, completion: nil) - } - } - } - - @objc func closeSheet() { - dismiss(animated: true, completion: nil) - } - - @objc func fetch() { - self.tableView.reloadData() - } - - @objc func startSign() { - self.navigationItem.leftBarButtonItem = nil - largeButton.showLoadingIndicator() - signInitialApp( - bundle: bundle!, - mainOptions: mainOptions, - signingOptions: signingDataWrapper, - appPath:getFilesForDownloadedApps(app: application as! DownloadedApps, getuuidonly: false)) - { result in - switch result { - case .success(let (signedPath, signedApp)): - self.appsViewController?.fetchSources() - self.appsViewController?.tableView.reloadData() - Debug.shared.log(message: signedPath.path) - if self.signingDataWrapper.signingOptions.installAfterSigned { - self.appsViewController?.startInstallProcess(meow: signedApp, filePath: signedPath.path) - self.signingCompletionHandler?(true) - } + var sectionTitles = [ + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_CUSTOMIZATION"), + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_SIGNING"), + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_ADVANCED"), + "", + ] + + public var application: NSManagedObject? + private var appsViewController: LibraryViewController? + + var signingDataWrapper: SigningDataWrapper + var mainOptions = SigningMainDataWrapper(mainOptions: MainSigningOptions()) + + var bundle: BundleOptions? + + var tableView: UITableView! + private var variableBlurView: UIVariableBlurView? + private var largeButton = ActivityIndicatorButton() + private var iconCell = IconImageViewCell() + var signingCompletionHandler: ((Bool) -> Void)? + + init(signingDataWrapper: SigningDataWrapper, application: NSManagedObject, appsViewController: LibraryViewController) { + self.signingDataWrapper = signingDataWrapper + self.application = application + self.appsViewController = appsViewController + super.init(nibName: nil, bundle: nil) + + if let name = application.value(forKey: "name") as? String, + let bundleId = application.value(forKey: "bundleidentifier") as? String, + let version = application.value(forKey: "version") as? String { + let sourceLocation = application.value(forKey: "oSU") as? String + let sourceURL = sourceLocation != nil ? URL(string: sourceLocation!) : nil + self.bundle = BundleOptions( + name: name, + bundleId: bundleId, + version: version, + sourceURL: sourceURL + ) + } + + if let hasGotCert = CoreDataManager.shared.getCurrentCertificate() { self.mainOptions.mainOptions.certificate = hasGotCert } + if let uuid = application.value(forKey: "uuid") as? String { self.mainOptions.mainOptions.uuid = uuid } + + if signingDataWrapper.signingOptions.ppqCheckProtection && + mainOptions.mainOptions.certificate?.certData?.pPQCheck == true { + + if !signingDataWrapper.signingOptions.dynamicProtection { + mainOptions.mainOptions.bundleId = (bundle?.bundleId)!+"."+Preferences.pPQCheckString + } + } + + if let currentBundleId = bundle?.bundleId, + let newBundleId = signingDataWrapper.signingOptions.bundleIdConfig[currentBundleId] { + mainOptions.mainOptions.bundleId = newBundleId + } + + if let currentName = bundle?.name, + let newName = signingDataWrapper.signingOptions.displayNameConfig[currentName] { + mainOptions.mainOptions.name = newName + } + + if signingDataWrapper.signingOptions.dynamicProtection { + Task { + await checkDynamicProtection() + } + } + } + + private func checkDynamicProtection() async { + guard signingDataWrapper.signingOptions.ppqCheckProtection, + mainOptions.mainOptions.certificate?.certData?.pPQCheck == true, + let bundleId = bundle?.bundleId else { + return + } + + let shouldModify = await BundleIdChecker.shouldModifyBundleId(originalBundleId: bundleId) + if shouldModify { + mainOptions.mainOptions.bundleId = bundleId+"."+Preferences.pPQCheckString + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupNavigation() + setupViews() + setupToolbar() + #if !targetEnvironment(simulator) + certAlert() + #endif + + let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) + swipeLeft.direction = .left + let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) + swipeRight.direction = .right + tableView.addGestureRecognizer(swipeLeft) + tableView.addGestureRecognizer(swipeRight) + NotificationCenter.default.addObserver(self, selector: #selector(fetch), name: Notification.Name("reloadSigningController"), object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self, name: Notification.Name("reloadSigningController"), object: nil) + } + + @objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) { + let location = gesture.location(in: tableView) + if let indexPath = tableView.indexPathForRow(at: location), + indexPath.section == 1 && indexPath.row == 0 { + let certificates = CoreDataManager.shared.getDatedCertificate() + guard certificates.count > 1 else { return } + + let currentIndex = certificates.firstIndex { $0 == mainOptions.mainOptions.certificate } ?? 0 + var newIndex = currentIndex + + switch gesture.direction { + case .left: + newIndex = (currentIndex + 1) % certificates.count + case .right: + newIndex = (currentIndex - 1 + certificates.count) % certificates.count + default: + break + } + + let feedbackGenerator = UISelectionFeedbackGenerator() + feedbackGenerator.prepare() + feedbackGenerator.selectionChanged() + + Preferences.selectedCert = newIndex + mainOptions.mainOptions.certificate = certificates[newIndex] + tableView.reloadRows(at: [indexPath], with: gesture.direction == .left ? .left : .right) + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.tableView.reloadData() + } + + fileprivate func setupNavigation() { + let logoImageView = UIImageView(image: UIImage(named: "feather_glyph")) + logoImageView.contentMode = .scaleAspectFit + navigationItem.titleView = logoImageView + self.navigationController?.navigationBar.prefersLargeTitles = false + + self.isModalInPresentation = true + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: String.localized("DISMISS"), style: .done, target: self, action: #selector(closeSheet)) + } + + fileprivate func setupViews() { + self.tableView = UITableView(frame: .zero, style: .insetGrouped) + self.tableView.translatesAutoresizingMaskIntoConstraints = false + self.tableView.dataSource = self + self.tableView.delegate = self + self.tableView.showsHorizontalScrollIndicator = false + self.tableView.showsVerticalScrollIndicator = false + self.tableView.contentInset.bottom = 70 + + self.view.addSubview(tableView) + self.tableView.constraintCompletely(to: view) + } + + fileprivate func setupToolbar() { + largeButton.translatesAutoresizingMaskIntoConstraints = false + largeButton.addTarget(self, action: #selector(startSign), for: .touchUpInside) + + let gradientMask = VariableBlurViewConstants.defaultGradientMask + variableBlurView = UIVariableBlurView(frame: .zero) + variableBlurView?.gradientMask = gradientMask + variableBlurView?.transform = CGAffineTransform(rotationAngle: CGFloat.pi) + variableBlurView?.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(variableBlurView!) + view.addSubview(largeButton) + + var height = 80.0 + if UIDevice.current.userInterfaceIdiom == .pad { height = 65.0 } + + NSLayoutConstraint.activate([ + variableBlurView!.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + variableBlurView!.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + variableBlurView!.bottomAnchor.constraint(equalTo: view.bottomAnchor), + variableBlurView!.heightAnchor.constraint(equalToConstant: height), + + largeButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), + largeButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), + largeButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -17), + largeButton.heightAnchor.constraint(equalToConstant: 50) + ]) + + variableBlurView?.layer.zPosition = 3 + largeButton.layer.zPosition = 4 + } + + fileprivate func certAlert() { + if (mainOptions.mainOptions.certificate == nil) { + DispatchQueue.main.async { + let alert = UIAlertController( + title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), + message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default) { _ in + self.dismiss(animated: true) + } + ) + self.present(alert, animated: true, completion: nil) + } + } + } + + @objc func closeSheet() { + dismiss(animated: true, completion: nil) + } + + @objc func fetch() { + self.tableView.reloadData() + } + + @objc func startSign() { + self.navigationItem.leftBarButtonItem = nil + largeButton.showLoadingIndicator() + signInitialApp( + bundle: bundle!, + mainOptions: mainOptions, + signingOptions: signingDataWrapper, + appPath:getFilesForDownloadedApps(app: application as! DownloadedApps, getuuidonly: false)) + { result in + switch result { + case .success(let (signedPath, signedApp)): + self.appsViewController?.fetchSources() + self.appsViewController?.tableView.reloadData() + Debug.shared.log(message: signedPath.path) + if self.signingDataWrapper.signingOptions.installAfterSigned { + self.appsViewController?.startInstallProcess(meow: signedApp, filePath: signedPath.path) + self.signingCompletionHandler?(true) + } - case .failure(let error): - Debug.shared.log(message: "Signing failed: \(error.localizedDescription)", type: .error) - self.signingCompletionHandler?(false) - } - - self.dismiss(animated: true) - } - } + case .failure(let error): + Debug.shared.log(message: "Signing failed: \(error.localizedDescription)", type: .error) + self.signingCompletionHandler?(false) + } + + self.dismiss(animated: true) + } + } } extension SigningsViewController: UITableViewDataSource, UITableViewDelegate { - func numberOfSections(in tableView: UITableView) -> Int { return sectionTitles.count } - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableData[section].count } - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionTitles[section] } - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return sectionTitles[section].isEmpty ? 0 : 40 } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let title = sectionTitles[section] - let headerView = InsetGroupedSectionHeader(title: title) - return headerView - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let reuseIdentifier = "Cell" - let cell = UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifier) - cell.accessoryType = .none - cell.selectionStyle = .gray - - let cellText = tableData[indexPath.section][indexPath.row] - cell.textLabel?.text = cellText - - switch cellText { - case "AppIcon": - let cell = iconCell - - if (mainOptions.mainOptions.iconURL != nil) { - cell.configure(with: mainOptions.mainOptions.iconURL) - } else { - cell.configure(with: CoreDataManager.shared.loadImage(from: getIconURL(for: application as! DownloadedApps))) - } - - cell.accessoryType = .disclosureIndicator - return cell - case String.localized("APPS_INFORMATION_TITLE_NAME"): - cell.textLabel?.text = String.localized("APPS_INFORMATION_TITLE_NAME") - cell.detailTextLabel?.text = mainOptions.mainOptions.name ?? bundle?.name - cell.accessoryType = .disclosureIndicator - case String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"): - cell.textLabel?.text = String.localized("APPS_INFORMATION_TITLE_IDENTIFIER") - cell.detailTextLabel?.text = mainOptions.mainOptions.bundleId ?? bundle?.bundleId - cell.accessoryType = .disclosureIndicator - case String.localized("APPS_INFORMATION_TITLE_VERSION"): - cell.detailTextLabel?.text = mainOptions.mainOptions.version ?? bundle?.version - cell.accessoryType = .disclosureIndicator - case "Signing": - if let hasGotCert = mainOptions.mainOptions.certificate { - let cell = CertificateViewTableViewCell() - cell.configure(with: hasGotCert, isSelected: false) - cell.selectionStyle = .none - return cell - } else { - cell.textLabel?.text = String.localized("SETTINGS_VIEW_CONTROLLER_CELL_CURRENT_CERTIFICATE_NOSELECTED") - cell.textLabel?.textColor = .secondaryLabel - cell.selectionStyle = .none - } - case "Change Certificate", String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES"): - cell.accessoryType = .disclosureIndicator - default: - break - } + func numberOfSections(in tableView: UITableView) -> Int { return sectionTitles.count } + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableData[section].count } + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionTitles[section] } + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return sectionTitles[section].isEmpty ? 0 : 40 } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let title = sectionTitles[section] + let headerView = InsetGroupedSectionHeader(title: title) + return headerView + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let reuseIdentifier = "Cell" + let cell = UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifier) + cell.accessoryType = .none + cell.selectionStyle = .gray + + let cellText = tableData[indexPath.section][indexPath.row] + cell.textLabel?.text = cellText + + switch cellText { + case "AppIcon": + let cell = iconCell + + if (mainOptions.mainOptions.iconURL != nil) { + cell.configure(with: mainOptions.mainOptions.iconURL) + } else { + cell.configure(with: CoreDataManager.shared.loadImage(from: getIconURL(for: application as! DownloadedApps))) + } + + cell.accessoryType = .disclosureIndicator + return cell + case String.localized("APPS_INFORMATION_TITLE_NAME"): + cell.textLabel?.text = String.localized("APPS_INFORMATION_TITLE_NAME") + cell.detailTextLabel?.text = mainOptions.mainOptions.name ?? bundle?.name + cell.accessoryType = .disclosureIndicator + case String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"): + cell.textLabel?.text = String.localized("APPS_INFORMATION_TITLE_IDENTIFIER") + cell.detailTextLabel?.text = mainOptions.mainOptions.bundleId ?? bundle?.bundleId + cell.accessoryType = .disclosureIndicator + case String.localized("APPS_INFORMATION_TITLE_VERSION"): + cell.detailTextLabel?.text = mainOptions.mainOptions.version ?? bundle?.version + cell.accessoryType = .disclosureIndicator + case "Signing": + if let hasGotCert = mainOptions.mainOptions.certificate { + let cell = CertificateViewTableViewCell() + cell.configure(with: hasGotCert, isSelected: false) + cell.selectionStyle = .none + return cell + } else { + cell.textLabel?.text = String.localized("SETTINGS_VIEW_CONTROLLER_CELL_CURRENT_CERTIFICATE_NOSELECTED") + cell.textLabel?.textColor = .secondaryLabel + cell.selectionStyle = .none + } + case "Change Certificate", String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES"): + cell.accessoryType = .disclosureIndicator + default: + break + } + + return cell + } - - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let itemTapped = tableData[indexPath.section][indexPath.row] - switch itemTapped { - case "AppIcon": - importAppIconFile() - case String.localized("APPS_INFORMATION_TITLE_NAME"): - - let l = SigningsInputViewController( - parentView: self, - initialValue: (mainOptions.mainOptions.name ?? bundle?.name)!, - valueToSaveTo: indexPath.row - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"): - - let l = SigningsInputViewController( - parentView: self, - initialValue: (mainOptions.mainOptions.bundleId ?? bundle?.bundleId)!, - valueToSaveTo: indexPath.row - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APPS_INFORMATION_TITLE_VERSION"): - - let l = SigningsInputViewController( - parentView: self, - initialValue: (mainOptions.mainOptions.version ?? bundle?.version)!, - valueToSaveTo: indexPath.row - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"): - - let l = SigningsTweakViewController( - signingDataWrapper: signingDataWrapper - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"): - - let l = SigningsDylibViewController( - mainOptions: mainOptions, - app: getFilesForDownloadedApps(app: application as! DownloadedApps, getuuidonly: false) - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES"): - - let l = SigningsAdvancedViewController( - signingDataWrapper: signingDataWrapper, - mainOptions: mainOptions - ) - - navigationController?.pushViewController(l, animated: true) - - default: - break - } - - tableView.deselectRow(at: indexPath, animated: true) - } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let itemTapped = tableData[indexPath.section][indexPath.row] + switch itemTapped { + case "AppIcon": + importAppIconFile() + case String.localized("APPS_INFORMATION_TITLE_NAME"): + + let l = SigningsInputViewController( + parentView: self, + initialValue: (mainOptions.mainOptions.name ?? bundle?.name)!, + valueToSaveTo: indexPath.row + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"): + + let l = SigningsInputViewController( + parentView: self, + initialValue: (mainOptions.mainOptions.bundleId ?? bundle?.bundleId)!, + valueToSaveTo: indexPath.row + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APPS_INFORMATION_TITLE_VERSION"): + + let l = SigningsInputViewController( + parentView: self, + initialValue: (mainOptions.mainOptions.version ?? bundle?.version)!, + valueToSaveTo: indexPath.row + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"): + + let l = SigningsTweakViewController( + signingDataWrapper: signingDataWrapper + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"): + + let l = SigningsDylibViewController( + mainOptions: mainOptions, + app: getFilesForDownloadedApps(app: application as! DownloadedApps, getuuidonly: false) + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES"): + + let l = SigningsAdvancedViewController( + signingDataWrapper: signingDataWrapper, + mainOptions: mainOptions + ) + + navigationController?.pushViewController(l, animated: true) + + default: + break + } + + tableView.deselectRow(at: indexPath, animated: true) + } } // MARK: - this sucks extension SigningsViewController { - - public func getFilesForDownloadedApps(app: DownloadedApps, getuuidonly: Bool) -> URL { - return CoreDataManager.shared.getFilesForDownloadedApps(for: app, getuuidonly: getuuidonly) - } - - private func getIconURL(for app: DownloadedApps) -> URL? { - guard let iconURLString = app.value(forKey: "iconURL") as? String, - let iconURL = URL(string: iconURLString) else { - return nil - } - - let filesURL = getFilesForDownloadedApps(app: app, getuuidonly: false) - return filesURL.appendingPathComponent(iconURL.lastPathComponent) - } -} + + public func getFilesForDownloadedApps(app: DownloadedApps, getuuidonly: Bool) -> URL { + return CoreDataManager.shared.getFilesForDownloadedApps(for: app, getuuidonly: getuuidonly) + } + + private func getIconURL(for app: DownloadedApps) -> URL? { + guard let iconURLString = app.value(forKey: "iconURL") as? String, + let iconURL = URL(string: iconURLString) else { + return nil + } + + let filesURL = getFilesForDownloadedApps(app: app, getuuidonly: false) + return filesURL.appendingPathComponent(iconURL.lastPathComponent) + } +} \ No newline at end of file From f50080fee14ae0a41cd6d814bfad535f90c2dd00 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 18:40:40 -0400 Subject: [PATCH 211/391] Update AppSigner.swift --- Shared/Magic/AppSigner.swift | 245 ++++++++++++++++++++++++++++++----- 1 file changed, 209 insertions(+), 36 deletions(-) diff --git a/Shared/Magic/AppSigner.swift b/Shared/Magic/AppSigner.swift index 7202a5cc..24a3f9c9 100644 --- a/Shared/Magic/AppSigner.swift +++ b/Shared/Magic/AppSigner.swift @@ -1,3 +1,11 @@ +// +// AppSigner.swift +// feather +// +// Created by HAHALOSAH on 7/17/24. +// Copyright (c) 2024 Samara M (khcrysalis) +// + import Foundation import UIKit import AlertKit @@ -10,7 +18,7 @@ func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, let tmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) let tmpDirApp = tmpDir.appendingPathComponent(appPath.lastPathComponent) var iconURL = "" - + do { Debug.shared.log(message: "============================================") Debug.shared.log(message: "\(mainOptions.mainOptions)") @@ -32,42 +40,42 @@ func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, } else { throw NSError(domain: "AppSigner", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to read Info.plist"]) } - + let handler = TweakHandler(urls: signingOptions.signingOptions.toInject, app: tmpDirApp) try handler.getInputFiles() - + if !mainOptions.mainOptions.removeInjectPaths.isEmpty { if let appexe = try? TweakHandler.findExecutable(at: tmpDirApp) { _ = uninstallDylibs(filePath: appexe.path, dylibPaths: mainOptions.mainOptions.removeInjectPaths) } } - + try updatePlugIns(options: signingOptions, app: tmpDirApp) try removeDumbAssPlaceHolderExtension(options: signingOptions, app: tmpDirApp) try updateMobileProvision(app: tmpDirApp) - + let certPath = try CoreDataManager.shared.getCertifcatePath(source: mainOptions.mainOptions.certificate) - let provisionPath = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.provisionPath ?? "")").path - let p12Path = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.p12Path ?? "")").path - + let provisionPath = certPath.appendingPathComponent(mainOptions.mainOptions.certificate?.provisionPath ?? "").path + let p12Path = certPath.appendingPathComponent(mainOptions.mainOptions.certificate?.p12Path ?? "").path + Debug.shared.log(message: "🦋 Start Signing 🦋") - + try signAppWithZSign(tmpDirApp: tmpDirApp, certPaths: (provisionPath, p12Path), password: mainOptions.mainOptions.certificate?.password ?? "", main: mainOptions, options: signingOptions) - + Debug.shared.log(message: "🦋 End Signing 🦋") - + let signedUUID = UUID().uuidString try fileManager.createDirectory(at: getDocumentsDirectory().appendingPathComponent("Apps/Signed"), withIntermediateDirectories: true) let signedPath = getDocumentsDirectory().appendingPathComponent("Apps/Signed").appendingPathComponent(signedUUID) try fileManager.moveItem(at: tmpDir, to: signedPath) - + DispatchQueue.main.async { var signedAppObject: NSManagedObject? = nil CoreDataManager.shared.addToSignedApps( - version: (mainOptions.mainOptions.version ?? bundle.version)!, - name: (mainOptions.mainOptions.name ?? bundle.name)!, - bundleidentifier: (mainOptions.mainOptions.bundleId ?? bundle.bundleId)!, + version: mainOptions.mainOptions.version ?? bundle.version ?? "", + name: mainOptions.mainOptions.name ?? bundle.name ?? "", + bundleidentifier: mainOptions.mainOptions.bundleId ?? bundle.bundleId ?? "", iconURL: iconURL, uuid: signedUUID, appPath: appPath.lastPathComponent, @@ -75,21 +83,25 @@ func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, teamName: mainOptions.mainOptions.certificate?.certData?.name ?? "", originalSourceURL: bundle.sourceURL ) { result in - switch result { case .success(let signedApp): signedAppObject = signedApp case .failure(let error): Debug.shared.log(message: "signApp: \(error)", type: .error) completion(.failure(error)) + return + } + + Debug.shared.log(message: String.localized("SUCCESS_SIGNED", arguments: [mainOptions.mainOptions.name ?? bundle.name ?? String.localized("UNKNOWN")]), type: .success) + Debug.shared.log(message: "============================================") + + UIApplication.shared.isIdleTimerDisabled = false + if let signedAppObject = signedAppObject { + completion(.success((signedPath, signedAppObject))) + } else { + completion(.failure(NSError(domain: "AppSigner", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to create signed app object"]))) } } - - Debug.shared.log(message: String.localized("SUCCESS_SIGNED", arguments: "\((mainOptions.mainOptions.name ?? bundle.name) ?? String.localized("UNKNOWN"))"), type: .success) - Debug.shared.log(message: "============================================") - - UIApplication.shared.isIdleTimerDisabled = false - completion(.success((signedPath, signedAppObject!))) } } catch { DispatchQueue.main.async { @@ -101,36 +113,163 @@ func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, } } +func resignApp(certificate: Certificate, appPath: URL, completion: @escaping (Bool) -> Void) { + UIApplication.shared.isIdleTimerDisabled = true + DispatchQueue(label: "Resigning").async { + do { + let certPath = try CoreDataManager.shared.getCertifcatePath(source: certificate) + let provisionPath = certPath.appendingPathComponent(certificate.provisionPath ?? "").path + let p12Path = certPath.appendingPathComponent(certificate.p12Path ?? "").path + + Debug.shared.log(message: "============================================") + Debug.shared.log(message: "🦋 Start Resigning 🦋") + + try signAppWithZSign(tmpDirApp: appPath, certPaths: (provisionPath, p12Path), password: certificate.password ?? "") + + Debug.shared.log(message: "🦋 End Resigning 🦋") + DispatchQueue.main.async { + UIApplication.shared.isIdleTimerDisabled = false + Debug.shared.log(message: String.localized("SUCCESS_RESIGN"), type: .success) + completion(true) + } + Debug.shared.log(message: "============================================") + } catch { + Debug.shared.log(message: "\(error)", type: .warning) + completion(false) + } + } +} + +private func signAppWithZSign(tmpDirApp: URL, certPaths: (provisionPath: String, p12Path: String), password: String, main: SigningMainDataWrapper? = nil, options: SigningDataWrapper? = nil) throws { + if zsign(tmpDirApp.path, + certPaths.provisionPath, + certPaths.p12Path, + password, + main?.mainOptions.bundleId ?? "", + main?.mainOptions.name ?? "", + main?.mainOptions.version ?? "", + options?.signingOptions.removeProvisioningFile ?? true + ) != 0 { + throw NSError(domain: "AppSigningErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: String.localized("ERROR_ZSIGN_FAILED")]) + } +} + +func injectDylib(filePath: String, dylibPath: String, weakInject: Bool) -> Bool { + let bCreate: Bool = false + let success = InjectDyLib(filePath, dylibPath, weakInject, bCreate) + return success +} + +func changeDylib(filePath: String, oldPath: String, newPath: String) -> Bool { + let success = ChangeDylibPath(filePath, oldPath, newPath) + return success +} + +func updateMobileProvision(app: URL) throws { + let provisioningFilePath = app.appendingPathComponent("embedded.mobileprovision") + if FileManager.default.fileExists(atPath: provisioningFilePath.path) { + do { + try FileManager.default.removeItem(at: provisioningFilePath) + Debug.shared.log(message: "Embedded.mobileprovision file removed successfully!") + } catch { + throw error + } + } else { + Debug.shared.log(message: "Could not find any mobileprovision to remove. ") + } +} + +func listDylibs(filePath: String) -> [String]? { + let dylibPathsArray = NSMutableArray() + + let success = ListDylibs(filePath, dylibPathsArray) + + if success { + let dylibPaths = dylibPathsArray as! [String] + return dylibPaths + } else { + Debug.shared.log(message: "Failed to list dylibs.") + return nil + } +} + +func uninstallDylibs(filePath: String, dylibPaths: [String]) -> Bool { + return UninstallDylibs(filePath, dylibPaths) +} + +func updatePlugIns(options: SigningDataWrapper, app: URL) throws { + if options.signingOptions.removePlugins { + let fileManager = FileManager.default + let path = app.appendingPathComponent("PlugIns") + if fileManager.fileExists(atPath: path.path) { + do { + try fileManager.removeItem(at: path) + Debug.shared.log(message: "Removed PlugIns!") + } catch { + throw error + } + } else { + Debug.shared.log(message: "Could not find any PlugIns to remove.") + } + } +} + +func removeDumbAssPlaceHolderExtension(options: SigningDataWrapper, app: URL) throws { + if options.signingOptions.removeWatchPlaceHolder { + let fileManager = FileManager.default + let path = app.appendingPathComponent("com.apple.WatchPlaceholder") + if fileManager.fileExists(atPath: path.path) { + do { + try fileManager.removeItem(at: path) + Debug.shared.log(message: "Removed placeholder watch app!") + } catch { + throw error + } + } else { + Debug.shared.log(message: "Placeholder watch app not found.") + } + } +} + func updateInfoPlist(infoDict: NSMutableDictionary, main: SigningMainDataWrapper, options: SigningDataWrapper, icon: UIImage?, app: URL) throws { if let iconURL = main.mainOptions.iconURL { - let iconSizes = [60, 76, 120, 152] - var cfBundleIconFiles = [String]() + let imageSizes = [ + (width: 120, height: 120, name: "FRIcon60x60@2x.png"), + (width: 152, height: 152, name: "FRIcon76x76@2x~ipad.png") + ] - for size in iconSizes { - let fileName = "AppIcon\(size)x\(size).png" - let resizedImage = iconURL.resize(size, size) + for imageSize in imageSizes { + let resizedImage = iconURL.resize(imageSize.width, imageSize.height) let imageData = resizedImage.pngData() - let fileURL = app.appendingPathComponent(fileName) + let fileURL = app.appendingPathComponent(imageSize.name) do { try imageData?.write(to: fileURL) Debug.shared.log(message: "Saved image to: \(fileURL)") - cfBundleIconFiles.append(fileName) } catch { - Debug.shared.log(message: "Failed to save image: \(fileName), error: \(error)") + Debug.shared.log(message: "Failed to save image: \(imageSize.name), error: \(error)") throw error } } let cfBundleIcons: [String: Any] = [ "CFBundlePrimaryIcon": [ - "CFBundleIconFiles": cfBundleIconFiles, - "CFBundleIconName": "AppIcon" + "CFBundleIconFiles": ["FRIcon60x60"], + "CFBundleIconName": "FRIcon" + ] + ] + + let cfBundleIconsIpad: [String: Any] = [ + "CFBundlePrimaryIcon": [ + "CFBundleIconFiles": ["FRIcon60x60", "FRIcon76x76"], + "CFBundleIconName": "FRIcon" ] ] infoDict["CFBundleIcons"] = cfBundleIcons + infoDict["CFBundleIcons~ipad"] = cfBundleIconsIpad + } else { Debug.shared.log(message: "updateInfoPlist.updateicon: Does not include an icon, skipping!") } @@ -144,15 +283,49 @@ func updateInfoPlist(infoDict: NSMutableDictionary, main: SigningMainDataWrapper Debug.shared.log(message: "updateInfoPlist.displayName: CFBundleDisplayName not found, skipping!") } } - + if options.signingOptions.forceFileSharing { infoDict.setObject(true, forKey: "UISupportsDocumentBrowser" as NSCopying) } if options.signingOptions.forceiTunesFileSharing { infoDict.setObject(true, forKey: "UIFileSharingEnabled" as NSCopying) } if options.signingOptions.removeSupportedDevices { infoDict.removeObject(forKey: "UISupportedDevices") } if options.signingOptions.removeURLScheme { infoDict.removeObject(forKey: "CFBundleURLTypes") } - if options.signingOptions.forceProMotion { infoDict.setObject(true, forKey: "CADisableMinimumFrameDurationOnPhone" as NSCopying) } - if options.signingOptions.forceGameMode { infoDict.setObject(true, forKey: "GCSupportsGameMode" as NSCopying) } + if options.signingOptions.forceProMotion { infoDict.setObject(true, forKey: "CADisableMinimumFrameDurationOnPhone" as NSCopying)} + if options.signingOptions.forceGameMode { infoDict.setObject(true, forKey: "GCSupportsGameMode" as NSCopying)} if options.signingOptions.forceForceFullScreen { infoDict.setObject(true, forKey: "UIRequiresFullScreen" as NSCopying) } if options.signingOptions.forceMinimumVersion != "Automatic" { infoDict.setObject(options.signingOptions.forceMinimumVersion, forKey: "MinimumOSVersion" as NSCopying) } - if options.signingOptions.forceLightDarkAppearence != "Automatic" { infoDict.setObject(options.signingOptions.forceLightDarkAppearence, forKey: "UIUserInterfaceStyle" as NSCopying) } + if options.signingOptions.forceLightDarkAppearence != "Automatic" { infoDict.setObject(options.signingOptions.forceLightDarkAppearence, forKey: "UIUserInterfaceStyle" as NSCopying)} try infoDict.write(to: app.appendingPathComponent("Info.plist")) +} + +func updateLocalizedInfoPlist(in appDirectory: URL, newDisplayName: String) { + let fileManager = FileManager.default + do { + let contents = try fileManager.contentsOfDirectory(at: appDirectory, includingPropertiesForKeys: nil) + let localizationBundles = contents.filter { $0.pathExtension == "lproj" } + + guard !localizationBundles.isEmpty else { + Debug.shared.log(message: "No .lproj directories found in \(appDirectory.path), skipping!") + return + } + + for localizationBundle in localizationBundles { + let infoPlistStringsURL = localizationBundle.appendingPathComponent("InfoPlist.strings") + + if fileManager.fileExists(atPath: infoPlistStringsURL.path) { + var localizedStrings = try String(contentsOf: infoPlistStringsURL, encoding: .utf8) + let localizedDict = NSDictionary(contentsOf: infoPlistStringsURL) as? [String: String] + + if localizedDict?["CFBundleDisplayName"] != newDisplayName { + localizedStrings = localizedStrings.replacingOccurrences(of: localizedDict?["CFBundleDisplayName"] ?? "", with: newDisplayName) + try localizedStrings.write(to: infoPlistStringsURL, atomically: true, encoding: .utf8) + Debug.shared.log(message: "Updated CFBundleDisplayName in \(infoPlistStringsURL.path)") + } + } + } + } catch { + Debug.shared.log(message: "Unable to localize, skipping!", type: .debug) + } +} + +func getDocumentsDirectory() -> URL { + return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! } \ No newline at end of file From b6f27f583d1439817f409acb8eefc70d91e368a4 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 18:52:18 -0400 Subject: [PATCH 212/391] Update TweakHandler.swift --- Shared/Magic/TweakHandler.swift | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Shared/Magic/TweakHandler.swift b/Shared/Magic/TweakHandler.swift index 81066aab..981ebbc8 100644 --- a/Shared/Magic/TweakHandler.swift +++ b/Shared/Magic/TweakHandler.swift @@ -269,4 +269,65 @@ extension TweakHandler { try fileManager.moveItem(at: sourceURL, to: destinationURL) } } +} + +// MARK: - External functions +func changeDylib(filePath: String, oldPath: String, newPath: String) -> Bool { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/install_name_tool") + process.arguments = ["-change", oldPath, newPath, filePath] + + do { + try process.run() + process.waitUntilExit() + return process.terminationStatus == 0 + } catch { + print("Error changing dylib path: \(error)") + return false + } +} + +func injectDylib(filePath: String, dylibPath: String, weakInject: Bool) -> Bool { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/install_name_tool") + process.arguments = weakInject ? ["-add_rpath", dylibPath, filePath] : ["-add_rpath", dylibPath, filePath] + + do { + try process.run() + process.waitUntilExit() + return process.terminationStatus == 0 + } catch { + print("Error injecting dylib: \(error)") + return false + } +} + +func extractAR(_ data: Data) throws -> [ARFile] { + let arContainer = try ARContainer.open(container: data) + return arContainer.contents +} + +func processFile(at url: inout URL) throws { + let fileManager = FileManager.default + + switch url.pathExtension.lowercased() { + case "lzma": + let decompressedURL = url.deletingPathExtension() + try SWCompression.decompress(file: url, to: decompressedURL, compressionType: .lzma) + url = decompressedURL + case "gz": + let decompressedURL = url.deletingPathExtension() + try SWCompression.decompress(file: url, to: decompressedURL, compressionType: .gzip) + url = decompressedURL + case "xz": + let decompressedURL = url.deletingPathExtension() + try SWCompression.decompress(file: url, to: decompressedURL, compressionType: .xz) + url = decompressedURL + case "bz2": + let decompressedURL = url.deletingPathExtension() + try SWCompression.decompress(file: url, to: decompressedURL, compressionType: .bzip2) + url = decompressedURL + default: + throw FileProcessingError.unsupportedFileExtension(url.pathExtension) + } } \ No newline at end of file From f72872f234f365701dee90dfff97fe91ce1e1170 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 19:00:32 -0400 Subject: [PATCH 213/391] Update HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 55 +++++++++++++++++++------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift index 62845378..3424e186 100644 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -1,41 +1,52 @@ import UIKit import ZIPFoundation +protocol FileHandlingDelegate: AnyObject { + var documentsDirectory: URL { get } + var activityIndicator: UIActivityIndicatorView { get } + func loadFiles() + func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) +} + class HomeViewFileHandlers { private let fileManager = FileManager.default private let utilities = HomeViewUtilities() - func uploadFile(viewController: UIViewController) { + func uploadFile(viewController: UIViewController & FileHandlingDelegate) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = viewController as? UIDocumentPickerDelegate documentPicker.modalPresentationStyle = .formSheet viewController.present(documentPicker, animated: true, completion: nil) } - func importFile(viewController: UIViewController) { + func importFile(viewController: UIViewController & FileHandlingDelegate) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) documentPicker.delegate = viewController as? UIDocumentPickerDelegate documentPicker.modalPresentationStyle = .formSheet viewController.present(documentPicker, animated: true, completion: nil) } - func createNewFolder(viewController: HomeViewController, folderName: String) { + func createNewFolder(viewController: FileHandlingDelegate, folderName: String) { let folderURL = viewController.documentsDirectory.appendingPathComponent(folderName) do { try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) viewController.loadFiles() } catch { - utilities.handleError(in: viewController, error: error, withTitle: "Creating Folder") + utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Creating Folder") } } - func createNewFile(viewController: HomeViewController, fileName: String) { + func createNewFile(viewController: FileHandlingDelegate, fileName: String) { let fileURL = viewController.documentsDirectory.appendingPathComponent(fileName) - fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) - viewController.loadFiles() + if !fileManager.fileExists(atPath: fileURL.path) { + fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + viewController.loadFiles() + } else { + utilities.handleError(in: viewController as! UIViewController, error: NSError(domain: "FileExists", code: 1, userInfo: [NSLocalizedDescriptionKey: "File already exists"]), withTitle: "Creating File") + } } - func renameFile(viewController: HomeViewController, fileURL: URL, newName: String) { + func renameFile(viewController: FileHandlingDelegate, fileURL: URL, newName: String, completion: @escaping (Result) -> Void) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) viewController.activityIndicator.startAnimating() DispatchQueue.global().async { @@ -44,17 +55,19 @@ class HomeViewFileHandlers { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() viewController.loadFiles() + completion(.success(destinationURL)) } } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController, error: error, withTitle: "Renaming File") + self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Renaming File") + completion(.failure(error)) } } } } - func deleteFile(viewController: HomeViewController, fileURL: URL) { + func deleteFile(viewController: FileHandlingDelegate, fileURL: URL, completion: @escaping (Result) -> Void) { viewController.activityIndicator.startAnimating() DispatchQueue.global().async { do { @@ -62,30 +75,34 @@ class HomeViewFileHandlers { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() viewController.loadFiles() + completion(.success(())) } } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController, error: error, withTitle: "Deleting File") + self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Deleting File") + completion(.failure(error)) } } } } - func unzipFile(viewController: HomeViewController, fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + func unzipFile(viewController: FileHandlingDelegate, fileURL: URL, destinationName: String, progressHandler: ((Double) -> Void)? = nil, completion: @escaping (Result) -> Void) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(destinationName) viewController.activityIndicator.startAnimating() DispatchQueue.global().async { do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: progressHandler) DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() viewController.loadFiles() + completion(.success(destinationURL)) } } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController, error: error, withTitle: "Unzipping File") + self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Unzipping File") + completion(.failure(error)) } } } @@ -95,4 +112,12 @@ class HomeViewFileHandlers { let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) viewController.present(activityController, animated: true, completion: nil) } +} + +class HomeViewUtilities { + func handleError(in viewController: UIViewController, error: Error, withTitle title: String) { + let alert = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + viewController.present(alert, animated: true, completion: nil) + } } \ No newline at end of file From 04b7f1c6c41f86e124fee63c938906e72822bd3c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 19:04:22 -0400 Subject: [PATCH 214/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 261 ++++++---------------- 1 file changed, 73 insertions(+), 188 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index c0123c41..ffe042cf 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -1,43 +1,57 @@ import UIKit import ZIPFoundation +protocol FileHandlingDelegate: AnyObject { + var documentsDirectory: URL { get } + var activityIndicator: UIActivityIndicatorView { get } + func loadFiles() + func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) +} + class HomeViewFileHandlers { private let fileManager = FileManager.default private let utilities = HomeViewUtilities() - // Existing functions... - - func uploadFile(viewController: UIViewController) { + func uploadFile(viewController: UIViewController & UIDocumentPickerDelegate & FileHandlingDelegate) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController as? UIDocumentPickerDelegate + documentPicker.delegate = viewController documentPicker.modalPresentationStyle = .formSheet viewController.present(documentPicker, animated: true, completion: nil) } - func importFile(viewController: UIViewController) { + func importFile(viewController: UIViewController & UIDocumentPickerDelegate & FileHandlingDelegate) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController as? UIDocumentPickerDelegate + documentPicker.delegate = viewController documentPicker.modalPresentationStyle = .formSheet viewController.present(documentPicker, animated: true, completion: nil) } - func createNewFolder(viewController: HomeViewController, folderName: String) { + func createNewFolder(viewController: FileHandlingDelegate, folderName: String, completion: @escaping (Result) -> Void) { let folderURL = viewController.documentsDirectory.appendingPathComponent(folderName) do { try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) viewController.loadFiles() + completion(.success(folderURL)) } catch { - utilities.handleError(in: viewController, error: error, withTitle: "Creating Folder") + utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Creating Folder") + completion(.failure(error)) } } - func createNewFile(viewController: HomeViewController, fileName: String) { + func createNewFile(viewController: FileHandlingDelegate, fileName: String, completion: @escaping (Result) -> Void) { let fileURL = viewController.documentsDirectory.appendingPathComponent(fileName) - fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) - viewController.loadFiles() + if !fileManager.fileExists(atPath: fileURL.path) { + fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + viewController.loadFiles() + completion(.success(fileURL)) + } else { + let error = NSError(domain: "FileExists", code: 1, userInfo: [NSLocalizedDescriptionKey: "File already exists"]) + utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Creating File") + completion(.failure(error)) + } } - func renameFile(viewController: HomeViewController, fileURL: URL, newName: String) { + func renameFile(viewController: FileHandlingDelegate, fileURL: URL, newName: String, completion: @escaping (Result) -> Void) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) viewController.activityIndicator.startAnimating() DispatchQueue.global().async { @@ -46,17 +60,19 @@ class HomeViewFileHandlers { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() viewController.loadFiles() + completion(.success(destinationURL)) } } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController, error: error, withTitle: "Renaming File") + self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Renaming File") + completion(.failure(error)) } } } } - func deleteFile(viewController: HomeViewController, fileURL: URL) { + func deleteFile(viewController: FileHandlingDelegate, fileURL: URL, completion: @escaping (Result) -> Void) { viewController.activityIndicator.startAnimating() DispatchQueue.global().async { do { @@ -64,30 +80,34 @@ class HomeViewFileHandlers { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() viewController.loadFiles() + completion(.success(())) } } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController, error: error, withTitle: "Deleting File") + self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Deleting File") + completion(.failure(error)) } } } } - func unzipFile(viewController: HomeViewController, fileURL: URL) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent("extracted") + func unzipFile(viewController: FileHandlingDelegate, fileURL: URL, destinationName: String, progressHandler: ((Double) -> Void)? = nil, completion: @escaping (Result) -> Void) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(destinationName) viewController.activityIndicator.startAnimating() DispatchQueue.global().async { do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL) + try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: progressHandler) DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() viewController.loadFiles() + completion(.success(destinationURL)) } } catch { DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController, error: error, withTitle: "Unzipping File") + self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Unzipping File") + completion(.failure(error)) } } } @@ -98,16 +118,15 @@ class HomeViewFileHandlers { viewController.present(activityController, animated: true, completion: nil) } - // New functions - - func listDylibs(filePath: String) throws -> [String]? { + // Process Execution Helper + private func executeProcess(executableURL: URL, arguments: [String]) throws -> String { let task = Process() - task.executableURL = URL(fileURLWithPath: "/usr/bin/otool") - task.arguments = ["-L", filePath] + task.executableURL = executableURL + task.arguments = arguments let pipe = Pipe() task.standardOutput = pipe - task.standardError = Pipe() // Capture standard error too + task.standardError = pipe try task.run() task.waitUntilExit() @@ -115,182 +134,48 @@ class HomeViewFileHandlers { if task.terminationStatus == 0 { let data = pipe.fileHandleForReading.readDataToEndOfFile() if let output = String(data: data, encoding: .utf8) { - let lines = output.components(separatedBy: .newlines) - var dylibs: [String] = [] - - for line in lines { - let trimmedLine = line.trimmingCharacters(in: .whitespaces) - if trimmedLine.hasPrefix("\t") { - if let dylib = trimmedLine.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) { - dylibs.append(dylib) - } - } - } - return dylibs + return output + } else { + throw NSError(domain: "Process", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to decode output"]) } } else { - let errorData = task.standardError as! Pipe - let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) - print("otool error: \(errorString ?? "Unknown error")") - throw NSError(domain: "otool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "otool failed with status \(task.terminationStatus)"]) - } - return nil - } - - func fetchSources() { - sources = CoreDataManager.shared.getAZSources() - searchResultsTableViewController.sources = sources - DispatchQueue.main.async { - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - } - } - - func uninstallDylibs(filePath: String, dylibPaths: [String]) throws { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/usr/bin/otool") - task.arguments = ["-L", filePath] - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = Pipe() // Capture standard error too - - try task.run() - task.waitUntilExit() - - if task.terminationStatus == 0 { - let data = pipe.fileHandleForReading.readDataToEndOfFile() - if let output = String(data: data, encoding: .utf8) { - let lines = output.components(separatedBy: .newlines) - var dylibs: [String] = [] - - for line in lines { - let trimmedLine = line.trimmingCharacters(in: .whitespaces) - if trimmedLine.hasPrefix("\t") { - if let dylib = trimmedLine.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) { - dylibs.append(dylib) - } - } - } - return dylibs + let errorData = task.standardError.fileHandleForReading.readDataToEndOfFile() + if let errorString = String(data: errorData, encoding: .utf8) { + throw NSError(domain: "Process", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "Process failed: \(errorString)"]) + } else { + throw NSError(domain: "Process", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "Process failed with unknown error"]) } - } else { - let errorData = task.standardError as! Pipe - let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) - print("otool error: \(errorString ?? "Unknown error")") - throw NSError(domain: "otool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "otool failed with status \(task.terminationStatus)"]) } - return nil } - func updatePlugIns(options: SigningOptions, app: URL) throws { - let plugInsPath = app.appendingPathComponent("PlugIns") - let plugIns = try fileManager.contentsOfDirectory(atPath: plugInsPath.path) - - for plugIn in plugIns { - let plugInPath = plugInsPath.appendingPathComponent(plugIn) - let infoPlistPath = plugInPath.appendingPathComponent("Info.plist") - - if fileManager.fileExists(atPath: infoPlistPath.path) { - if let info = NSDictionary(contentsOf: infoPlistPath)?.mutableCopy() as? NSMutableDictionary { - try updateInfoPlist(infoDict: info, main: options.mainOptions, options: options, icon: options.mainOptions.iconURL, app: plugInPath) - } - } - - let handler = TweakHandler(urls: options.signingOptions.toInject, app: plugInPath) - try handler.getInputFiles() - - if !options.mainOptions.removeInjectPaths.isEmpty { - if let appexe = try? TweakHandler.findExecutable(at: plugInPath) { - _ = uninstallDylibs(filePath: appexe.path, dylibPaths: options.mainOptions.removeInjectPaths) + func listDylibs(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { + let otoolURL = URL(fileURLWithPath: "/usr/bin/otool") + let arguments = ["-L", filePath] + DispatchQueue.global().async { + do { + let output = try self.executeProcess(executableURL: otoolURL, arguments: arguments) + let lines = output.components(separatedBy: .newlines) + let dylibs = lines.filter { $0.hasPrefix("\t") }.compactMap { line in + line.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) } + DispatchQueue.main.async { completion(.success(dylibs)) } + } catch { + DispatchQueue.main.async { completion(.failure(error)) } } } } - func removeDumbAssPlaceHolderExtension(options: SigningOptions, app: URL) throws { - let extensionsPath = app.appendingPathComponent("Extensions") - let extensions = try fileManager.contentsOfDirectory(atPath: extensionsPath.path) - - for extensionItem in extensions { - let extensionPath = extensionsPath.appendingPathComponent(extensionItem) - - if extensionPath.lastPathComponent == "DumbAssPlaceHolderExtension.appex" { - try fileManager.removeItem(at: extensionPath) - } - } + func listDylibsFromApp(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { + listDylibs(filePath: filePath, completion: completion) } - func updateMobileProvision(app: URL) throws { - let provisioningPath = app.appendingPathComponent("embedded.mobileprovision") - - if fileManager.fileExists(atPath: provisioningPath.path) { - try fileManager.removeItem(at: provisioningPath) - } - - let certPath = try CoreDataManager.shared.getCertifcatePath(source: mainOptions.mainOptions.certificate) - let provisionPath = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.provisionPath ?? "")").path - - try fileManager.copyItem(atPath: provisionPath, toPath: provisioningPath.path) - } + // Add other functions here, using executeProcess and DispatchQueue.global().async for stability. +} - func signAppWithZSign(tmpDirApp: URL, certPaths: (String, String), password: String, main: MainOptions, options: SigningOptions) throws { - let zsignCmd = "/usr/local/bin/zsign" - let args = [ - "-k", certPaths.1, - "-m", certPaths.0, - "-p", password, - "-o", tmpDirApp.path, - tmpDirApp.path - ] - - let task = Process() - task.executableURL = URL(fileURLWithPath: zsignCmd) - task.arguments = args - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = Pipe() - - try task.run() - task.waitUntilExit() - - if task.terminationStatus != 0 { - let errorData = task.standardError as! Pipe - let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) - print("zsign error: \(errorString ?? "Unknown error")") - throw NSError(domain: "zsign", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "zsign failed with status \(task.terminationStatus)"]) - } - } - - func changeDylib(oldPath: String, newPath: String, appPath: String) throws { - let task = Process() - task.launchPath = "/usr/bin/install_name_tool" - task.arguments = ["-change", oldPath, newPath, appPath] - - try task.run() - task.waitUntilExit() - - if task.terminationStatus != 0 { - let errorData = task.standardError as! Pipe - let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) - print("install_name_tool error: \(errorString ?? "Unknown error")") - throw NSError(domain: "install_name_tool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "install_name_tool failed with status \(task.terminationStatus)"]) - } - } - - func injectDylib(dylibPath: String, appPath: String) throws { - let task = Process() - task.launchPath = "/usr/bin/install_name_tool" - task.arguments = ["-add_rpath", dylibPath, appPath] - - try task.run() - task.waitUntilExit() - - if task.terminationStatus != 0 { - let errorData = task.standardError as! Pipe - let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) - print("install_name_tool error: \(errorString ?? "Unknown error")") - throw NSError(domain: "install_name_tool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "install_name_tool failed with status \(task.terminationStatus)"]) - } +class HomeViewUtilities { + func handleError(in viewController: UIViewController, error: Error, withTitle title: String) { + let alert = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + viewController.present(alert, animated: true, completion: nil) } } \ No newline at end of file From ca9b745d889ed0fa4d57bf3e389c3328b3521c89 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 19:08:51 -0400 Subject: [PATCH 215/391] Update DownloadCertificate.swift --- Shared/Server/DownloadCertificate.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Shared/Server/DownloadCertificate.swift b/Shared/Server/DownloadCertificate.swift index df70376a..f1da3d4a 100644 --- a/Shared/Server/DownloadCertificate.swift +++ b/Shared/Server/DownloadCertificate.swift @@ -35,10 +35,4 @@ func getCertificates() { Debug.shared.log(message: "Error fetching data from \(uri): \(error.localizedDescription)", type: .error) } } -} - -func getDocumentsDirectory() -> URL { - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - let documentsDirectory = paths[0] - return documentsDirectory } \ No newline at end of file From 33cf7a0b9e5db0564161f179350666cbab05861a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 19:26:43 -0400 Subject: [PATCH 216/391] Update AR.swift --- Shared/Magic/decompression/AR.swift | 107 +++++++++++++--------------- 1 file changed, 50 insertions(+), 57 deletions(-) diff --git a/Shared/Magic/decompression/AR.swift b/Shared/Magic/decompression/AR.swift index 631387b4..935a6232 100644 --- a/Shared/Magic/decompression/AR.swift +++ b/Shared/Magic/decompression/AR.swift @@ -1,73 +1,66 @@ -// -// AR.swift -// feather -// -// Created by samara on 8/18/24. -// - import Foundation public struct ARFile { - var name: String - var modificationDate: Date - var ownerId: Int - var groupId: Int - var mode: Int - var size: Int - var content: Data + var name: String + var modificationDate: Date + var ownerId: Int + var groupId: Int + var mode: Int + var size: Int + var content: Data } func removePadding(_ paddedString: String) -> String { - let data = paddedString.data(using: .utf8)! - - guard let firstNonSpaceIndex = data.firstIndex(of: UInt8(ascii: " ")) else { - return paddedString - } - - let actualData = data[.. ARFile { - let size = Int(removePadding(String(data: data.subdata(in: offset+48.. [ARFile] { - if [UInt8](rawData.subdata(in: Range(0...7))) != [0x21, 0x3c, 0x61, 0x72, 0x63, 0x68, 0x3e, 0x0a] { - throw ARError.badArchive("Invalid magic") - } + if [UInt8](rawData.subdata(in: Range(0...7))) != [0x21, 0x3c, 0x61, 0x72, 0x63, 0x68, 0x3e, 0x0a] { + throw ARError.badArchive("Invalid magic") + } - let data = rawData.subdata(in: 8.. Date: Sun, 16 Mar 2025 19:27:04 -0400 Subject: [PATCH 217/391] Update Decompression.swift --- .../Magic/decompression/Decompression.swift | 128 ++++++++---------- 1 file changed, 60 insertions(+), 68 deletions(-) diff --git a/Shared/Magic/decompression/Decompression.swift b/Shared/Magic/decompression/Decompression.swift index 2786b1fc..4bae5e72 100644 --- a/Shared/Magic/decompression/Decompression.swift +++ b/Shared/Magic/decompression/Decompression.swift @@ -1,73 +1,65 @@ -// -// Decompression.swift -// feather -// -// Created by samara on 21.08.2024. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import Foundation import SWCompression import Compression func processFile(at packagesFile: inout URL) throws { - let succeededExtension = packagesFile.pathExtension.lowercased() - let fileManager = FileManager.default - - func readData(from url: URL) throws -> Data { - return try Data(contentsOf: url) - } - - func writeData(_ data: Data, to url: URL) throws { - try data.write(to: url) - } - - func handleCompressedFile(extension: String, decompressor: (Data) throws -> Data) throws { - let compressedData = try readData(from: packagesFile) - let decompressedData = try decompressor(compressedData) - let outputURL = packagesFile.deletingPathExtension() - try writeData(decompressedData, to: outputURL) - packagesFile = outputURL - } - - func handleTarFile() throws { - let tarData = try readData(from: packagesFile) - let tarContainer = try TarContainer.open(container: tarData) - - let extractionDirectory = packagesFile.deletingLastPathComponent().appendingPathComponent(UUID().uuidString) - try fileManager.createDirectory(at: extractionDirectory, withIntermediateDirectories: true, attributes: nil) - - for entry in tarContainer { - let entryPath = extractionDirectory.appendingPathComponent(entry.info.name) - if entry.info.type == .regular { - if let entryData = entry.data { - try writeData(entryData, to: entryPath) - } - } else if entry.info.type == .directory { - try fileManager.createDirectory(at: entryPath, withIntermediateDirectories: true, attributes: nil) - } - } - - packagesFile = extractionDirectory - } - - switch succeededExtension { - case "xz": - try handleCompressedFile(extension: succeededExtension, decompressor: XZArchive.unarchive) - - case "lzma": - try handleCompressedFile(extension: succeededExtension, decompressor: LZMA.decompress) - - case "bz2": - try handleCompressedFile(extension: succeededExtension, decompressor: BZip2.decompress) - - case "gz": - try handleCompressedFile(extension: succeededExtension, decompressor: GzipArchive.unarchive) - - case "tar": - try handleTarFile() - - default: - throw FileProcessingError.unsupportedFileExtension(succeededExtension) - } -} + let succeededExtension = packagesFile.pathExtension.lowercased() + let fileManager = FileManager.default + + func readData(from url: URL) throws -> Data { + return try Data(contentsOf: url) + } + + func writeData(_ data: Data, to url: URL) throws { + try data.write(to: url) + } + + func handleCompressedFile(extension: String, decompressor: (Data) throws -> Data) throws { + let compressedData = try readData(from: packagesFile) + let decompressedData = try decompressor(compressedData) + let outputURL = packagesFile.deletingPathExtension() + try writeData(decompressedData, to: outputURL) + packagesFile = outputURL + } + + func handleTarFile() throws { + let tarData = try readData(from: packagesFile) + let tarContainer = try TarContainer.open(container: tarData) + + let extractionDirectory = packagesFile.deletingLastPathComponent().appendingPathComponent(UUID().uuidString) + try fileManager.createDirectory(at: extractionDirectory, withIntermediateDirectories: true, attributes: nil) + + for entry in tarContainer { + let entryPath = extractionDirectory.appendingPathComponent(entry.info.name) + if entry.info.type == .regular { + if let entryData = entry.data { + try writeData(entryData, to: entryPath) + } + } else if entry.info.type == .directory { + try fileManager.createDirectory(at: entryPath, withIntermediateDirectories: true, attributes: nil) + } + } + + packagesFile = extractionDirectory + } + + switch succeededExtension { + case "xz": + try handleCompressedFile(extension: succeededExtension, decompressor: XZArchive.unarchive) + + case "lzma": + try handleCompressedFile(extension: succeededExtension, decompressor: LZMA.decompress) + + case "bz2": + try handleCompressedFile(extension: succeededExtension, decompressor: BZip2.decompress) + + case "gz": + try handleCompressedFile(extension: succeededExtension, decompressor: GzipArchive.unarchive) + + case "tar": + try handleTarFile() + + default: + throw FileProcessingError.unsupportedFileExtension(succeededExtension) + } +} \ No newline at end of file From 83f7c7a93154000a38fb8986314acfacf8f59dcc Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 19:29:12 -0400 Subject: [PATCH 218/391] Update AppSigner.swift --- Shared/Magic/AppSigner.swift | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/Shared/Magic/AppSigner.swift b/Shared/Magic/AppSigner.swift index 24a3f9c9..902ca48b 100644 --- a/Shared/Magic/AppSigner.swift +++ b/Shared/Magic/AppSigner.swift @@ -1,11 +1,3 @@ -// -// AppSigner.swift -// feather -// -// Created by HAHALOSAH on 7/17/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import Foundation import UIKit import AlertKit @@ -18,7 +10,7 @@ func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, let tmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) let tmpDirApp = tmpDir.appendingPathComponent(appPath.lastPathComponent) var iconURL = "" - + do { Debug.shared.log(message: "============================================") Debug.shared.log(message: "\(mainOptions.mainOptions)") @@ -40,35 +32,35 @@ func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, } else { throw NSError(domain: "AppSigner", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to read Info.plist"]) } - + let handler = TweakHandler(urls: signingOptions.signingOptions.toInject, app: tmpDirApp) try handler.getInputFiles() - + if !mainOptions.mainOptions.removeInjectPaths.isEmpty { if let appexe = try? TweakHandler.findExecutable(at: tmpDirApp) { _ = uninstallDylibs(filePath: appexe.path, dylibPaths: mainOptions.mainOptions.removeInjectPaths) } } - + try updatePlugIns(options: signingOptions, app: tmpDirApp) try removeDumbAssPlaceHolderExtension(options: signingOptions, app: tmpDirApp) try updateMobileProvision(app: tmpDirApp) - + let certPath = try CoreDataManager.shared.getCertifcatePath(source: mainOptions.mainOptions.certificate) let provisionPath = certPath.appendingPathComponent(mainOptions.mainOptions.certificate?.provisionPath ?? "").path let p12Path = certPath.appendingPathComponent(mainOptions.mainOptions.certificate?.p12Path ?? "").path - + Debug.shared.log(message: "🦋 Start Signing 🦋") - + try signAppWithZSign(tmpDirApp: tmpDirApp, certPaths: (provisionPath, p12Path), password: mainOptions.mainOptions.certificate?.password ?? "", main: mainOptions, options: signingOptions) - + Debug.shared.log(message: "🦋 End Signing 🦋") - + let signedUUID = UUID().uuidString try fileManager.createDirectory(at: getDocumentsDirectory().appendingPathComponent("Apps/Signed"), withIntermediateDirectories: true) let signedPath = getDocumentsDirectory().appendingPathComponent("Apps/Signed").appendingPathComponent(signedUUID) try fileManager.moveItem(at: tmpDir, to: signedPath) - + DispatchQueue.main.async { var signedAppObject: NSManagedObject? = nil @@ -181,9 +173,9 @@ func updateMobileProvision(app: URL) throws { func listDylibs(filePath: String) -> [String]? { let dylibPathsArray = NSMutableArray() - + let success = ListDylibs(filePath, dylibPathsArray) - + if success { let dylibPaths = dylibPathsArray as! [String] return dylibPaths From 665f87c879901ddb8f7c3c5455d45755bab81ec0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 19:51:02 -0400 Subject: [PATCH 219/391] Update main.yml --- .github/workflows/main.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d3e08ff7..2a5173a2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,12 +11,17 @@ concurrency: jobs: build: - runs-on: macos-15 + runs-on: macos-latest # Use macos-latest for the latest Xcode steps: - name: Checkout uses: actions/checkout@v3 + - name: Select Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: 'latest-stable' # Or a specific version that supports the latest swift + - name: Clean build environment run: | rm -rf build upload From b4ca82bcb3fbf66b62f26178b33faecd545a29a6 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 20:05:06 -0400 Subject: [PATCH 220/391] Update main.yml --- .github/workflows/main.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a5173a2..e2bf2d68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -77,4 +77,18 @@ jobs: - name: Notify Build Failure if: failure() continue-on-error: true - run: echo "Build and release process failed." \ No newline at end of file + run: echo "Build and release process failed." + + retrieve_logs: + needs: build + runs-on: ubuntu-latest + if: always() # This job will run regardless of the previous job's outcome + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Retrieve Workflow Logs + uses: Superbasil3/retrieve-workflow-logs-action@v0.1.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + keep-only-error-files: true \ No newline at end of file From e94005d33469d9350f106992ccedff6cebdc0f26 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 20:09:00 -0400 Subject: [PATCH 221/391] Add files via upload --- Logs/text.txt | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 Logs/text.txt diff --git a/Logs/text.txt b/Logs/text.txt new file mode 100644 index 00000000..e2bf2d68 --- /dev/null +++ b/Logs/text.txt @@ -0,0 +1,94 @@ +name: Create New Release + +on: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: macos-latest # Use macos-latest for the latest Xcode + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Select Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: 'latest-stable' # Or a specific version that supports the latest swift + + - name: Clean build environment + run: | + rm -rf build upload + mkdir -p build upload + + - name: Install dependencies (packages) + run: | + brew update + curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 + sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid + brew install 7zip gnu-sed + + - name: Cache Homebrew + uses: actions/cache@v3 + with: + path: /Users/runner/Library/Caches/Homebrew + key: ${{ runner.os }}-homebrew-${{ hashFiles('**/Brewfile.lock.json') }} + restore-keys: | + ${{ runner.os }}-homebrew- + + - name: Compile + run: | + make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone + mv packages/* upload/ + + - name: Get Version Number + id: get_version + run: | + VERSION=$( /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/backdoor.app/Info.plist ) + echo "VERSION=${VERSION}" >> $GITHUB_ENV + + - name: Setup + run: | + mv upload/backdoor.ipa upload/backdoor_v${VERSION}.ipa + cp upload/backdoor_v${VERSION}.ipa upload/backdoor_v${VERSION}.tipa + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: Backdoor v${{ env.VERSION }} + tag_name: v${{ env.VERSION }} + files: | + upload/*ipa + generate_release_notes: true + fail_on_unmatched_files: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Notify Build Success + if: success() + run: echo "Build and release process completed successfully." + + - name: Notify Build Failure + if: failure() + continue-on-error: true + run: echo "Build and release process failed." + + retrieve_logs: + needs: build + runs-on: ubuntu-latest + if: always() # This job will run regardless of the previous job's outcome + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Retrieve Workflow Logs + uses: Superbasil3/retrieve-workflow-logs-action@v0.1.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + keep-only-error-files: true \ No newline at end of file From 617ff4f2d9ce8ade8122576d4070ed3f1081b016 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 20:11:40 -0400 Subject: [PATCH 222/391] Update main.yml --- .github/workflows/main.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e2bf2d68..d55875b3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,4 +91,24 @@ jobs: uses: Superbasil3/retrieve-workflow-logs-action@v0.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} - keep-only-error-files: true \ No newline at end of file + keep-only-error-files: true + + - name: Save Logs + run: | + # Create Logs directory if it doesn't exist + mkdir -p Logs + + # Assuming the logs are saved by the action in a specific directory or format, adjust as needed + # Here we're assuming logs are in the current working directory after the action runs + cp -r *.log Logs/ + + # If logs are in a specific folder created by the action, you might need to adjust the path + # cp -r some_action_output_dir/*.log Logs/ + + - name: Commit and Push Logs + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add Logs/*.log + git commit -m "Add workflow logs" || echo "No changes to commit" + git push origin main \ No newline at end of file From 6ff70d69adb8418f9628b40157a16d2c8f6dc38d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 20:21:59 -0400 Subject: [PATCH 223/391] Update logs.yml --- .github/workflows/logs.yml | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/logs.yml diff --git a/.github/workflows/logs.yml b/.github/workflows/logs.yml new file mode 100644 index 00000000..4a482d2d --- /dev/null +++ b/.github/workflows/logs.yml @@ -0,0 +1,49 @@ +name: Retrieve Logs After Release + +on: + workflow_run: + workflows: ["Create New Release"] + types: + - completed + +jobs: + retrieve_logs: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Retrieve Workflow Logs + uses: Superbasil3/retrieve-workflow-logs-action@v0.1.0 + with: + github-token: ${{ secrets.WORKFLOW_SECRET }} + keep-only-error-files: true + continue-on-error: true # This allows the workflow to continue even if this step fails + + - name: Check if logs were retrieved + id: check_logs + run: | + if [ -d "logs" ]; then + echo "logs_exist=true" >> $GITHUB_OUTPUT + else + echo "logs_exist=false" >> $GITHUB_OUTPUT + fi + + - name: Save Logs + if: steps.check_logs.outputs.logs_exist == 'true' + run: | + mkdir -p Logs + cp -r logs/* Logs/ + + - name: Commit and Push Logs + if: steps.check_logs.outputs.logs_exist == 'true' + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add Logs/*.log + git commit -m "Add workflow logs" || echo "No changes to commit" + git push origin main + + - name: Log Failure + if: steps.check_logs.outputs.logs_exist == 'false' + run: echo "Failed to retrieve logs. Logs directory does not exist." \ No newline at end of file From 38754e8496f64a9f3677a7179655efdd62a2738b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 20:22:51 -0400 Subject: [PATCH 224/391] Update main.yml --- .github/workflows/main.yml | 36 +----------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d55875b3..2a5173a2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -77,38 +77,4 @@ jobs: - name: Notify Build Failure if: failure() continue-on-error: true - run: echo "Build and release process failed." - - retrieve_logs: - needs: build - runs-on: ubuntu-latest - if: always() # This job will run regardless of the previous job's outcome - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Retrieve Workflow Logs - uses: Superbasil3/retrieve-workflow-logs-action@v0.1.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - keep-only-error-files: true - - - name: Save Logs - run: | - # Create Logs directory if it doesn't exist - mkdir -p Logs - - # Assuming the logs are saved by the action in a specific directory or format, adjust as needed - # Here we're assuming logs are in the current working directory after the action runs - cp -r *.log Logs/ - - # If logs are in a specific folder created by the action, you might need to adjust the path - # cp -r some_action_output_dir/*.log Logs/ - - - name: Commit and Push Logs - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add Logs/*.log - git commit -m "Add workflow logs" || echo "No changes to commit" - git push origin main \ No newline at end of file + run: echo "Build and release process failed." \ No newline at end of file From c1c16e889db819b239f23134caf5680a70c52fec Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 20:36:02 -0400 Subject: [PATCH 225/391] Update main.yml --- .github/workflows/main.yml | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a5173a2..2e7ddcaf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -68,7 +68,7 @@ jobs: generate_release_notes: true fail_on_unmatched_files: true env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }} - name: Notify Build Success if: success() @@ -77,4 +77,38 @@ jobs: - name: Notify Build Failure if: failure() continue-on-error: true - run: echo "Build and release process failed." \ No newline at end of file + run: echo "Build and release process failed." + + retrieve_logs: + needs: build + runs-on: ubuntu-latest + if: always() # This job will run regardless of the previous job's outcome + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Retrieve Workflow Logs + uses: Superbasil3/retrieve-workflow-logs-action@v0.1.0 + with: + github-token: ${{ secrets.WORKFLOW_SECRET }} + keep-only-error-files: true + + - name: Save Logs + run: | + # Create Logs directory if it doesn't exist + mkdir -p Logs + + # Assuming the logs are saved by the action in a specific directory or format, adjust as needed + # Here we're assuming logs are in the current working directory after the action runs + cp -r *.log Logs/ + + # If logs are in a specific folder created by the action, you might need to adjust the path + # cp -r some_action_output_dir/*.log Logs/ + + - name: Commit and Push Logs + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add Logs/*.log + git commit -m "Add workflow logs" || echo "No changes to commit" + git push origin main \ No newline at end of file From ecc6eb6a6256d3cad7e413d77142694ee4155ef6 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 20:51:04 -0400 Subject: [PATCH 226/391] Update main.yml --- .github/workflows/main.yml | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2e7ddcaf..3e22d6f4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -87,23 +87,36 @@ jobs: - name: Checkout code uses: actions/checkout@v3 + - name: Install jq + run: sudo apt-get install -y jq + - name: Retrieve Workflow Logs - uses: Superbasil3/retrieve-workflow-logs-action@v0.1.0 - with: - github-token: ${{ secrets.WORKFLOW_SECRET }} - keep-only-error-files: true + continue-on-error: true + run: | + # Fetch the workflow run information first to get logs_url + RUN_INFO=$(curl -s -H "Authorization: token ${{ secrets.WORKFLOW_SECRET }}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}") + + # Extract logs_url from the run info + LOGS_URL=$(echo $RUN_INFO | jq -r '.logs_url') + + if [ -n "$LOGS_URL" ]; then + # Download logs if logs_url is available + curl -s -H "Authorization: token ${{ secrets.WORKFLOW_SECRET }}" \ + -H "Accept: application/vnd.github.v3+json" \ + "$LOGS_URL" -o logs.zip + mkdir -p logs + unzip -o logs.zip -d logs/ + else + echo "No logs URL found for this run." + fi + shell: bash - name: Save Logs run: | - # Create Logs directory if it doesn't exist mkdir -p Logs - - # Assuming the logs are saved by the action in a specific directory or format, adjust as needed - # Here we're assuming logs are in the current working directory after the action runs - cp -r *.log Logs/ - - # If logs are in a specific folder created by the action, you might need to adjust the path - # cp -r some_action_output_dir/*.log Logs/ + cp -r logs/*.log Logs/ || echo "No logs to copy" - name: Commit and Push Logs run: | From ca24bc195dd91fb24f76fe638d140b5751735ca6 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 21:13:15 -0400 Subject: [PATCH 227/391] Add files via upload --- scripts/extract_swift_errors.py | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 scripts/extract_swift_errors.py diff --git a/scripts/extract_swift_errors.py b/scripts/extract_swift_errors.py new file mode 100644 index 00000000..e9dc9ead --- /dev/null +++ b/scripts/extract_swift_errors.py @@ -0,0 +1,49 @@ +import sys +import re + +def extract_swift_build_errors(log_file_path): + """ + Extracts error and warning messages from a Swift build log. + + Args: + log_file_path (str): The path to the Swift build log file. + + Returns: + list: A list of error and warning messages. + """ + try: + with open(log_file_path, 'r', encoding='utf-8') as f: + log_content = f.read() + except FileNotFoundError: + print(f"Error: Log file not found at {log_file_path}") + return [] + except Exception as e: + print(f"An error occurred while reading the log file: {e}") + return [] + + # Regular expression to capture error and warning lines. + # This regex is designed to catch Xcode's standard error/warning output. + error_pattern = re.compile(r'^(.*?)(error|warning):\s*(.*)$', re.MULTILINE | re.IGNORECASE) + + matches = error_pattern.findall(log_content) + + extracted_messages = [] + for filepath, level, message in matches: + extracted_messages.append(f"{filepath.strip()}: {level.strip()}: {message.strip()}") + + return extracted_messages + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python extract_swift_errors.py ") + sys.exit(1) + + log_file_path = sys.argv[1] + errors = extract_swift_build_errors(log_file_path) + + if errors: + print("Swift Build Errors and Warnings:") + for error in errors: + print(error) + else: + print("No Swift build errors or warnings found.") \ No newline at end of file From ff84ce823f766485a65518cd79f6b367a97dff3c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 21:14:43 -0400 Subject: [PATCH 228/391] Update logs.yml --- .github/workflows/logs.yml | 65 +++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/.github/workflows/logs.yml b/.github/workflows/logs.yml index 4a482d2d..bfcfaf8e 100644 --- a/.github/workflows/logs.yml +++ b/.github/workflows/logs.yml @@ -1,49 +1,50 @@ -name: Retrieve Logs After Release +name: Analyze Swift Build on: workflow_run: - workflows: ["Create New Release"] + workflows: ["Your Swift Build Workflow Name"] # Replace with your Swift build workflow name types: - completed jobs: - retrieve_logs: - runs-on: ubuntu-latest + analyze-failure: + runs-on: macos-latest # Or ubuntu-latest, depending on your build environment + if: ${{ github.event.workflow_run.conclusion == 'failure' }} steps: - - name: Checkout code + - name: Check out repo content uses: actions/checkout@v3 - - name: Retrieve Workflow Logs - uses: Superbasil3/retrieve-workflow-logs-action@v0.1.0 - with: - github-token: ${{ secrets.WORKFLOW_SECRET }} - keep-only-error-files: true - continue-on-error: true # This allows the workflow to continue even if this step fails + - name: Get failed job ID + id: get_job_id + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + JOB_ID=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/jobs -q '.jobs[] | select(.conclusion == "failure") | .id' | head -n 1) + echo "JOB_ID=$JOB_ID" >> $GITHUB_ENV - - name: Check if logs were retrieved - id: check_logs + - name: Get logs from failed job + id: get_logs + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - if [ -d "logs" ]; then - echo "logs_exist=true" >> $GITHUB_OUTPUT - else - echo "logs_exist=false" >> $GITHUB_OUTPUT + if [[ -z "$JOB_ID" ]]; then + echo "No failed jobs found." + exit 0 fi + LOGS=$(gh api /repos/${{ github.repository }}/actions/jobs/$JOB_ID/logs) + echo "LOGS<> $GITHUB_OUTPUT + echo "$LOGS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - - name: Save Logs - if: steps.check_logs.outputs.logs_exist == 'true' + - name: Save Logs to File run: | - mkdir -p Logs - cp -r logs/* Logs/ + echo "${{ steps.get_logs.outputs.LOGS }}" > build.log - - name: Commit and Push Logs - if: steps.check_logs.outputs.logs_exist == 'true' - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add Logs/*.log - git commit -m "Add workflow logs" || echo "No changes to commit" - git push origin main + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' # Or a specific Python version - - name: Log Failure - if: steps.check_logs.outputs.logs_exist == 'false' - run: echo "Failed to retrieve logs. Logs directory does not exist." \ No newline at end of file + - name: Run Python Script to Extract Errors + run: | + python scripts/extract_swift_errors.py build.log \ No newline at end of file From 52cbbdac9fe13db74d3e2b61dfdca2369cfb81d2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 21:15:11 -0400 Subject: [PATCH 229/391] Update logs.yml --- .github/workflows/logs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/logs.yml b/.github/workflows/logs.yml index bfcfaf8e..8ef96400 100644 --- a/.github/workflows/logs.yml +++ b/.github/workflows/logs.yml @@ -2,7 +2,7 @@ name: Analyze Swift Build on: workflow_run: - workflows: ["Your Swift Build Workflow Name"] # Replace with your Swift build workflow name + workflows: ["Create New Release"] # Replace with your Swift build workflow name types: - completed From 70488ef59faa8c959ff749c67ee34a0eefc010ed Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 21:17:39 -0400 Subject: [PATCH 230/391] Update logs.yml --- .github/workflows/logs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/logs.yml b/.github/workflows/logs.yml index 8ef96400..628530fb 100644 --- a/.github/workflows/logs.yml +++ b/.github/workflows/logs.yml @@ -43,7 +43,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.x' # Or a specific Python version + python-version: '3.12' # Use the latest stable Python version - name: Run Python Script to Extract Errors run: | From 7950a1803c7418ec39c6ff55513f3bfe548e26bd Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 21:38:14 -0400 Subject: [PATCH 231/391] Update logs.yml --- .github/workflows/logs.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/logs.yml b/.github/workflows/logs.yml index 628530fb..f3c26472 100644 --- a/.github/workflows/logs.yml +++ b/.github/workflows/logs.yml @@ -36,14 +36,30 @@ jobs: echo "$LOGS" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - - name: Save Logs to File + - name: Save Logs to File (Line-Based Chunking) run: | - echo "${{ steps.get_logs.outputs.LOGS }}" > build.log + LOGS="${{ steps.get_logs.outputs.LOGS }}" + CHUNK_SIZE=2500 # Adjusted chunk size to 2500 lines. + LINES=$(echo "$LOGS" | wc -l) + START=0 + PART=1 + + while [ $START -lt $LINES ]; do + END=$((START + CHUNK_SIZE)) + if [ $END -gt $LINES ]; then + END=$LINES + fi + echo "$LOGS" | head -n $END | tail -n $CHUNK_SIZE >> build.log.part$PART + START=$END + PART=$((PART + 1)) + done + cat build.log.part* > build.log + rm build.log.part* - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.12' # Use the latest stable Python version + python-version: '3.12' - name: Run Python Script to Extract Errors run: | From 319be8b449b2ed68df7608178b528fdbe1617e8a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 21:40:15 -0400 Subject: [PATCH 232/391] Update main.yml --- .github/workflows/main.yml | 53 +++----------------------------------- 1 file changed, 3 insertions(+), 50 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3e22d6f4..a125ef8f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,7 +20,7 @@ jobs: - name: Select Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 'latest-stable' # Or a specific version that supports the latest swift + xcode-version: 'latest-stable' # Always use latest stable Xcode - name: Clean build environment run: | @@ -68,7 +68,7 @@ jobs: generate_release_notes: true fail_on_unmatched_files: true env: - GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }} + GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} # Using WORKFLOW_SECRET here - name: Notify Build Success if: success() @@ -77,51 +77,4 @@ jobs: - name: Notify Build Failure if: failure() continue-on-error: true - run: echo "Build and release process failed." - - retrieve_logs: - needs: build - runs-on: ubuntu-latest - if: always() # This job will run regardless of the previous job's outcome - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Install jq - run: sudo apt-get install -y jq - - - name: Retrieve Workflow Logs - continue-on-error: true - run: | - # Fetch the workflow run information first to get logs_url - RUN_INFO=$(curl -s -H "Authorization: token ${{ secrets.WORKFLOW_SECRET }}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}") - - # Extract logs_url from the run info - LOGS_URL=$(echo $RUN_INFO | jq -r '.logs_url') - - if [ -n "$LOGS_URL" ]; then - # Download logs if logs_url is available - curl -s -H "Authorization: token ${{ secrets.WORKFLOW_SECRET }}" \ - -H "Accept: application/vnd.github.v3+json" \ - "$LOGS_URL" -o logs.zip - mkdir -p logs - unzip -o logs.zip -d logs/ - else - echo "No logs URL found for this run." - fi - shell: bash - - - name: Save Logs - run: | - mkdir -p Logs - cp -r logs/*.log Logs/ || echo "No logs to copy" - - - name: Commit and Push Logs - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add Logs/*.log - git commit -m "Add workflow logs" || echo "No changes to commit" - git push origin main \ No newline at end of file + run: echo "Build and release process failed." \ No newline at end of file From b1852bb23a5aa4a34e1113315aef4f99f5a28125 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 22:03:22 -0400 Subject: [PATCH 233/391] Update logs.yml --- .github/workflows/logs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/logs.yml b/.github/workflows/logs.yml index f3c26472..5446333c 100644 --- a/.github/workflows/logs.yml +++ b/.github/workflows/logs.yml @@ -2,7 +2,7 @@ name: Analyze Swift Build on: workflow_run: - workflows: ["Create New Release"] # Replace with your Swift build workflow name + workflows: ["Create New Build"] # Updated to your workflow name types: - completed From 032630cec1ea9f57a3d6743fd22b9c320ee240e6 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 22:14:24 -0400 Subject: [PATCH 234/391] Update logs.yml --- .github/workflows/logs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/logs.yml b/.github/workflows/logs.yml index 5446333c..81c8b598 100644 --- a/.github/workflows/logs.yml +++ b/.github/workflows/logs.yml @@ -2,7 +2,7 @@ name: Analyze Swift Build on: workflow_run: - workflows: ["Create New Build"] # Updated to your workflow name + workflows: ["Create New Release"] # Updated to your workflow name types: - completed From d3cbcde92ec568edf964682b11484fd30123d57d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Sun, 16 Mar 2025 22:37:59 -0400 Subject: [PATCH 235/391] Update main.yml --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a125ef8f..b677bc06 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,9 +42,9 @@ jobs: restore-keys: | ${{ runner.os }}-homebrew- - - name: Compile + - name: Compile (Error-Focused Logs) run: | - make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone + make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone 2>&1 | grep -E "(error|warning):" > ${GITHUB_WORKSPACE}/build_errors.log mv packages/* upload/ - name: Get Version Number From b365f9e69776cbd2fa46e1403ba6bb1f7ad8738f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 00:30:25 -0400 Subject: [PATCH 236/391] Update main.yml --- .github/workflows/main.yml | 56 ++++++++------------------------------ 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b677bc06..f2bff300 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,80 +1,48 @@ name: Create New Release on: - push: - branches: - - main -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + workflow_dispatch: jobs: build: - runs-on: macos-latest # Use macos-latest for the latest Xcode - + runs-on: macos-15 steps: - name: Checkout uses: actions/checkout@v3 - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 'latest-stable' # Always use latest stable Xcode - - - name: Clean build environment - run: | - rm -rf build upload - mkdir -p build upload - - name: Install dependencies (packages) run: | - brew update curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid brew install 7zip gnu-sed - - name: Cache Homebrew - uses: actions/cache@v3 - with: - path: /Users/runner/Library/Caches/Homebrew - key: ${{ runner.os }}-homebrew-${{ hashFiles('**/Brewfile.lock.json') }} - restore-keys: | - ${{ runner.os }}-homebrew- - - - name: Compile (Error-Focused Logs) - run: | - make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone 2>&1 | grep -E "(error|warning):" > ${GITHUB_WORKSPACE}/build_errors.log + - name: Compile f + run: | + mkdir upload + make package SCHEME="'feather (Release)'" mv packages/* upload/ - name: Get Version Number id: get_version run: | - VERSION=$( /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/backdoor.app/Info.plist ) + VERSION=$( /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/feather.app/Info.plist ) echo "VERSION=${VERSION}" >> $GITHUB_ENV - name: Setup run: | - mv upload/backdoor.ipa upload/backdoor_v${VERSION}.ipa - cp upload/backdoor_v${VERSION}.ipa upload/backdoor_v${VERSION}.tipa + mv upload/feather.ipa upload/feather_v${VERSION}.ipa + cp upload/feather_v${VERSION}.ipa upload/feather_v${VERSION}.tipa - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - name: Backdoor v${{ env.VERSION }} + name: Feather v${{ env.VERSION }} tag_name: v${{ env.VERSION }} files: | upload/*ipa generate_release_notes: true fail_on_unmatched_files: true + draft: true env: - GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} # Using WORKFLOW_SECRET here - - - name: Notify Build Success - if: success() - run: echo "Build and release process completed successfully." - - - name: Notify Build Failure - if: failure() - continue-on-error: true - run: echo "Build and release process failed." \ No newline at end of file + GITHUB_TOKEN: ${{ env.WORKFLOW_SECRET }} \ No newline at end of file From 0ba661d71da47d0404dd6b365b1e6035b8d220ba Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 00:31:23 -0400 Subject: [PATCH 237/391] Delete scripts directory --- scripts/extract_swift_errors.py | 49 --------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 scripts/extract_swift_errors.py diff --git a/scripts/extract_swift_errors.py b/scripts/extract_swift_errors.py deleted file mode 100644 index e9dc9ead..00000000 --- a/scripts/extract_swift_errors.py +++ /dev/null @@ -1,49 +0,0 @@ -import sys -import re - -def extract_swift_build_errors(log_file_path): - """ - Extracts error and warning messages from a Swift build log. - - Args: - log_file_path (str): The path to the Swift build log file. - - Returns: - list: A list of error and warning messages. - """ - try: - with open(log_file_path, 'r', encoding='utf-8') as f: - log_content = f.read() - except FileNotFoundError: - print(f"Error: Log file not found at {log_file_path}") - return [] - except Exception as e: - print(f"An error occurred while reading the log file: {e}") - return [] - - # Regular expression to capture error and warning lines. - # This regex is designed to catch Xcode's standard error/warning output. - error_pattern = re.compile(r'^(.*?)(error|warning):\s*(.*)$', re.MULTILINE | re.IGNORECASE) - - matches = error_pattern.findall(log_content) - - extracted_messages = [] - for filepath, level, message in matches: - extracted_messages.append(f"{filepath.strip()}: {level.strip()}: {message.strip()}") - - return extracted_messages - -if __name__ == "__main__": - if len(sys.argv) != 2: - print("Usage: python extract_swift_errors.py ") - sys.exit(1) - - log_file_path = sys.argv[1] - errors = extract_swift_build_errors(log_file_path) - - if errors: - print("Swift Build Errors and Warnings:") - for error in errors: - print(error) - else: - print("No Swift build errors or warnings found.") \ No newline at end of file From 9bedb3483620efa046398afea08d8419f1c4d1bf Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 00:31:37 -0400 Subject: [PATCH 238/391] Delete Logs directory --- Logs/text.txt | 94 --------------------------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 Logs/text.txt diff --git a/Logs/text.txt b/Logs/text.txt deleted file mode 100644 index e2bf2d68..00000000 --- a/Logs/text.txt +++ /dev/null @@ -1,94 +0,0 @@ -name: Create New Release - -on: - push: - branches: - - main - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: macos-latest # Use macos-latest for the latest Xcode - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 'latest-stable' # Or a specific version that supports the latest swift - - - name: Clean build environment - run: | - rm -rf build upload - mkdir -p build upload - - - name: Install dependencies (packages) - run: | - brew update - curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 - sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid - brew install 7zip gnu-sed - - - name: Cache Homebrew - uses: actions/cache@v3 - with: - path: /Users/runner/Library/Caches/Homebrew - key: ${{ runner.os }}-homebrew-${{ hashFiles('**/Brewfile.lock.json') }} - restore-keys: | - ${{ runner.os }}-homebrew- - - - name: Compile - run: | - make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone - mv packages/* upload/ - - - name: Get Version Number - id: get_version - run: | - VERSION=$( /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/backdoor.app/Info.plist ) - echo "VERSION=${VERSION}" >> $GITHUB_ENV - - - name: Setup - run: | - mv upload/backdoor.ipa upload/backdoor_v${VERSION}.ipa - cp upload/backdoor_v${VERSION}.ipa upload/backdoor_v${VERSION}.tipa - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - name: Backdoor v${{ env.VERSION }} - tag_name: v${{ env.VERSION }} - files: | - upload/*ipa - generate_release_notes: true - fail_on_unmatched_files: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Notify Build Success - if: success() - run: echo "Build and release process completed successfully." - - - name: Notify Build Failure - if: failure() - continue-on-error: true - run: echo "Build and release process failed." - - retrieve_logs: - needs: build - runs-on: ubuntu-latest - if: always() # This job will run regardless of the previous job's outcome - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Retrieve Workflow Logs - uses: Superbasil3/retrieve-workflow-logs-action@v0.1.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - keep-only-error-files: true \ No newline at end of file From b15447fa794dd7475b53765ce3fc7f8267173995 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 00:34:36 -0400 Subject: [PATCH 239/391] Delete .github/workflows/logs.yml --- .github/workflows/logs.yml | 66 -------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 .github/workflows/logs.yml diff --git a/.github/workflows/logs.yml b/.github/workflows/logs.yml deleted file mode 100644 index 81c8b598..00000000 --- a/.github/workflows/logs.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Analyze Swift Build - -on: - workflow_run: - workflows: ["Create New Release"] # Updated to your workflow name - types: - - completed - -jobs: - analyze-failure: - runs-on: macos-latest # Or ubuntu-latest, depending on your build environment - if: ${{ github.event.workflow_run.conclusion == 'failure' }} - steps: - - name: Check out repo content - uses: actions/checkout@v3 - - - name: Get failed job ID - id: get_job_id - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - JOB_ID=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/jobs -q '.jobs[] | select(.conclusion == "failure") | .id' | head -n 1) - echo "JOB_ID=$JOB_ID" >> $GITHUB_ENV - - - name: Get logs from failed job - id: get_logs - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [[ -z "$JOB_ID" ]]; then - echo "No failed jobs found." - exit 0 - fi - LOGS=$(gh api /repos/${{ github.repository }}/actions/jobs/$JOB_ID/logs) - echo "LOGS<> $GITHUB_OUTPUT - echo "$LOGS" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Save Logs to File (Line-Based Chunking) - run: | - LOGS="${{ steps.get_logs.outputs.LOGS }}" - CHUNK_SIZE=2500 # Adjusted chunk size to 2500 lines. - LINES=$(echo "$LOGS" | wc -l) - START=0 - PART=1 - - while [ $START -lt $LINES ]; do - END=$((START + CHUNK_SIZE)) - if [ $END -gt $LINES ]; then - END=$LINES - fi - echo "$LOGS" | head -n $END | tail -n $CHUNK_SIZE >> build.log.part$PART - START=$END - PART=$((PART + 1)) - done - cat build.log.part* > build.log - rm build.log.part* - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.12' - - - name: Run Python Script to Extract Errors - run: | - python scripts/extract_swift_errors.py build.log \ No newline at end of file From d282bf2ba74c9ad6b20bd730c7fd6a2eef993a14 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 01:06:22 -0400 Subject: [PATCH 240/391] Delete iOS/Views/Home/HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 123 ------------------------- 1 file changed, 123 deletions(-) delete mode 100644 iOS/Views/Home/HomeViewUtilities.swift diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift deleted file mode 100644 index 3424e186..00000000 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ /dev/null @@ -1,123 +0,0 @@ -import UIKit -import ZIPFoundation - -protocol FileHandlingDelegate: AnyObject { - var documentsDirectory: URL { get } - var activityIndicator: UIActivityIndicatorView { get } - func loadFiles() - func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) -} - -class HomeViewFileHandlers { - private let fileManager = FileManager.default - private let utilities = HomeViewUtilities() - - func uploadFile(viewController: UIViewController & FileHandlingDelegate) { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController as? UIDocumentPickerDelegate - documentPicker.modalPresentationStyle = .formSheet - viewController.present(documentPicker, animated: true, completion: nil) - } - - func importFile(viewController: UIViewController & FileHandlingDelegate) { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController as? UIDocumentPickerDelegate - documentPicker.modalPresentationStyle = .formSheet - viewController.present(documentPicker, animated: true, completion: nil) - } - - func createNewFolder(viewController: FileHandlingDelegate, folderName: String) { - let folderURL = viewController.documentsDirectory.appendingPathComponent(folderName) - do { - try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) - viewController.loadFiles() - } catch { - utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Creating Folder") - } - } - - func createNewFile(viewController: FileHandlingDelegate, fileName: String) { - let fileURL = viewController.documentsDirectory.appendingPathComponent(fileName) - if !fileManager.fileExists(atPath: fileURL.path) { - fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) - viewController.loadFiles() - } else { - utilities.handleError(in: viewController as! UIViewController, error: NSError(domain: "FileExists", code: 1, userInfo: [NSLocalizedDescriptionKey: "File already exists"]), withTitle: "Creating File") - } - } - - func renameFile(viewController: FileHandlingDelegate, fileURL: URL, newName: String, completion: @escaping (Result) -> Void) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.loadFiles() - completion(.success(destinationURL)) - } - } catch { - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Renaming File") - completion(.failure(error)) - } - } - } - } - - func deleteFile(viewController: FileHandlingDelegate, fileURL: URL, completion: @escaping (Result) -> Void) { - viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.removeItem(at: fileURL) - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.loadFiles() - completion(.success(())) - } - } catch { - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Deleting File") - completion(.failure(error)) - } - } - } - } - - func unzipFile(viewController: FileHandlingDelegate, fileURL: URL, destinationName: String, progressHandler: ((Double) -> Void)? = nil, completion: @escaping (Result) -> Void) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(destinationName) - viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: progressHandler) - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.loadFiles() - completion(.success(destinationURL)) - } - } catch { - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Unzipping File") - completion(.failure(error)) - } - } - } - } - - func shareFile(viewController: UIViewController, fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - viewController.present(activityController, animated: true, completion: nil) - } -} - -class HomeViewUtilities { - func handleError(in viewController: UIViewController, error: Error, withTitle title: String) { - let alert = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - viewController.present(alert, animated: true, completion: nil) - } -} \ No newline at end of file From 6e28bad8f1ce016e7654d72f829c3de74ff72086 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 01:43:43 -0400 Subject: [PATCH 241/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 349 +++++++++++++++--------- 1 file changed, 221 insertions(+), 128 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 793de65f..e7c4864c 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,30 +1,30 @@ import UIKit import ZIPFoundation -class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { - +class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource, FileHandlingDelegate { + // MARK: - Properties - private var fileList: [String] = [] - private var filteredFileList: [String] = [] + private var fileList: [String] = + private var filteredFileList: [String] = private let fileManager = FileManager.default private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name let fileHandlers = HomeViewFileHandlers() - let utilities = HomeViewUtilities() - + let utilities = HomeViewUtilities() + var documentsDirectory: URL { let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") createFilesDirectoryIfNeeded(at: directory) return directory } - + enum SortOrder { case name, date, size } - + let fileListTableView = UITableView() let activityIndicator = UIActivityIndicatorView(style: .large) - + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -33,166 +33,259 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe loadFiles() configureTableView() } - + // MARK: - UI Setup private func setupUI() { view.backgroundColor = .systemBackground - + let navItem = UINavigationItem(title: "Files") - let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) - let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(changeSortOrder)) - navItem.rightBarButtonItems = [menuButton, sortButton] - navigationController?.navigationBar.setItems([navItem], animated: false) - + let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showOptionsMenu)) + navItem.rightBarButtonItem = menuButton + + let navBar = UINavigationBar(frame: CGRect(x: 0, y: view.safeAreaInsets.top, width: view.bounds.width, height: 44)) + navBar.translatesAutoresizingMaskIntoConstraints = false + navBar.items = [navItem] + view.addSubview(navBar) + + searchController.searchResultsUpdater = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.searchBar.placeholder = "Search Files" + navigationItem.searchController = searchController + definesPresentationContext = true + view.addSubview(fileListTableView) - view.addSubview(activityIndicator) - + NSLayoutConstraint.activate([ - fileListTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + fileListTableView.topAnchor.constraint(equalTo: navBar.bottomAnchor), fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - - activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) + fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) - - fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "FileCell") } - + private func setupActivityIndicator() { - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.hidesWhenStopped = true + view.addSubview(activityIndicator) + activityIndicator.center = view.center } - + private func configureTableView() { fileListTableView.delegate = self fileListTableView.dataSource = self fileListTableView.dragDelegate = self fileListTableView.dropDelegate = self - searchController.searchResultsUpdater = self - navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = false - filteredFileList = fileList + fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") } - - // MARK: - Load Files + + private func createFilesDirectoryIfNeeded(at directory: URL) { + if !fileManager.fileExists(atPath: directory.path) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + print("Error creating directory: \(error)") + } + } + } + + // MARK: - File Operations func loadFiles() { activityIndicator.startAnimating() - DispatchQueue.global().async { [weak self] in + DispatchQueue.global().async { do { - self?.fileList = try self?.fileManager.contentsOfDirectory(atPath: self?.documentsDirectory.path ?? "") ?? [] - self?.sortFiles() + let contents = try self.fileManager.contentsOfDirectory(atPath: self.documentsDirectory.path) DispatchQueue.main.async { - self?.filteredFileList = self?.fileList ?? [] - self?.fileListTableView.reloadData() - self?.activityIndicator.stopAnimating() + self.fileList = contents.sorted() + self.filteredFileList = self.fileList + self.fileListTableView.reloadData() + self.activityIndicator.stopAnimating() } } catch { DispatchQueue.main.async { - self?.activityIndicator.stopAnimating() - self?.utilities.handleError(in: self!, error: error, withTitle: "Loading Files") + self.activityIndicator.stopAnimating() + self.utilities.handleError(in: self, error: error, withTitle: "Error Loading Files") } } } } - - private func sortFiles() { - switch sortOrder { - case .name: - fileList.sort { $0.lowercased() < $1.lowercased() } - case .date: - fileList.sort { getFileDate($0) < getFileDate($1) } - case .size: - fileList.sort { getFileSize($0) < getFileSize($1) } - } - } - - private func getFileDate(_ fileName: String) -> Date { - let fileURL = documentsDirectory.appendingPathComponent(fileName) - return (try? fileManager.attributesOfItem(atPath: fileURL.path)[.modificationDate] as? Date) ?? Date.distantPast - } - - private func getFileSize(_ fileName: String) -> UInt64 { - let fileURL = documentsDirectory.appendingPathComponent(fileName) - return (try? fileManager.attributesOfItem(atPath: fileURL.path)[.size] as? UInt64) ?? 0 - } - - // MARK: - Actions - @objc private func showMenu() { - let menu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - ["Select", "Import", "New Folder", "New File"].forEach { actionTitle in - menu.addAction(UIAlertAction(title: actionTitle, style: .default) { _ in - switch actionTitle { - case "Select": self.selectFiles() - case "Import": self.fileHandlers.importFile(viewController: self) - case "New Folder": - self.utilities.showInputAlert(in: self, title: "New Folder", message: "Enter folder name", actionTitle: "Create") { folderName in - self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) - } - case "New File": - self.utilities.showInputAlert(in: self, title: "New File", message: "Enter file name", actionTitle: "Create") { fileName in - self.fileHandlers.createNewFile(viewController: self, fileName: fileName) - } - default: break - } - }) - } - menu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(menu, animated: true, completion: nil) - } - - @objc private func changeSortOrder() { - let sortMenu = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) - ["Name", "Date", "Size"].forEach { sortOption in - sortMenu.addAction(UIAlertAction(title: sortOption, style: .default) { _ in - switch sortOption { - case "Name": self.sortOrder = .name - case "Date": self.sortOrder = .date - case "Size": self.sortOrder = .size - default: return - } - self.loadFiles() - }) + + func deleteFile(at url: URL) { + do { + try fileManager.removeItem(at: url) + loadFiles() + } catch { + utilities.handleError(in: self, error: error, withTitle: "Error Deleting File") } - sortMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - present(sortMenu, animated: true, completion: nil) - } - - private func selectFiles() { - // Implement select files functionality } - - @objc private func uploadFile() { - fileHandlers.uploadFile(viewController: self) + + func renameFile(at url: URL, newName: String) { + let newURL = url.deletingLastPathComponent().appendingPathComponent(newName) + do { + try fileManager.moveItem(at: url, to: newURL) + loadFiles() + } catch { + utilities.handleError(in: self, error: error, withTitle: "Error Renaming File") + } } - - private func createFilesDirectoryIfNeeded(at directory: URL) { + + func duplicateFile(at url: URL) { + let newName = "Copy of \(url.lastPathComponent)" + let newURL = url.deletingLastPathComponent().appendingPathComponent(newName) do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + try fileManager.copyItem(at: url, to: newURL) + loadFiles() } catch { - utilities.handleError(in: self, error: error, withTitle: "Creating Files Directory") + utilities.handleError(in: self, error: error, withTitle: "Error Duplicating File") } } - - // MARK: - UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return filteredFileList.count + + // MARK: - Navigation + @objc private func showOptionsMenu() { + let alertController = UIAlertController(title: nil, message: "File Options", preferredStyle: .actionSheet) + + let uploadAction = UIAlertAction(title: "Upload File", style: .default) { _ in + self.fileHandlers.uploadFile(viewController: self) + } + alertController.addAction(uploadAction) + + let importAction = UIAlertAction(title: "Import File", style: .default) { _ in + self.fileHandlers.importFile(viewController: self) + } + alertController.addAction(importAction) + + let newFolderAction = UIAlertAction(title: "New Folder", style: .default) { _ in + self.showNewFolderAlert() + } + alertController.addAction(newFolderAction) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + present(alertController, animated: true, completion: nil) } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) - cell.textLabel?.text = filteredFileList[indexPath.row] - return cell + + private func showNewFolderAlert() { + let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.placeholder = "Folder Name" + } + + let createAction = UIAlertAction(title: "Create", style: .default) { [weak self, weak alertController] _ in + guard let folderName = alertController?.textFields?.first?.text, !folderName.isEmpty else { return } + self?.fileHandlers.createNewFolder(viewController: self!, folderName: folderName) { result in + switch result { + case .success(_): + self?.loadFiles() + case .failure(let error): + self?.utilities.handleError(in: self!, error: error, withTitle: "Error Creating Folder") + } + } + } + alertController.addAction(createAction) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + present(alertController, animated: true, completion: nil) + } + + func showFileOptions(for fileURL: URL) { + let alertController = UIAlertController(title: nil, message: fileURL.lastPathComponent, preferredStyle: .actionSheet) + + let openAction = UIAlertAction(title: "Open", style: .default) { _ in + self.openFile(at: fileURL) + } + alertController.addAction(openAction) + + let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in + self.showRenameAlert(for: fileURL) + } + alertController.addAction(renameAction) + + let duplicateAction = UIAlertAction(title: "Duplicate", style: .default) { _ in + self.duplicateFile(at: fileURL) + } + alertController.addAction(duplicateAction) + + let shareAction = UIAlertAction(title: "Share", style: .default) { _ in + self.utilities.shareFile(viewController: self, fileURL: fileURL) + } + alertController.addAction(shareAction) + + let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in + self.deleteFile(at: fileURL) + } + alertController.addAction(deleteAction) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + present(alertController, animated: true, completion: nil) + } + + func openFile(at fileURL: URL) { + let fileExtension = fileURL.pathExtension.lowercased() + + switch fileExtension { + case "txt", "swift", "json", "js", "html", "css", "c", "cpp", "h", "m", "rs": + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + case "plist": + let plistVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistVC, animated: true) + default: + UIDocumentPickerViewController.revealDocument(at: fileURL, in: self.view, animated: true) { _ in + // Completion handler (optional) + } + } + } + + private func showRenameAlert(for fileURL: URL) { + let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) + alertController.addTextField { textField in + textField.text = fileURL.lastPathComponent + } + + let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self, weak alertController] _ in + guard let newName = alertController?.textFields?.first?.text, !newName.isEmpty else { return } + self?.renameFile(at: fileURL, newName: newName) + } + alertController.addAction(renameAction) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + present(alertController, animated: true, completion: nil) + } + + // MARK: - UIDocumentPickerDelegate + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { return } + + let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) + + if fileManager.fileExists(atPath: destinationURL.path) { + utilities.handleError(in: self, error: NSError(domain: "com.example.app", code: -1, userInfo: [NSLocalizedDescriptionKey: "File already exists"]), withTitle: "File Exists") + return + } + + do { + try fileManager.copyItem(at: selectedFileURL, to: destinationURL) + loadFiles() + } catch { + utilities.handleError(in: self, error: error, withTitle: "File Copy Failed") + } } - + // MARK: - UISearchResultsUpdating func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } fileListTableView.reloadData() } - + // MARK: - UITableViewDragDelegate func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { let item = self.fileList[indexPath.row] @@ -200,7 +293,7 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe let dragItem = UIDragItem(itemProvider: itemProvider) return [dragItem] } - + // MARK: - UITableViewDropDelegate func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { coordinator.session.loadObjects(ofClass: NSString.self) { items in @@ -209,11 +302,11 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe self.loadFiles() } } - + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { return session.canLoadObjects(ofClass: NSString.self) } - + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { return UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) } From 5f13a7f5cb4d1732ef5afa8a339d7dbc5c0422df Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 01:45:19 -0400 Subject: [PATCH 242/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index ffe042cf..f60d0a06 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -12,16 +12,16 @@ class HomeViewFileHandlers { private let fileManager = FileManager.default private let utilities = HomeViewUtilities() - func uploadFile(viewController: UIViewController & UIDocumentPickerDelegate & FileHandlingDelegate) { + func uploadFile(viewController: FileHandlingDelegate) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController + documentPicker.delegate = viewController as? UIDocumentPickerDelegate documentPicker.modalPresentationStyle = .formSheet viewController.present(documentPicker, animated: true, completion: nil) } - func importFile(viewController: UIViewController & UIDocumentPickerDelegate & FileHandlingDelegate) { + func importFile(viewController: FileHandlingDelegate) { let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController + documentPicker.delegate = viewController as? UIDocumentPickerDelegate documentPicker.modalPresentationStyle = .formSheet viewController.present(documentPicker, animated: true, completion: nil) } From 9fa1737d6b774404f59b974d2e501cacb9abf855 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 01:53:15 -0400 Subject: [PATCH 243/391] Add files via upload --- iOS/Views/Home/HomeViewUtilities.swift | 191 +++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 iOS/Views/Home/HomeViewUtilities.swift diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift new file mode 100644 index 00000000..c1306c49 --- /dev/null +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -0,0 +1,191 @@ +import UIKit + import ZIPFoundation + import os.log // Import os.log for logging + + // MARK: - Error Handling Enhancements + + /// Custom error hierarchy for file operations. + enum FileAppError: Error { + case fileNotFound(String) // File not found at path + case fileAlreadyExists(String) // File already exists + case invalidFileName(String) // File name contains invalid characters + case invalidFileType(String) // File type not supported + case permissionDenied(String) // Permission denied for operation + case directoryCreationFailed(String) // Directory creation failed + case fileCreationFailed(String) // File creation failed + case fileRenameFailed(String, String) // Renaming file failed (old, new) + case fileDeleteFailed(String) // Deleting file failed + case fileMoveFailed(String, String) // Moving file failed (old, new) + case fileUnzipFailed(String, String, Error?) // Unzipping failed (file, dest, error) + case fileZipFailed(String, String, Error?) // Zipping failed (file, dest, error) + case dylibListingFailed(String, Error?) // Listing dylibs failed (path, error) + case unknown(Error) // An unexpected error occurred + } + + // MARK: - Alert Configuration + + /// Structure to encapsulate alert configurations. + struct AlertConfig { + let title: String? + let message: String? + let style: UIAlertController.Style + let actions: [AlertActionConfig] + let preferredAction: Int? // Index of preferred action + let completionHandler: (() -> Void)? + } + + struct AlertActionConfig { + let title: String? + let style: UIAlertAction.Style + let handler: (() -> Void)? + } + + // MARK: - HomeViewUtilities Class + + class HomeViewUtilities { + + private let logger: Logger // Inject a logger dependency + + init(logger: Logger = Logger(subsystem: "com.example.FileApp", category: "Utilities")) { + self.logger = logger + } + + // MARK: - Error Handling + + /// Handles and presents an error to the user. + /// + /// - Parameters: + /// - viewController: The view controller to present the alert in. + /// - error: The error to handle. + /// - title: The title for the error alert. + func handleError(in viewController: UIViewController, error: Error, withTitle title: String) { + var message: String + var logType: OSLogType = .error + + if let fileError = error as? FileAppError { + switch fileError { + case .fileNotFound(let fileName): + message = "File not found: \(fileName). Please check the file name and try again." + logType = .info // Log as info, user error + case .fileAlreadyExists(let fileName): + message = "A file with the name \(fileName) already exists. Please choose a different name." + logType = .info + case .unknown(let underlyingError): + message = "An unknown error occurred: \(underlyingError.localizedDescription)" + logger.error("Unknown error: \(underlyingError.localizedDescription)") + // Log the underlyingError here using os_log + default: + message = error.localizedDescription + } + } else { + message = error.localizedDescription + logger.error("Unexpected error: \(error.localizedDescription)") + } + + // Present alert on main thread + DispatchQueue.main.async { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + viewController.present(alert, animated: true, completion: nil) + } + } + + // MARK: - Alert Presentation + + /// Presents a basic alert using the provided configuration. + /// + /// - Parameters: + /// - config: The alert configuration. + /// - viewController: The view controller to present the alert in. + func showAlert(config: AlertConfig, in viewController: UIViewController) { + let alert = UIAlertController(title: config.title, message: config.message, preferredStyle: config.style) + + for (index, actionConfig) in config.actions.enumerated() { + let action = UIAlertAction(title: actionConfig.title, style: actionConfig.style) { _ in + actionConfig.handler?() + } + alert.addAction(action) + if let preferredIndex = config.preferredAction, preferredIndex == index { + alert.preferredAction = alert.actions[preferredIndex] + } + } + + DispatchQueue.main.async { + viewController.present(alert, animated: true, completion: config.completionHandler) + } + } + + /// Presents a confirmation alert with "OK" and "Cancel" actions. + /// + /// - Parameters: + /// - title: The title for the alert. + /// - message: The message for the alert. + /// - okHandler: Handler to be executed when the "OK" action is tapped. + /// - cancelHandler: Handler to be executed when the "Cancel" action is tapped. + /// - viewController: The view controller to present the alert in. + func showConfirmationAlert(title: String?, message: String?, okHandler: (() -> Void)?, cancelHandler: (() -> Void)?, in viewController: UIViewController) { + let okAction = AlertActionConfig(title: "OK", style: .default, handler: okHandler) + let cancelAction = AlertActionConfig(title: "Cancel", style: .cancel, handler: cancelHandler) + let config = AlertConfig(title: title, message: message, style: .alert, actions: [okAction, cancelAction], preferredAction: nil, completionHandler: nil) + showAlert(config: config, in: viewController) + } + + /// Presents an alert with a text field for user input. + /// + /// - Parameters: + /// - title: The title for the alert. + /// - message: The message for the alert. + /// - textFieldHandler: Handler to configure the text field. + /// - okHandler: Handler to be executed when the "OK" action is tapped, with the text field's text. + /// - cancelHandler: Handler to be executed when the "Cancel" action is tapped. + /// - viewController: The view controller to present the alert in. + func showInputAlert(title: String?, message: String?, textFieldHandler: ((UITextField) -> Void)?, okHandler: ((String?) -> Void)?, cancelHandler: (() -> Void)?, in viewController: UIViewController) { + let okAction = AlertActionConfig(title: "OK", style: .default) { [weak alert] in + let textField = alert?.textFields?.first + okHandler(textField?.text) + } + let cancelAction = AlertActionConfig(title: "Cancel", style: .cancel, handler: cancelHandler) + let config = AlertConfig(title: title, message: message, style: .alert, actions: [okAction, cancelAction], preferredAction: nil, completionHandler: nil) + + let alert = UIAlertController(title: config.title, message: config.message, preferredStyle: config.style) + alert.addTextField(configurationHandler: textFieldHandler) + + for actionConfig in config.actions { + let action = UIAlertAction(title: actionConfig.title, style: actionConfig.style) { _ in + actionConfig.handler?() + } + alert.addAction(action) + } + + DispatchQueue.main.async { + viewController.present(alert, animated: true, completion: config.completionHandler) + } + } + + // MARK: - Haptic Feedback + + /// Generates haptic feedback using UIImpactFeedbackGenerator. + /// + /// - Parameter style: The style of the impact feedback. + func generateHapticFeedback(style: UIImpactFeedbackGenerator.FeedbackStyle) { + let generator = UIImpactFeedbackGenerator(style: style) + generator.prepare() + generator.impactOccurred() + } + + /// Generates haptic feedback using UINotificationFeedbackGenerator. + /// + /// - Parameter type: The type of the notification feedback. + func generateNotificationFeedback(type: UINotificationFeedbackGenerator.FeedbackType) { + let generator = UINotificationFeedbackGenerator() + generator.prepare() + generator.notificationOccurred(type) + } + + /// Generates haptic feedback using UISelectionFeedbackGenerator. + func generateSelectionFeedback() { + let generator = UISelectionFeedbackGenerator() + generator.prepare() + generator.selectionChanged() + } + } \ No newline at end of file From 832f2db4a0e4e9505299119a832bfe9fc80971bd Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 01:58:27 -0400 Subject: [PATCH 244/391] Update FileOperations.swift --- iOS/Views/Home/FileOperations.swift | 65 +++++++++++++++++++---------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/iOS/Views/Home/FileOperations.swift b/iOS/Views/Home/FileOperations.swift index 1f0d6256..35682b32 100644 --- a/iOS/Views/Home/FileOperations.swift +++ b/iOS/Views/Home/FileOperations.swift @@ -9,9 +9,15 @@ enum FileOperationError: Error { } class FileOperations { - + static let fileManager = FileManager.default - + + /// Copies a file from a source URL to a destination URL. + /// + /// - Parameters: + /// - sourceURL: The URL of the file to copy. + /// - destinationURL: The URL to copy the file to. + /// - Throws: An error if the file does not exist or if the copy operation fails. static func copyFile(at sourceURL: URL, to destinationURL: URL) throws { guard fileManager.fileExists(atPath: sourceURL.path) else { throw FileOperationError.fileNotFound("Source file not found at \(sourceURL.path)") @@ -23,7 +29,13 @@ class FileOperations { throw FileOperationError.unknownError("Failed to copy file: \(error.localizedDescription)") } } - + + /// Moves a file from a source URL to a destination URL. + /// + /// - Parameters: + /// - sourceURL: The URL of the file to move. + /// - destinationURL: The URL to move the file to. + /// - Throws: An error if the file does not exist or if the move operation fails. static func moveFile(at sourceURL: URL, to destinationURL: URL) throws { guard fileManager.fileExists(atPath: sourceURL.path) else { throw FileOperationError.fileNotFound("Source file not found at \(sourceURL.path)") @@ -35,33 +47,29 @@ class FileOperations { throw FileOperationError.unknownError("Failed to move file: \(error.localizedDescription)") } } - + + /// Compresses a file at a given URL to a destination URL using ZIPFoundation. + /// + /// - Parameters: + /// - fileURL: The URL of the file to compress. + /// - destinationURL: The URL where the ZIP archive should be created. + /// - Throws: An error if the file does not exist or if the compression fails. static func compressFile(at fileURL: URL, to destinationURL: URL) throws { guard fileManager.fileExists(atPath: fileURL.path) else { throw FileOperationError.fileNotFound("File not found at \(fileURL.path)") } do { - let archive = try Archive(url: destinationURL, accessMode: .create) - try archive.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent()) + try fileManager.zipItem(at: fileURL, to: destinationURL) print("File compressed from \(fileURL.path) to \(destinationURL.path)") } catch { throw FileOperationError.unknownError("Failed to compress file: \(error.localizedDescription)") } } - - static func renameFile(at sourceURL: URL, to newName: String) throws { - guard fileManager.fileExists(atPath: sourceURL.path) else { - throw FileOperationError.fileNotFound("Source file not found at \(sourceURL.path)") - } - let destinationURL = sourceURL.deletingLastPathComponent().appendingPathComponent(newName) - do { - try fileManager.moveItem(at: sourceURL, to: destinationURL) - print("File renamed from \(sourceURL.path) to \(destinationURL.path)") - } catch { - throw FileOperationError.unknownError("Failed to rename file: \(error.localizedDescription)") - } - } - + + /// Deletes a file at the specified URL. + /// + /// - Parameter fileURL: The URL of the file to delete. + /// - Throws: An error if the file does not exist or if the deletion fails. static func deleteFile(at fileURL: URL) throws { guard fileManager.fileExists(atPath: fileURL.path) else { throw FileOperationError.fileNotFound("File not found at \(fileURL.path)") @@ -73,7 +81,13 @@ class FileOperations { throw FileOperationError.unknownError("Failed to delete file: \(error.localizedDescription)") } } - + + /// Unzips a file at a given URL to a destination URL using ZIPFoundation. + /// + /// - Parameters: + /// - fileURL: The URL of the ZIP archive to extract. + /// - destinationURL: The URL where the contents of the archive should be extracted. + /// - Throws: An error if the file does not exist or if the extraction fails. static func unzipFile(at fileURL: URL, to destinationURL: URL) throws { guard fileManager.fileExists(atPath: fileURL.path) else { throw FileOperationError.fileNotFound("File not found at \(fileURL.path)") @@ -93,13 +107,18 @@ class FileOperations { throw FileOperationError.unknownError("Failed to unzip file: \(error.localizedDescription)") } } - + + /// Presents a Hex Editor View Controller for editing the file. + /// + /// - Parameters: + /// - fileURL: The URL of the file to be edited. + /// - viewController: The view controller to present the Hex Editor from. static func hexEditFile(at fileURL: URL, in viewController: UIViewController) { guard fileManager.fileExists(atPath: fileURL.path) else { print("File not found at \(fileURL.path)") return } - + let hexEditorViewController = HexEditorViewController(fileURL: fileURL) viewController.present(hexEditorViewController, animated: true, completion: nil) } From 62dcc6ef3153d7d116f6b347dd4dca30a98b3b7d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 02:04:15 -0400 Subject: [PATCH 245/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 791 ++++++++++++++---------- 1 file changed, 479 insertions(+), 312 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index e7c4864c..940175fa 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,313 +1,480 @@ import UIKit -import ZIPFoundation - -class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource, FileHandlingDelegate { - - // MARK: - Properties - private var fileList: [String] = - private var filteredFileList: [String] = - private let fileManager = FileManager.default - private let searchController = UISearchController(searchResultsController: nil) - private var sortOrder: SortOrder = .name - let fileHandlers = HomeViewFileHandlers() - let utilities = HomeViewUtilities() - - var documentsDirectory: URL { - let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") - createFilesDirectoryIfNeeded(at: directory) - return directory - } - - enum SortOrder { - case name, date, size - } - - let fileListTableView = UITableView() - let activityIndicator = UIActivityIndicatorView(style: .large) - - // MARK: - Lifecycle - override func viewDidLoad() { - super.viewDidLoad() - setupUI() - setupActivityIndicator() - loadFiles() - configureTableView() - } - - // MARK: - UI Setup - private func setupUI() { - view.backgroundColor = .systemBackground - - let navItem = UINavigationItem(title: "Files") - let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showOptionsMenu)) - navItem.rightBarButtonItem = menuButton - - let navBar = UINavigationBar(frame: CGRect(x: 0, y: view.safeAreaInsets.top, width: view.bounds.width, height: 44)) - navBar.translatesAutoresizingMaskIntoConstraints = false - navBar.items = [navItem] - view.addSubview(navBar) - - searchController.searchResultsUpdater = self - searchController.obscuresBackgroundDuringPresentation = false - searchController.searchBar.placeholder = "Search Files" - navigationItem.searchController = searchController - definesPresentationContext = true - - view.addSubview(fileListTableView) - - NSLayoutConstraint.activate([ - navBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - navBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), - navBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - - fileListTableView.topAnchor.constraint(equalTo: navBar.bottomAnchor), - fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - } - - private func setupActivityIndicator() { - view.addSubview(activityIndicator) - activityIndicator.center = view.center - } - - private func configureTableView() { - fileListTableView.delegate = self - fileListTableView.dataSource = self - fileListTableView.dragDelegate = self - fileListTableView.dropDelegate = self - fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") - } - - private func createFilesDirectoryIfNeeded(at directory: URL) { - if !fileManager.fileExists(atPath: directory.path) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - print("Error creating directory: \(error)") - } - } - } - - // MARK: - File Operations - func loadFiles() { - activityIndicator.startAnimating() - DispatchQueue.global().async { - do { - let contents = try self.fileManager.contentsOfDirectory(atPath: self.documentsDirectory.path) - DispatchQueue.main.async { - self.fileList = contents.sorted() - self.filteredFileList = self.fileList - self.fileListTableView.reloadData() - self.activityIndicator.stopAnimating() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.utilities.handleError(in: self, error: error, withTitle: "Error Loading Files") - } - } - } - } - - func deleteFile(at url: URL) { - do { - try fileManager.removeItem(at: url) - loadFiles() - } catch { - utilities.handleError(in: self, error: error, withTitle: "Error Deleting File") - } - } - - func renameFile(at url: URL, newName: String) { - let newURL = url.deletingLastPathComponent().appendingPathComponent(newName) - do { - try fileManager.moveItem(at: url, to: newURL) - loadFiles() - } catch { - utilities.handleError(in: self, error: error, withTitle: "Error Renaming File") - } - } - - func duplicateFile(at url: URL) { - let newName = "Copy of \(url.lastPathComponent)" - let newURL = url.deletingLastPathComponent().appendingPathComponent(newName) - do { - try fileManager.copyItem(at: url, to: newURL) - loadFiles() - } catch { - utilities.handleError(in: self, error: error, withTitle: "Error Duplicating File") - } - } - - // MARK: - Navigation - @objc private func showOptionsMenu() { - let alertController = UIAlertController(title: nil, message: "File Options", preferredStyle: .actionSheet) - - let uploadAction = UIAlertAction(title: "Upload File", style: .default) { _ in - self.fileHandlers.uploadFile(viewController: self) - } - alertController.addAction(uploadAction) - - let importAction = UIAlertAction(title: "Import File", style: .default) { _ in - self.fileHandlers.importFile(viewController: self) - } - alertController.addAction(importAction) - - let newFolderAction = UIAlertAction(title: "New Folder", style: .default) { _ in - self.showNewFolderAlert() - } - alertController.addAction(newFolderAction) - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - present(alertController, animated: true, completion: nil) - } - - private func showNewFolderAlert() { - let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.placeholder = "Folder Name" - } - - let createAction = UIAlertAction(title: "Create", style: .default) { [weak self, weak alertController] _ in - guard let folderName = alertController?.textFields?.first?.text, !folderName.isEmpty else { return } - self?.fileHandlers.createNewFolder(viewController: self!, folderName: folderName) { result in - switch result { - case .success(_): - self?.loadFiles() - case .failure(let error): - self?.utilities.handleError(in: self!, error: error, withTitle: "Error Creating Folder") - } - } - } - alertController.addAction(createAction) - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - present(alertController, animated: true, completion: nil) - } - - func showFileOptions(for fileURL: URL) { - let alertController = UIAlertController(title: nil, message: fileURL.lastPathComponent, preferredStyle: .actionSheet) - - let openAction = UIAlertAction(title: "Open", style: .default) { _ in - self.openFile(at: fileURL) - } - alertController.addAction(openAction) - - let renameAction = UIAlertAction(title: "Rename", style: .default) { _ in - self.showRenameAlert(for: fileURL) - } - alertController.addAction(renameAction) - - let duplicateAction = UIAlertAction(title: "Duplicate", style: .default) { _ in - self.duplicateFile(at: fileURL) - } - alertController.addAction(duplicateAction) - - let shareAction = UIAlertAction(title: "Share", style: .default) { _ in - self.utilities.shareFile(viewController: self, fileURL: fileURL) - } - alertController.addAction(shareAction) - - let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in - self.deleteFile(at: fileURL) - } - alertController.addAction(deleteAction) - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - present(alertController, animated: true, completion: nil) - } - - func openFile(at fileURL: URL) { - let fileExtension = fileURL.pathExtension.lowercased() - - switch fileExtension { - case "txt", "swift", "json", "js", "html", "css", "c", "cpp", "h", "m", "rs": - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - case "plist": - let plistVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistVC, animated: true) - default: - UIDocumentPickerViewController.revealDocument(at: fileURL, in: self.view, animated: true) { _ in - // Completion handler (optional) - } - } - } - - private func showRenameAlert(for fileURL: URL) { - let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) - alertController.addTextField { textField in - textField.text = fileURL.lastPathComponent - } - - let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self, weak alertController] _ in - guard let newName = alertController?.textFields?.first?.text, !newName.isEmpty else { return } - self?.renameFile(at: fileURL, newName: newName) - } - alertController.addAction(renameAction) - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - present(alertController, animated: true, completion: nil) - } - - // MARK: - UIDocumentPickerDelegate - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { return } - - let destinationURL = documentsDirectory.appendingPathComponent(selectedFileURL.lastPathComponent) - - if fileManager.fileExists(atPath: destinationURL.path) { - utilities.handleError(in: self, error: NSError(domain: "com.example.app", code: -1, userInfo: [NSLocalizedDescriptionKey: "File already exists"]), withTitle: "File Exists") - return - } - - do { - try fileManager.copyItem(at: selectedFileURL, to: destinationURL) - loadFiles() - } catch { - utilities.handleError(in: self, error: error, withTitle: "File Copy Failed") - } - } - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } - fileListTableView.reloadData() - } - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] - let itemProvider = NSItemProvider(object: item as NSString) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - guard let string = items.first as? String else { return } - self.fileList.append(string) - self.loadFiles() - } - } - - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) - } -} \ No newline at end of file + import ZIPFoundation + + + class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { + + + // MARK: - Properties + private var fileList: [String] = + private var filteredFileList: [String] = + private let fileManager = FileManager.default + private let searchController = UISearchController(searchResultsController: nil) + private var sortOrder: SortOrder = .name + let fileHandlers = HomeViewFileHandlers() + let utilities = HomeViewUtilities() + + + var documentsDirectory: URL { + let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") + createFilesDirectoryIfNeeded(at: directory) + return directory + } + + + enum SortOrder { + case name, date, size + } + + + let fileListTableView = UITableView() + let activityIndicator = UIActivityIndicatorView(style: .large) + + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + setupActivityIndicator() + loadFiles() + configureTableView() + } + + + // MARK: - UI Setup + private func setupUI() { + view.backgroundColor = .systemBackground + + + let navItem = UINavigationItem(title: "Files") + let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) + let uploadButton = UIBarButtonItem(image: UIImage(systemName: "arrow.up.doc"), style: .plain, target: self, action: #selector(uploadFile)) + navItem.rightBarButtonItems = [menuButton, uploadButton] + let navBar = UINavigationBar() + navBar.translatesAutoresizingMaskIntoConstraints = false + navBar.barTintColor = .systemBlue + navBar.titleTextAttributes = [.foregroundColor: UIColor.white] + navBar.setItems([navItem], animated: false) + view.addSubview(navBar) + + + searchController.searchResultsUpdater = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.searchBar.placeholder = "Search Files" + navigationItem.searchController = searchController + definesPresentationContext = true + + + fileListTableView.translatesAutoresizingMaskIntoConstraints = false + fileListTableView.separatorStyle = .singleLine + fileListTableView.rowHeight = UITableView.automaticDimension + fileListTableView.estimatedRowHeight = 44 + view.addSubview(fileListTableView) + + + NSLayoutConstraint.activate([ + navBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + + fileListTableView.topAnchor.constraint(equalTo: navBar.bottomAnchor), + fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + } + + + private func setupActivityIndicator() { + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.hidesWhenStopped = true + activityIndicator.color = .systemBlue + view.addSubview(activityIndicator) + + + NSLayoutConstraint.activate([ + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + } + + + private func createFilesDirectoryIfNeeded(at directory: URL) { + if !fileManager.fileExists(atPath: directory.path) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + utilities.handleError(in: self, error: error, withTitle: "Error Creating Directory") + } + } + } + + + private func configureTableView() { + fileListTableView.delegate = self + fileListTableView.dataSource = self + fileListTableView.dragDelegate = self + fileListTableView.dropDelegate = self + fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") + } + + + // MARK: - File Operations + func loadFiles() { + fileList = + do { + let files = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) + fileList = files.filter { !$0.hasPrefix(".") } + sortFiles() + DispatchQueue.main.async { + self.fileListTableView.reloadData() + } + } catch { + utilities.handleError(in: self, error: error, withTitle: "Error Loading Files") + } + } + + + func sortFiles() { + switch sortOrder { + case .name: + fileList.sort { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } + case .date: + fileList.sort { + let url1 = documentsDirectory.appendingPathComponent($0) + let url2 = documentsDirectory.appendingPathComponent($1) + if let date1 = try? fileManager.attributesOfItem(atPath: url1.path).fileModificationDate, + let date2 = try? fileManager.attributesOfItem(atPath: url2.path).fileModificationDate { + return date1 < date2 + } + return false + } + case .size: + fileList.sort { + let url1 = documentsDirectory.appendingPathComponent($0) + let url2 = documentsDirectory.appendingPathComponent($1) + if let size1 = try? fileManager.attributesOfItem(atPath: url1.path).fileSize, + let size2 = try? fileManager.attributesOfItem(atPath: url2.path).fileSize { + return size1 < size2 + } + return false + } + } + } + + + func showFileOptions(for fileURL: URL) { + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + + let openAction = UIAlertAction(title: "Open", style: .default) { [weak self] _ in + self?.openFile(at: fileURL) + } + alertController.addAction(openAction) + + + let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self] _ in + self?.showRenameDialog(for: fileURL) + } + alertController.addAction(renameAction) + + + let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in + self?.deleteFile(at: fileURL) + } + alertController.addAction(deleteAction) + + + if fileURL.pathExtension == "zip" { + let unzipAction = UIAlertAction(title: "Unzip", style: .default) { [weak self] _ in + self?.unzipFile(at: fileURL) + } + alertController.addAction(unzipAction) + } + + + let shareAction = UIAlertAction(title: "Share", style: .default) { [weak self] _ in + self?.fileHandlers.shareFile(viewController: self, fileURL: fileURL) + } + alertController.addAction(shareAction) + + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + + present(alertController, animated: true, completion: nil) + } + + + func openFile(at fileURL: URL) { + if ["txt", "md", "swift", "js", "html", "css"].contains(fileURL.pathExtension) { + let textEditorVC = TextEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(textEditorVC, animated: true) + } else if fileURL.pathExtension == "plist" { + let plistEditorVC = PlistEditorViewController(fileURL: fileURL) + navigationController?.pushViewController(plistEditorVC, animated: true) + } else { + UIDocumentInteractionController(url: fileURL).presentPreview(animated: true) + } + } + + + func deleteFile(at fileURL: URL) { + fileHandlers.deleteFile(viewController: self, fileURL: fileURL) { result in + switch result { + case .success(_): + break + case .failure(let error): + self.utilities.handleError(in: self, error: error, withTitle: "Error Deleting File") + } + } + } + + + func unzipFile(at fileURL: URL) { + let destinationName = fileURL.deletingPathExtension().lastPathComponent + fileHandlers.unzipFile(viewController: self, fileURL: fileURL, destinationName: destinationName) { result in + switch result { + case .success(_): + break + case .failure(let error): + self.utilities.handleError(in: self, error: error, withTitle: "Error Unzipping File") + } + } + } + + + func showRenameDialog(for fileURL: URL) { + let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) + alertController.addTextField() + let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self] _ in + guard let self = self, let newName = alertController.textFields?.first?.text, !newName.isEmpty else { return } + let newURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + do { + try FileManager.default.moveItem(at: fileURL, to: newURL) + self.loadFiles() + } catch { + self.utilities.handleError(in: self, error: error, withTitle: "Error Renaming File") + } + } + alertController.addAction(renameAction) + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + present(alertController, animated: true, completion: nil) + } + + + // MARK: - UI Actions + + + @objc func uploadFile() { + fileHandlers.uploadFile(viewController: self) + } + + + @objc func showMenu() { + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + + let newFolderAction = UIAlertAction(title: "New Folder", style: .default) { [weak self] _ in + self?.showNewFolderDialog() + } + alertController.addAction(newFolderAction) + + + let newFileAction = UIAlertAction(title: "New File", style: .default) { [weak self] _ in + self?.showNewFileDialog() + } + alertController.addAction(newFileAction) + + + let sortAction = UIAlertAction(title: "Sort", style: .default) { [weak self] _ in + self?.showSortOptions() + } + alertController.addAction(sortAction) + + + let importAction = UIAlertAction(title: "Import File", style: .default) { [weak self] _ in + self?.fileHandlers.importFile(viewController: self) + } + alertController.addAction(importAction) + + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + + present(alertController, animated: true, completion: nil) + } + + + /// Shows a dialog to create a new folder. + func showNewFolderDialog() { + let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) + alertController.addTextField() + let createAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in + guard let self = self, let folderName = alertController.textFields?.first?.text, !folderName.isEmpty else { return } + self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) { result in + switch result { + case .success(_): + break + case .failure(let error): + self.utilities.handleError(in: self, error: error, withTitle: "Error Creating Folder") + } + } + } + alertController.addAction(createAction) + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + present(alertController, animated: true, completion: nil) + } + + + /// Shows a dialog to create a new file. + func showNewFileDialog() { + let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) + alertController.addTextField() + let createAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in + guard let self = self, let fileName = alertController.textFields?.first?.text, !fileName.isEmpty else { return } + self.fileHandlers.createNewFile(viewController: self, fileName: fileName) { result in + switch result { + case .success(_): + break + case .failure(let error): + self.utilities.handleError(in: self, error: error, withTitle: "Error Creating File") + } + } + } + alertController.addAction(createAction) + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + present(alertController, animated: true, completion: nil) + } + + + /// Shows options to sort the file list. + func showSortOptions() { + let alertController = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) + + + let nameAction = UIAlertAction(title: "Name", style: .default) { [weak self] _ in + self?.sortOrder = .name + self?.sortFiles() + self?.fileListTableView.reloadData() + } + alertController.addAction(nameAction) + + + let dateAction = UIAlertAction(title: "Date", style: .default) { [weak self] _ in + self?.sortOrder = .date + self?.sortFiles() + self?.fileListTableView.reloadData() + } + alertController.addAction(dateAction) + + + let sizeAction = UIAlertAction(title: "Size", style: .default) { [weak self] _ in + self?.sortOrder = .size + self?.sortFiles() + self?.fileListTableView.reloadData() + } + alertController.addAction(sizeAction) + + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + + present(alertController, animated: true, completion: nil) + } + + + // MARK: - UIDocumentPickerDelegate + + + /// Handles the selection of a document from the document picker. + /// + /// - Parameter controller: The document picker controller. + /// - Parameter urls: The URLs of the selected documents. + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let url = urls.first else { return } + let destinationURL = documentsDirectory.appendingPathComponent(url.lastPathComponent) + do { + try FileManager.default.copyItem(at: url, to: destinationURL) + loadFiles() + } catch { + utilities.handleError(in: self, error: error, withTitle: "Error Importing File") + } + } + + + // MARK: - UISearchResultsUpdating + + + /// Updates the search results based on the search query. + /// + /// - Parameter searchController: The search controller. + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } + fileListTableView.reloadData() + } + + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + guard let string = items.first as? String else { return } + self.fileList.append(string) + self.loadFiles() + } + } + + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return session.canLoadObjects(ofClass: NSString.self) + } + + + func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { + return UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) + } + } + + + // MARK: - UITableViewDelegate, UITableViewDataSource + + + extension HomeViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + showFileOptions(for: fileURL) + } + } \ No newline at end of file From da0e009e22038fac2b6cca53f9f5f0c4c4fc545c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 02:06:22 -0400 Subject: [PATCH 246/391] Update HomeViewTableHandlers.swift --- iOS/Views/Home/HomeViewTableHandlers.swift | 39 +++++++++++---------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/iOS/Views/Home/HomeViewTableHandlers.swift b/iOS/Views/Home/HomeViewTableHandlers.swift index f5404e66..0ce88f68 100644 --- a/iOS/Views/Home/HomeViewTableHandlers.swift +++ b/iOS/Views/Home/HomeViewTableHandlers.swift @@ -1,22 +1,25 @@ import UIKit + -extension HomeViewController: UITableViewDelegate, UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } + extension HomeViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchController.isActive ? filteredFileList.count : fileList.count + } + - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + let file = File(url: fileURL) + cell.configure(with: file) + return cell + } + - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - showFileOptions(for: fileURL) - } -} \ No newline at end of file + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] + let fileURL = documentsDirectory.appendingPathComponent(fileName) + showFileOptions(for: fileURL) + } + } \ No newline at end of file From 69a728a66eb8224cf8b3694e447f63f7cfb4a3d8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 02:45:12 -0400 Subject: [PATCH 247/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 100 +++++++----------------- 1 file changed, 28 insertions(+), 72 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 940175fa..621bce87 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -2,7 +2,7 @@ import UIKit import ZIPFoundation - class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { + class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource, FileHandlingDelegate { // MARK: - Properties @@ -116,27 +116,39 @@ import UIKit fileListTableView.dataSource = self fileListTableView.dragDelegate = self fileListTableView.dropDelegate = self + searchController.searchResultsUpdater = self + navigationItem.searchController = searchController + definesPresentationContext = true fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") } // MARK: - File Operations func loadFiles() { + activityIndicator.startAnimating() fileList = + DispatchQueue.global().async { [weak self] in + guard let self = self else { return } do { - let files = try fileManager.contentsOfDirectory(atPath: documentsDirectory.path) - fileList = files.filter { !$0.hasPrefix(".") } - sortFiles() + let files = try self.fileManager.contentsOfDirectory(atPath: self.documentsDirectory.path) + self.fileList = files.filter { !$0.hasPrefix(".") } + self.sortFiles() DispatchQueue.main.async { + self.filteredFileList = self.fileList self.fileListTableView.reloadData() + self.activityIndicator.stopAnimating() } } catch { - utilities.handleError(in: self, error: error, withTitle: "Error Loading Files") + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.utilities.handleError(in: self, error: error, withTitle: "Error Loading Files") + } } } func sortFiles() { + guard !fileList.isEmpty else { return } switch sortOrder { case .name: fileList.sort { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } @@ -341,9 +353,13 @@ import UIKit self.fileHandlers.createNewFile(viewController: self, fileName: fileName) { result in switch result { case .success(_): + // Handle success, e.g., reload file list, show a message + self.loadFiles() // Assuming this reloads the file list break case .failure(let error): + // Handle failure, e.g., show an error message to the user self.utilities.handleError(in: self, error: error, withTitle: "Error Creating File") + break } } } @@ -354,33 +370,32 @@ import UIKit } - /// Shows options to sort the file list. func showSortOptions() { let alertController = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) - let nameAction = UIAlertAction(title: "Name", style: .default) { [weak self] _ in + let sortByNameAction = UIAlertAction(title: "Name", style: .default) { [weak self] _ in self?.sortOrder = .name self?.sortFiles() self?.fileListTableView.reloadData() } - alertController.addAction(nameAction) + alertController.addAction(sortByNameAction) - let dateAction = UIAlertAction(title: "Date", style: .default) { [weak self] _ in + let sortByDateAction = UIAlertAction(title: "Date", style: .default) { [weak self] _ in self?.sortOrder = .date self?.sortFiles() self?.fileListTableView.reloadData() } - alertController.addAction(dateAction) + alertController.addAction(sortByDateAction) - let sizeAction = UIAlertAction(title: "Size", style: .default) { [weak self] _ in + let sortBySizeAction = UIAlertAction(title: "Size", style: .default) { [weak self] _ in self?.sortOrder = .size self?.sortFiles() self?.fileListTableView.reloadData() } - alertController.addAction(sizeAction) + alertController.addAction(sortBySizeAction) let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) @@ -391,31 +406,7 @@ import UIKit } - // MARK: - UIDocumentPickerDelegate - - - /// Handles the selection of a document from the document picker. - /// - /// - Parameter controller: The document picker controller. - /// - Parameter urls: The URLs of the selected documents. - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let url = urls.first else { return } - let destinationURL = documentsDirectory.appendingPathComponent(url.lastPathComponent) - do { - try FileManager.default.copyItem(at: url, to: destinationURL) - loadFiles() - } catch { - utilities.handleError(in: self, error: error, withTitle: "Error Importing File") - } - } - - // MARK: - UISearchResultsUpdating - - - /// Updates the search results based on the search query. - /// - /// - Parameter searchController: The search controller. func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } @@ -442,39 +433,4 @@ import UIKit } - func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { - return session.canLoadObjects(ofClass: NSString.self) - } - - - func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - return UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) - } - } - - - // MARK: - UITableViewDelegate, UITableViewDataSource - - - extension HomeViewController: UITableViewDelegate, UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchController.isActive ? filteredFileList.count : fileList.count - } - - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell", for: indexPath) as! FileTableViewCell - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - let file = File(url: fileURL) - cell.configure(with: file) - return cell - } - - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let fileName = searchController.isActive ? filteredFileList[indexPath.row] : fileList[indexPath.row] - let fileURL = documentsDirectory.appendingPathComponent(fileName) - showFileOptions(for: fileURL) - } - } \ No newline at end of file + func tableView(_ tableView: \ No newline at end of file From add0f6bb82de671231a40cec263d7db33ffecc18 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 02:49:01 -0400 Subject: [PATCH 248/391] Create create.yml --- .github/workflows/create.yml | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/create.yml diff --git a/.github/workflows/create.yml b/.github/workflows/create.yml new file mode 100644 index 00000000..595e67d7 --- /dev/null +++ b/.github/workflows/create.yml @@ -0,0 +1,47 @@ +name: Create Beta Build + +on: + workflow_dispatch: + +jobs: + build: + runs-on: macos-15 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install dependencies (packages) + run: | + curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 + sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid + brew install 7zip gnu-sed + + - name: Compile f + run: | + mkdir upload + make package SCHEME="'feather (Release)'" + mv packages/* upload/ + + - name: Get Version Number + id: get_version + run: | + VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/feather.app/Info.plist) + echo "VERSION=${VERSION}" >> $GITHUB_ENV + + - name: Setup + run: | + mv upload/feather.ipa upload/feather_v${VERSION}.ipa + cp upload/feather_v${VERSION}.ipa upload/feather_v${VERSION}.tipa + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: Feather v${{ env.VERSION }} + tag_name: v${{ env.VERSION }} + files: | + upload/*ipa + generate_release_notes: true + fail_on_unmatched_files: true + draft: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 679e8011204952932c4e94f4ed9eaf3886ebbbf9 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 03:30:06 -0400 Subject: [PATCH 249/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 895 ++++++++++++------------ 1 file changed, 460 insertions(+), 435 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 621bce87..35ce3998 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,436 +1,461 @@ import UIKit - import ZIPFoundation - - - class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource, FileHandlingDelegate { - - - // MARK: - Properties - private var fileList: [String] = - private var filteredFileList: [String] = - private let fileManager = FileManager.default - private let searchController = UISearchController(searchResultsController: nil) - private var sortOrder: SortOrder = .name - let fileHandlers = HomeViewFileHandlers() - let utilities = HomeViewUtilities() - - - var documentsDirectory: URL { - let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") - createFilesDirectoryIfNeeded(at: directory) - return directory - } - - - enum SortOrder { - case name, date, size - } - - - let fileListTableView = UITableView() - let activityIndicator = UIActivityIndicatorView(style: .large) - - - // MARK: - Lifecycle - override func viewDidLoad() { - super.viewDidLoad() - setupUI() - setupActivityIndicator() - loadFiles() - configureTableView() - } - - - // MARK: - UI Setup - private func setupUI() { - view.backgroundColor = .systemBackground - - - let navItem = UINavigationItem(title: "Files") - let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) - let uploadButton = UIBarButtonItem(image: UIImage(systemName: "arrow.up.doc"), style: .plain, target: self, action: #selector(uploadFile)) - navItem.rightBarButtonItems = [menuButton, uploadButton] - let navBar = UINavigationBar() - navBar.translatesAutoresizingMaskIntoConstraints = false - navBar.barTintColor = .systemBlue - navBar.titleTextAttributes = [.foregroundColor: UIColor.white] - navBar.setItems([navItem], animated: false) - view.addSubview(navBar) - - - searchController.searchResultsUpdater = self - searchController.obscuresBackgroundDuringPresentation = false - searchController.searchBar.placeholder = "Search Files" - navigationItem.searchController = searchController - definesPresentationContext = true - - - fileListTableView.translatesAutoresizingMaskIntoConstraints = false - fileListTableView.separatorStyle = .singleLine - fileListTableView.rowHeight = UITableView.automaticDimension - fileListTableView.estimatedRowHeight = 44 - view.addSubview(fileListTableView) - - - NSLayoutConstraint.activate([ - navBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - navBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), - navBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - - - fileListTableView.topAnchor.constraint(equalTo: navBar.bottomAnchor), - fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - } - - - private func setupActivityIndicator() { - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.hidesWhenStopped = true - activityIndicator.color = .systemBlue - view.addSubview(activityIndicator) - - - NSLayoutConstraint.activate([ - activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) - ]) - } - - - private func createFilesDirectoryIfNeeded(at directory: URL) { - if !fileManager.fileExists(atPath: directory.path) { - do { - try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - } catch { - utilities.handleError(in: self, error: error, withTitle: "Error Creating Directory") - } - } - } - - - private func configureTableView() { - fileListTableView.delegate = self - fileListTableView.dataSource = self - fileListTableView.dragDelegate = self - fileListTableView.dropDelegate = self - searchController.searchResultsUpdater = self - navigationItem.searchController = searchController - definesPresentationContext = true - fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") - } - - - // MARK: - File Operations - func loadFiles() { - activityIndicator.startAnimating() - fileList = - DispatchQueue.global().async { [weak self] in - guard let self = self else { return } - do { - let files = try self.fileManager.contentsOfDirectory(atPath: self.documentsDirectory.path) - self.fileList = files.filter { !$0.hasPrefix(".") } - self.sortFiles() - DispatchQueue.main.async { - self.filteredFileList = self.fileList - self.fileListTableView.reloadData() - self.activityIndicator.stopAnimating() - } - } catch { - DispatchQueue.main.async { - self.activityIndicator.stopAnimating() - self.utilities.handleError(in: self, error: error, withTitle: "Error Loading Files") - } - } - } - - - func sortFiles() { - guard !fileList.isEmpty else { return } - switch sortOrder { - case .name: - fileList.sort { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } - case .date: - fileList.sort { - let url1 = documentsDirectory.appendingPathComponent($0) - let url2 = documentsDirectory.appendingPathComponent($1) - if let date1 = try? fileManager.attributesOfItem(atPath: url1.path).fileModificationDate, - let date2 = try? fileManager.attributesOfItem(atPath: url2.path).fileModificationDate { - return date1 < date2 - } - return false - } - case .size: - fileList.sort { - let url1 = documentsDirectory.appendingPathComponent($0) - let url2 = documentsDirectory.appendingPathComponent($1) - if let size1 = try? fileManager.attributesOfItem(atPath: url1.path).fileSize, - let size2 = try? fileManager.attributesOfItem(atPath: url2.path).fileSize { - return size1 < size2 - } - return false - } - } - } - - - func showFileOptions(for fileURL: URL) { - let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - - - let openAction = UIAlertAction(title: "Open", style: .default) { [weak self] _ in - self?.openFile(at: fileURL) - } - alertController.addAction(openAction) - - - let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self] _ in - self?.showRenameDialog(for: fileURL) - } - alertController.addAction(renameAction) - - - let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in - self?.deleteFile(at: fileURL) - } - alertController.addAction(deleteAction) - - - if fileURL.pathExtension == "zip" { - let unzipAction = UIAlertAction(title: "Unzip", style: .default) { [weak self] _ in - self?.unzipFile(at: fileURL) - } - alertController.addAction(unzipAction) - } - - - let shareAction = UIAlertAction(title: "Share", style: .default) { [weak self] _ in - self?.fileHandlers.shareFile(viewController: self, fileURL: fileURL) - } - alertController.addAction(shareAction) - - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - - present(alertController, animated: true, completion: nil) - } - - - func openFile(at fileURL: URL) { - if ["txt", "md", "swift", "js", "html", "css"].contains(fileURL.pathExtension) { - let textEditorVC = TextEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(textEditorVC, animated: true) - } else if fileURL.pathExtension == "plist" { - let plistEditorVC = PlistEditorViewController(fileURL: fileURL) - navigationController?.pushViewController(plistEditorVC, animated: true) - } else { - UIDocumentInteractionController(url: fileURL).presentPreview(animated: true) - } - } - - - func deleteFile(at fileURL: URL) { - fileHandlers.deleteFile(viewController: self, fileURL: fileURL) { result in - switch result { - case .success(_): - break - case .failure(let error): - self.utilities.handleError(in: self, error: error, withTitle: "Error Deleting File") - } - } - } - - - func unzipFile(at fileURL: URL) { - let destinationName = fileURL.deletingPathExtension().lastPathComponent - fileHandlers.unzipFile(viewController: self, fileURL: fileURL, destinationName: destinationName) { result in - switch result { - case .success(_): - break - case .failure(let error): - self.utilities.handleError(in: self, error: error, withTitle: "Error Unzipping File") - } - } - } - - - func showRenameDialog(for fileURL: URL) { - let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) - alertController.addTextField() - let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self] _ in - guard let self = self, let newName = alertController.textFields?.first?.text, !newName.isEmpty else { return } - let newURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - do { - try FileManager.default.moveItem(at: fileURL, to: newURL) - self.loadFiles() - } catch { - self.utilities.handleError(in: self, error: error, withTitle: "Error Renaming File") - } - } - alertController.addAction(renameAction) - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - present(alertController, animated: true, completion: nil) - } - - - // MARK: - UI Actions - - - @objc func uploadFile() { - fileHandlers.uploadFile(viewController: self) - } - - - @objc func showMenu() { - let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - - - let newFolderAction = UIAlertAction(title: "New Folder", style: .default) { [weak self] _ in - self?.showNewFolderDialog() - } - alertController.addAction(newFolderAction) - - - let newFileAction = UIAlertAction(title: "New File", style: .default) { [weak self] _ in - self?.showNewFileDialog() - } - alertController.addAction(newFileAction) - - - let sortAction = UIAlertAction(title: "Sort", style: .default) { [weak self] _ in - self?.showSortOptions() - } - alertController.addAction(sortAction) - - - let importAction = UIAlertAction(title: "Import File", style: .default) { [weak self] _ in - self?.fileHandlers.importFile(viewController: self) - } - alertController.addAction(importAction) - - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - - present(alertController, animated: true, completion: nil) - } - - - /// Shows a dialog to create a new folder. - func showNewFolderDialog() { - let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) - alertController.addTextField() - let createAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in - guard let self = self, let folderName = alertController.textFields?.first?.text, !folderName.isEmpty else { return } - self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) { result in - switch result { - case .success(_): - break - case .failure(let error): - self.utilities.handleError(in: self, error: error, withTitle: "Error Creating Folder") - } - } - } - alertController.addAction(createAction) - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - present(alertController, animated: true, completion: nil) - } - - - /// Shows a dialog to create a new file. - func showNewFileDialog() { - let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) - alertController.addTextField() - let createAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in - guard let self = self, let fileName = alertController.textFields?.first?.text, !fileName.isEmpty else { return } - self.fileHandlers.createNewFile(viewController: self, fileName: fileName) { result in - switch result { - case .success(_): - // Handle success, e.g., reload file list, show a message - self.loadFiles() // Assuming this reloads the file list - break - case .failure(let error): - // Handle failure, e.g., show an error message to the user - self.utilities.handleError(in: self, error: error, withTitle: "Error Creating File") - break - } - } - } - alertController.addAction(createAction) - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - present(alertController, animated: true, completion: nil) - } - - - func showSortOptions() { - let alertController = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) - - - let sortByNameAction = UIAlertAction(title: "Name", style: .default) { [weak self] _ in - self?.sortOrder = .name - self?.sortFiles() - self?.fileListTableView.reloadData() - } - alertController.addAction(sortByNameAction) - - - let sortByDateAction = UIAlertAction(title: "Date", style: .default) { [weak self] _ in - self?.sortOrder = .date - self?.sortFiles() - self?.fileListTableView.reloadData() - } - alertController.addAction(sortByDateAction) - - - let sortBySizeAction = UIAlertAction(title: "Size", style: .default) { [weak self] _ in - self?.sortOrder = .size - self?.sortFiles() - self?.fileListTableView.reloadData() - } - alertController.addAction(sortBySizeAction) - - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - - present(alertController, animated: true, completion: nil) - } - - - // MARK: - UISearchResultsUpdating - func updateSearchResults(for searchController: UISearchController) { - guard let searchText = searchController.searchBar.text else { return } - filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } - fileListTableView.reloadData() - } - - - // MARK: - UITableViewDragDelegate - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let item = self.fileList[indexPath.row] - let itemProvider = NSItemProvider(object: item as NSString) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } - - - // MARK: - UITableViewDropDelegate - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - coordinator.session.loadObjects(ofClass: NSString.self) { items in - guard let string = items.first as? String else { return } - self.fileList.append(string) - self.loadFiles() - } - } - - - func tableView(_ tableView: \ No newline at end of file + import ZIPFoundation +  + + class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource, FileHandlingDelegate { +  + +  // MARK: - Properties +  private var fileList: [String] = +  private var filteredFileList: [String] = +  private let fileManager = FileManager.default +  private let searchController = UISearchController(searchResultsController: nil) +  private var sortOrder: SortOrder = .name +  let fileHandlers = HomeViewFileHandlers() +  let utilities = HomeViewUtilities() +  + +  var documentsDirectory: URL { +  let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") +  createFilesDirectoryIfNeeded(at: directory) +  return directory +  } +  + +  enum SortOrder { +  case name, date, size +  } +  + +   +  let fileListTableView = UITableView() +  let activityIndicator = UIActivityIndicatorView(style: .large) +  + +  // MARK: - Lifecycle +  override func viewDidLoad() { +  super.viewDidLoad() +  setupUI() +  setupActivityIndicator() +  loadFiles() +  configureTableView() +  } +  + +  // MARK: - UI Setup +  private func setupUI() { +  view.backgroundColor = .systemBackground +  + +  let navItem = UINavigationItem(title: "Files") +  let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) +  let uploadButton = UIBarButtonItem(image: UIImage(systemName: "arrow.up.doc"), style: .plain, target: self, action: #selector(uploadFile)) +  navItem.rightBarButtonItems = [menuButton, uploadButton] +  let navBar = UINavigationBar() +  navBar.translatesAutoresizingMaskIntoConstraints = false +  navBar.barTintColor = .systemBlue +  navBar.titleTextAttributes = [.foregroundColor: UIColor.white] +  navBar.setItems([navItem], animated: false) +  view.addSubview(navBar) +  + +  searchController.searchResultsUpdater = self +  searchController.obscuresBackgroundDuringPresentation = false +  searchController.searchBar.placeholder = "Search Files" +  navigationItem.searchController = searchController +  definesPresentationContext = true +  + +  fileListTableView.translatesAutoresizingMaskIntoConstraints = false +  fileListTableView.separatorStyle = .singleLine +  fileListTableView.rowHeight = UITableView.automaticDimension +  fileListTableView.estimatedRowHeight = 44 +  view.addSubview(fileListTableView) +  + +  NSLayoutConstraint.activate([ +  navBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), +  navBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), +  navBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), +  + +  fileListTableView.topAnchor.constraint(equalTo: navBar.bottomAnchor), +  fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), +  fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), +  fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) +  ]) +  } +  + +  private func setupActivityIndicator() { +  activityIndicator.translatesAutoresizingMaskIntoConstraints = false +  activityIndicator.hidesWhenStopped = true +  activityIndicator.color = .systemBlue +   +  view.addSubview(activityIndicator) +  + +  NSLayoutConstraint.activate([ +  activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), +  activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) +  ]) +  } +  + +  private func createFilesDirectoryIfNeeded(at directory: URL) { +  if !fileManager.fileExists(atPath: directory.path) { +  do { +  try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) +  } catch { +  utilities.handleError(in: self, error: error, withTitle: "Error Creating Directory") +  } +  } +  } +  + +  private func configureTableView() { +  fileListTableView.delegate = self +  fileListTableView.dataSource = self +  fileListTableView.dragDelegate = self +  fileListTableView.dropDelegate = self +  searchController.searchResultsUpdater = self +  navigationItem.searchController = searchController +  definesPresentationContext = true +  fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") +  } +  + +  //  +  // MARK: - File Operations +  func loadFiles() { +  activityIndicator.startAnimating() +  fileList = +  DispatchQueue.global().async { [weak self] in +  guard let self = self else { return } +  do { +  let files = try self.fileManager.contentsOfDirectory(atPath: self.documentsDirectory.path) +  self.fileList = files.filter { !$0.hasPrefix(".") } +  self.sortFiles() +  DispatchQueue.main.async { +  self.filteredFileList = self.fileList +  self.fileListTableView.reloadData() +  self.activityIndicator.stopAnimating() +  } +  } catch { +  DispatchQueue.main.async { +  self.activityIndicator.stopAnimating() +  self.utilities.handleError(in: self, error: error, withTitle: "Error Loading Files") +  } +  } +  } +  + +  func sortFiles() { +  guard !fileList.isEmpty else { return } +  switch sortOrder { +  case .name: +  fileList.sort { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } +  case .date: +  fileList.sort { +  let url1 = documentsDirectory.appendingPathComponent($0) +  let url2 = documentsDirectory.appendingPathComponent($1) +  if let date1 = try? +  fileManager.attributesOfItem(atPath: url1.path).fileModificationDate, +  let date2 = try? fileManager.attributesOfItem(atPath: url2.path).fileModificationDate { +  return date1 < date2 +  } +  return false +  } +  case .size: +  fileList.sort { +  let url1 = documentsDirectory.appendingPathComponent($0) +  let url2 = documentsDirectory.appendingPathComponent($1) +  if let size1 = try? +  fileManager.attributesOfItem(atPath: url1.path).fileSize, +  let size2 = try? fileManager.attributesOfItem(atPath: url2.path).fileSize { +  return size1 < size2 +  } +  return false +  } +  } +  } +  + +  func showFileOptions(for fileURL: URL) { +  let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) +  + +  let openAction = UIAlertAction(title: "Open", style: .default) { [weak self] _ in +  self?.openFile(at: fileURL) +  } +  alertController.addAction(openAction) +  + +  let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self] _ in +  self?.showRenameDialog(for: fileURL) +  } +  alertController.addAction(renameAction) +  + +  let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in +  self?.deleteFile(at: fileURL) +  } +  alertController.addAction(deleteAction) +  + +  if fileURL.pathExtension == "zip" { +  let unzipAction = UIAlertAction(title: "Unzip", style: .default) { [weak self] _ in +  self?.unzipFile(at: fileURL) +  } +  alertController.addAction(unzipAction) +  } +  + +  let shareAction = UIAlertAction(title: "Share", style: .default) { [weak self] _ in +  self?.fileHandlers.shareFile(viewController: self, fileURL: fileURL) +  } +  alertController.addAction(shareAction) +  + +  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) +  alertController.addAction(cancelAction) +  + +  present(alertController, animated: true, completion: nil) +  } +  + +  func openFile(at fileURL: URL) { +  if ["txt", "md", "swift", "js", "html", "css"].contains(fileURL.pathExtension) { +  let textEditorVC =  +  TextEditorViewController(fileURL: fileURL) +  navigationController?.pushViewController(textEditorVC, animated: true) +  } else if fileURL.pathExtension == "plist" { +  let plistEditorVC = PlistEditorViewController(fileURL: fileURL) +  navigationController?.pushViewController(plistEditorVC, animated: true) +  } else { +  UIDocumentInteractionController(url: fileURL).presentPreview(animated: true) +  } +  } +  + +  func deleteFile(at fileURL: URL) { +  fileHandlers.deleteFile(viewController: self, fileURL: fileURL) { result in +  switch result { +  case .success(_): +  break +  case .failure(let error): +  self.utilities.handleError(in: self, error: error, withTitle: "Error Deleting File") +  } +  } +  } +  + +  func unzipFile(at fileURL: URL) { +  let destinationName = fileURL.deletingPathExtension().lastPathComponent +  let destinationURL = documentsDirectory.appendingPathComponent(destinationName) +  + +  let progress = Progress(totalUnitCount: 100) // Initialize Progress +   +  do { +  let archive = try Archive(url: fileURL, accessMode: .read) +   +  try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) +   +  try archive.extractAll(to: destinationURL, progress: progress) { entry in +  // Update progress here based on the entry, if needed. +  // ZIPFoundation doesn't give a simple percentage, but you can +  // track the number of files or bytes processed if you need more granular progress. +  // For now, we rely on ZIPFoundation's internal progress updates. +  print("Extracted: \(entry.path)") // You can log this or use it for more complex progress updates +  } +   +  // Handle completion on the main thread +  DispatchQueue.main.async { +  self.loadFiles() // Reload files to show the unzipped content +  } +   +  } catch { +  // Handle errors on the main thread +  DispatchQueue.main.async { +  self.utilities.handleError(in: self, error: error, withTitle: "Error Unzipping File") +  } +  } +  } +  + +  func showRenameDialog(for fileURL: URL) { +  let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) +  alertController.addTextField() +  let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self] _ in +  guard let self = self, let newName = alertController.textFields?.first?.text, !newName.isEmpty else { return } +  let newURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) +  do { +  try FileManager.default.moveItem(at: fileURL, to: newURL) +  self.loadFiles() +  } catch { +  self.utilities.handleError(in: self, error: error, withTitle: "Error Renaming File") +  } +  } +  alertController.addAction(renameAction) +  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) +  alertController.addAction(cancelAction) +  present(alertController, animated: true, completion: nil) +  } +  + +  // MARK: - UI Actions +  + +  @objc func uploadFile() { +  fileHandlers.uploadFile(viewController: self) +  } +  + +  @objc func showMenu() { +  let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) +  + +  let newFolderAction = UIAlertAction(title: "New Folder", style: .default) { [weak self] _ in +  self?.showNewFolderDialog() +  } +  alertController.addAction(newFolderAction) +  + +  let newFileAction = UIAlertAction(title: "New File", style: .default) { [weak self] _ in +  self?.showNewFileDialog() +  } +  alertController.addAction(newFileAction) +  + +  let sortAction = UIAlertAction(title: "Sort", style: .default) { [weak self] _ in +  self?.showSortOptions() +  } +  alertController.addAction(sortAction) +  + +  let importAction = UIAlertAction(title: "Import File", style: .default) { [weak self] _ in +  self?.fileHandlers.importFile(viewController: self) +  } +  alertController.addAction(importAction) +  + +  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) +  alertController.addAction(cancelAction) +  + +  present(alertController, animated: true, completion: nil) +  } +  + +  /// Shows a dialog to create a new folder. +  func showNewFolderDialog() { +  let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) +  alertController.addTextField() +  let createAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in +  guard let self = self, let folderName = alertController.textFields?.first?.text, !folderName.isEmpty else { return } +  self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) { result in +  switch result { +  case .success(_): +  break // No extra handling needed, folder created and list reloaded +  case .failure(let error): +  self.utilities.handleError(in: self, error: error, withTitle: "Error Creating Folder") +  } +  } +  } +  alertController.addAction(createAction) +  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) +  alertController.addAction(cancelAction) +  present(alertController, animated: true, completion: nil) +  } +  + +  /// Shows a dialog to create a new file. +  func showNewFileDialog() { +  let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) +  alertController.addTextField() +  let createAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in +  guard let self = self, let fileName = alertController.textFields?.first?.text, !fileName.isEmpty else { return } +  self.fileHandlers.createNewFile(viewController: self, fileName: fileName) { result in +  switch result { +  case .success(_): +  break // No extra handling needed, file created and list reloaded +  case .failure(let error): +  self.utilities.handleError(in: self, error: error, withTitle: "Error Creating File") +  } +  } +  } +  alertController.addAction(createAction) +  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) +  alertController.addAction(cancelAction) +  present(alertController, animated: true, completion: nil) +  } +  + +  func showSortOptions() { +  let alertController = UIAlertController(title: "Sort by", message: nil, preferredStyle: .actionSheet) +  + +  let sortByNameAction = UIAlertAction(title: "Name", style: .default) { [weak self] _ in +  self?.sortOrder = .name +  self?.sortFiles() +  self?.fileListTableView.reloadData() +  } +  alertController.addAction(sortByNameAction) +  + +  let sortByDateAction = UIAlertAction(title: "Date", style: .default) { [weak self] _ in +  self?.sortOrder = .date +  self?.sortFiles() +  self?.fileListTableView.reloadData() +  } +  alertController.addAction(sortByDateAction) +  + +  let sortBySizeAction = UIAlertAction(title: "Size", style: .default) { [weak self] _ in +  self?.sortOrder = .size +  self?.sortFiles() +  self?.fileListTableView.reloadData() +  } +  alertController.addAction(sortBySizeAction) +  + +  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) +  alertController.addAction(cancelAction) +  + +  present(alertController, animated: true, completion: nil) +  } +  + +  // MARK: - UISearchResultsUpdating +  func updateSearchResults(for searchController: UISearchController) { +  guard let searchText = searchController.searchBar.text else { return } +  filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } +  fileListTableView.reloadData() +  } +  + +  // MARK: - UITableViewDragDelegate +  func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { +  let item = self.fileList[indexPath.row] +  let itemProvider = NSItemProvider(object: item as NSString) +  let dragItem = UIDragItem(itemProvider: itemProvider) +  return [dragItem] +  } +  + +  // MARK: - UITableViewDropDelegate +  func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { +  coordinator.session.loadObjects(ofClass: NSString.self) { items in +  guard let string = items.first as? String else { return } +  self.fileList.append(string as String) +  self.fileListTableView.reloadData() +  } +  } +  + +  func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { +  return session.canLoadObjects(ofClass: NSString.self) +  } From 49ea69af84a8180563e6a517e8ec7dde815a4b5a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 03:30:58 -0400 Subject: [PATCH 250/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 35ce3998..e24b07a0 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,5 +1,5 @@ import UIKit - import ZIPFoundation +import ZIPFoundation    class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource, FileHandlingDelegate { From 299bf5a563d8a9c18a3386b038e620fbab4037b0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 04:37:51 -0400 Subject: [PATCH 251/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 778 ++++++++++-------------- 1 file changed, 319 insertions(+), 459 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index e24b07a0..8b3dd525 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,461 +1,321 @@ import UIKit import ZIPFoundation -  - - class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource, FileHandlingDelegate { -  - -  // MARK: - Properties -  private var fileList: [String] = -  private var filteredFileList: [String] = -  private let fileManager = FileManager.default -  private let searchController = UISearchController(searchResultsController: nil) -  private var sortOrder: SortOrder = .name -  let fileHandlers = HomeViewFileHandlers() -  let utilities = HomeViewUtilities() -  - -  var documentsDirectory: URL { -  let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") -  createFilesDirectoryIfNeeded(at: directory) -  return directory -  } -  - -  enum SortOrder { -  case name, date, size -  } -  - -   -  let fileListTableView = UITableView() -  let activityIndicator = UIActivityIndicatorView(style: .large) -  - -  // MARK: - Lifecycle -  override func viewDidLoad() { -  super.viewDidLoad() -  setupUI() -  setupActivityIndicator() -  loadFiles() -  configureTableView() -  } -  - -  // MARK: - UI Setup -  private func setupUI() { -  view.backgroundColor = .systemBackground -  - -  let navItem = UINavigationItem(title: "Files") -  let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) -  let uploadButton = UIBarButtonItem(image: UIImage(systemName: "arrow.up.doc"), style: .plain, target: self, action: #selector(uploadFile)) -  navItem.rightBarButtonItems = [menuButton, uploadButton] -  let navBar = UINavigationBar() -  navBar.translatesAutoresizingMaskIntoConstraints = false -  navBar.barTintColor = .systemBlue -  navBar.titleTextAttributes = [.foregroundColor: UIColor.white] -  navBar.setItems([navItem], animated: false) -  view.addSubview(navBar) -  - -  searchController.searchResultsUpdater = self -  searchController.obscuresBackgroundDuringPresentation = false -  searchController.searchBar.placeholder = "Search Files" -  navigationItem.searchController = searchController -  definesPresentationContext = true -  - -  fileListTableView.translatesAutoresizingMaskIntoConstraints = false -  fileListTableView.separatorStyle = .singleLine -  fileListTableView.rowHeight = UITableView.automaticDimension -  fileListTableView.estimatedRowHeight = 44 -  view.addSubview(fileListTableView) -  - -  NSLayoutConstraint.activate([ -  navBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), -  navBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), -  navBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), -  - -  fileListTableView.topAnchor.constraint(equalTo: navBar.bottomAnchor), -  fileListTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), -  fileListTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), -  fileListTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) -  ]) -  } -  - -  private func setupActivityIndicator() { -  activityIndicator.translatesAutoresizingMaskIntoConstraints = false -  activityIndicator.hidesWhenStopped = true -  activityIndicator.color = .systemBlue -   -  view.addSubview(activityIndicator) -  - -  NSLayoutConstraint.activate([ -  activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), -  activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) -  ]) -  } -  - -  private func createFilesDirectoryIfNeeded(at directory: URL) { -  if !fileManager.fileExists(atPath: directory.path) { -  do { -  try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) -  } catch { -  utilities.handleError(in: self, error: error, withTitle: "Error Creating Directory") -  } -  } -  } -  - -  private func configureTableView() { -  fileListTableView.delegate = self -  fileListTableView.dataSource = self -  fileListTableView.dragDelegate = self -  fileListTableView.dropDelegate = self -  searchController.searchResultsUpdater = self -  navigationItem.searchController = searchController -  definesPresentationContext = true -  fileListTableView.register(FileTableViewCell.self, forCellReuseIdentifier: "FileCell") -  } -  - -  //  -  // MARK: - File Operations -  func loadFiles() { -  activityIndicator.startAnimating() -  fileList = -  DispatchQueue.global().async { [weak self] in -  guard let self = self else { return } -  do { -  let files = try self.fileManager.contentsOfDirectory(atPath: self.documentsDirectory.path) -  self.fileList = files.filter { !$0.hasPrefix(".") } -  self.sortFiles() -  DispatchQueue.main.async { -  self.filteredFileList = self.fileList -  self.fileListTableView.reloadData() -  self.activityIndicator.stopAnimating() -  } -  } catch { -  DispatchQueue.main.async { -  self.activityIndicator.stopAnimating() -  self.utilities.handleError(in: self, error: error, withTitle: "Error Loading Files") -  } -  } -  } -  - -  func sortFiles() { -  guard !fileList.isEmpty else { return } -  switch sortOrder { -  case .name: -  fileList.sort { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } -  case .date: -  fileList.sort { -  let url1 = documentsDirectory.appendingPathComponent($0) -  let url2 = documentsDirectory.appendingPathComponent($1) -  if let date1 = try? -  fileManager.attributesOfItem(atPath: url1.path).fileModificationDate, -  let date2 = try? fileManager.attributesOfItem(atPath: url2.path).fileModificationDate { -  return date1 < date2 -  } -  return false -  } -  case .size: -  fileList.sort { -  let url1 = documentsDirectory.appendingPathComponent($0) -  let url2 = documentsDirectory.appendingPathComponent($1) -  if let size1 = try? -  fileManager.attributesOfItem(atPath: url1.path).fileSize, -  let size2 = try? fileManager.attributesOfItem(atPath: url2.path).fileSize { -  return size1 < size2 -  } -  return false -  } -  } -  } -  - -  func showFileOptions(for fileURL: URL) { -  let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) -  - -  let openAction = UIAlertAction(title: "Open", style: .default) { [weak self] _ in -  self?.openFile(at: fileURL) -  } -  alertController.addAction(openAction) -  - -  let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self] _ in -  self?.showRenameDialog(for: fileURL) -  } -  alertController.addAction(renameAction) -  - -  let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in -  self?.deleteFile(at: fileURL) -  } -  alertController.addAction(deleteAction) -  - -  if fileURL.pathExtension == "zip" { -  let unzipAction = UIAlertAction(title: "Unzip", style: .default) { [weak self] _ in -  self?.unzipFile(at: fileURL) -  } -  alertController.addAction(unzipAction) -  } -  - -  let shareAction = UIAlertAction(title: "Share", style: .default) { [weak self] _ in -  self?.fileHandlers.shareFile(viewController: self, fileURL: fileURL) -  } -  alertController.addAction(shareAction) -  - -  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) -  alertController.addAction(cancelAction) -  - -  present(alertController, animated: true, completion: nil) -  } -  - -  func openFile(at fileURL: URL) { -  if ["txt", "md", "swift", "js", "html", "css"].contains(fileURL.pathExtension) { -  let textEditorVC =  -  TextEditorViewController(fileURL: fileURL) -  navigationController?.pushViewController(textEditorVC, animated: true) -  } else if fileURL.pathExtension == "plist" { -  let plistEditorVC = PlistEditorViewController(fileURL: fileURL) -  navigationController?.pushViewController(plistEditorVC, animated: true) -  } else { -  UIDocumentInteractionController(url: fileURL).presentPreview(animated: true) -  } -  } -  - -  func deleteFile(at fileURL: URL) { -  fileHandlers.deleteFile(viewController: self, fileURL: fileURL) { result in -  switch result { -  case .success(_): -  break -  case .failure(let error): -  self.utilities.handleError(in: self, error: error, withTitle: "Error Deleting File") -  } -  } -  } -  - -  func unzipFile(at fileURL: URL) { -  let destinationName = fileURL.deletingPathExtension().lastPathComponent -  let destinationURL = documentsDirectory.appendingPathComponent(destinationName) -  - -  let progress = Progress(totalUnitCount: 100) // Initialize Progress -   -  do { -  let archive = try Archive(url: fileURL, accessMode: .read) -   -  try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) -   -  try archive.extractAll(to: destinationURL, progress: progress) { entry in -  // Update progress here based on the entry, if needed. -  // ZIPFoundation doesn't give a simple percentage, but you can -  // track the number of files or bytes processed if you need more granular progress. -  // For now, we rely on ZIPFoundation's internal progress updates. -  print("Extracted: \(entry.path)") // You can log this or use it for more complex progress updates -  } -   -  // Handle completion on the main thread -  DispatchQueue.main.async { -  self.loadFiles() // Reload files to show the unzipped content -  } -   -  } catch { -  // Handle errors on the main thread -  DispatchQueue.main.async { -  self.utilities.handleError(in: self, error: error, withTitle: "Error Unzipping File") -  } -  } -  } -  - -  func showRenameDialog(for fileURL: URL) { -  let alertController = UIAlertController(title: "Rename File", message: "Enter new file name", preferredStyle: .alert) -  alertController.addTextField() -  let renameAction = UIAlertAction(title: "Rename", style: .default) { [weak self] _ in -  guard let self = self, let newName = alertController.textFields?.first?.text, !newName.isEmpty else { return } -  let newURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) -  do { -  try FileManager.default.moveItem(at: fileURL, to: newURL) -  self.loadFiles() -  } catch { -  self.utilities.handleError(in: self, error: error, withTitle: "Error Renaming File") -  } -  } -  alertController.addAction(renameAction) -  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) -  alertController.addAction(cancelAction) -  present(alertController, animated: true, completion: nil) -  } -  - -  // MARK: - UI Actions -  - -  @objc func uploadFile() { -  fileHandlers.uploadFile(viewController: self) -  } -  - -  @objc func showMenu() { -  let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) -  - -  let newFolderAction = UIAlertAction(title: "New Folder", style: .default) { [weak self] _ in -  self?.showNewFolderDialog() -  } -  alertController.addAction(newFolderAction) -  - -  let newFileAction = UIAlertAction(title: "New File", style: .default) { [weak self] _ in -  self?.showNewFileDialog() -  } -  alertController.addAction(newFileAction) -  - -  let sortAction = UIAlertAction(title: "Sort", style: .default) { [weak self] _ in -  self?.showSortOptions() -  } -  alertController.addAction(sortAction) -  - -  let importAction = UIAlertAction(title: "Import File", style: .default) { [weak self] _ in -  self?.fileHandlers.importFile(viewController: self) -  } -  alertController.addAction(importAction) -  - -  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) -  alertController.addAction(cancelAction) -  - -  present(alertController, animated: true, completion: nil) -  } -  - -  /// Shows a dialog to create a new folder. -  func showNewFolderDialog() { -  let alertController = UIAlertController(title: "New Folder", message: "Enter folder name", preferredStyle: .alert) -  alertController.addTextField() -  let createAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in -  guard let self = self, let folderName = alertController.textFields?.first?.text, !folderName.isEmpty else { return } -  self.fileHandlers.createNewFolder(viewController: self, folderName: folderName) { result in -  switch result { -  case .success(_): -  break // No extra handling needed, folder created and list reloaded -  case .failure(let error): -  self.utilities.handleError(in: self, error: error, withTitle: "Error Creating Folder") -  } -  } -  } -  alertController.addAction(createAction) -  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) -  alertController.addAction(cancelAction) -  present(alertController, animated: true, completion: nil) -  } -  - -  /// Shows a dialog to create a new file. -  func showNewFileDialog() { -  let alertController = UIAlertController(title: "New File", message: "Enter file name", preferredStyle: .alert) -  alertController.addTextField() -  let createAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in -  guard let self = self, let fileName = alertController.textFields?.first?.text, !fileName.isEmpty else { return } -  self.fileHandlers.createNewFile(viewController: self, fileName: fileName) { result in -  switch result { -  case .success(_): -  break // No extra handling needed, file created and list reloaded -  case .failure(let error): -  self.utilities.handleError(in: self, error: error, withTitle: "Error Creating File") -  } -  } -  } -  alertController.addAction(createAction) -  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) -  alertController.addAction(cancelAction) -  present(alertController, animated: true, completion: nil) -  } -  - -  func showSortOptions() { -  let alertController = UIAlertController(title: "Sort by", message: nil, preferredStyle: .actionSheet) -  - -  let sortByNameAction = UIAlertAction(title: "Name", style: .default) { [weak self] _ in -  self?.sortOrder = .name -  self?.sortFiles() -  self?.fileListTableView.reloadData() -  } -  alertController.addAction(sortByNameAction) -  - -  let sortByDateAction = UIAlertAction(title: "Date", style: .default) { [weak self] _ in -  self?.sortOrder = .date -  self?.sortFiles() -  self?.fileListTableView.reloadData() -  } -  alertController.addAction(sortByDateAction) -  - -  let sortBySizeAction = UIAlertAction(title: "Size", style: .default) { [weak self] _ in -  self?.sortOrder = .size -  self?.sortFiles() -  self?.fileListTableView.reloadData() -  } -  alertController.addAction(sortBySizeAction) -  - -  let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) -  alertController.addAction(cancelAction) -  - -  present(alertController, animated: true, completion: nil) -  } -  - -  // MARK: - UISearchResultsUpdating -  func updateSearchResults(for searchController: UISearchController) { -  guard let searchText = searchController.searchBar.text else { return } -  filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } -  fileListTableView.reloadData() -  } -  - -  // MARK: - UITableViewDragDelegate -  func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { -  let item = self.fileList[indexPath.row] -  let itemProvider = NSItemProvider(object: item as NSString) -  let dragItem = UIDragItem(itemProvider: itemProvider) -  return [dragItem] -  } -  - -  // MARK: - UITableViewDropDelegate -  func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { -  coordinator.session.loadObjects(ofClass: NSString.self) { items in -  guard let string = items.first as? String else { return } -  self.fileList.append(string as String) -  self.fileListTableView.reloadData() -  } -  } -  - -  func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { -  return session.canLoadObjects(ofClass: NSString.self) -  } + +class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { + + // MARK: - Properties + private var fileList: [String] = + private var filteredFileList: [String] = + private let fileManager = FileManager.default + private let searchController = UISearchController(searchResultsController: nil) + private var sortOrder: SortOrder = .name + let fileHandlers = HomeViewFileHandlers() + let utilities = HomeViewUtilities() + + var documentsDirectory: URL { + let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("files") + createFilesDirectoryIfNeeded(at: directory) + return directory + } + + enum SortOrder { + case name, date, size + } + + let fileListTableView = UITableView() + let activityIndicator = UIActivityIndicatorView(style: .large) + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + setupActivityIndicator() + loadFiles() + configureTableView() + } + + // MARK: - UI Setup + private func setupUI() { + view.backgroundColor = .systemBackground + + let navItem = UINavigationItem(title: "Files") + let menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .plain, target: self, action: #selector(showMenu)) + let uploadButton = UIBarButtonItem(image: UIImage(systemName: "square.and.arrow.up"), style: .plain, target: self, action: #selector(importFile)) + let addButton = UIBarButtonItem(image: UIImage(systemName: "folder.badge.plus"), style: .plain, target: self, action: #selector(addDirectory)) // Add Directory button + + navItem.rightBarButtonItems = [menuButton, uploadButton, addButton] + navigationController?.navigationBar.setItems([navItem], animated: false) + + searchController.searchResultsUpdater = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.searchBar.placeholder = "Search Files" + navigationItem.searchController = searchController + definesPresentationContext = true + + view.addSubview(fileListTableView) + fileListTableView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + fileListTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + fileListTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + fileListTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + fileListTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) + } + + private func setupActivityIndicator() { + view.addSubview(activityIndicator) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + } + + private func configureTableView() { + fileListTableView.delegate = self + fileListTableView.dataSource = self + fileListTableView.dragDelegate = self + fileListTableView.dropDelegate = self + fileListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "fileCell") + } + + private func createFilesDirectoryIfNeeded(at directory: URL) { + if !fileManager.fileExists(atPath: directory.path) { + do { + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + } catch { + print("Error creating directory: \(error)") + } + } + } + + // MARK: - File Operations + func loadFiles() { + activityIndicator.startAnimating() + DispatchQueue.global().async { [weak self] in + guard let self = self else { return } + do { + let files = try self.fileManager.contentsOfDirectory(atPath: self.documentsDirectory.path) + DispatchQueue.main.async { + self.fileList = files + self.sortFiles() + self.fileListTableView.reloadData() + self.activityIndicator.stopAnimating() + } + } catch { + print("Error loading files: \(error)") + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + } + } + } + } + + @objc private func importFile() { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.zip, .item]) + documentPicker.delegate = self + documentPicker.allowsMultipleSelection = false + present(documentPicker, animated: true, completion: nil) + } + + func handleImportedFile(url: URL) { + let destinationURL = documentsDirectory.appendingPathComponent(url.lastPathComponent) + + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + guard let self = self else { return } + + do { + if url.startAccessingSecurityScopedResource() { + if url.pathExtension == "zip" { + try self.fileManager.unzipItem(at: url, to: destinationURL) + } else { + try self.fileManager.copyItem(at: url, to: destinationURL) + } + url.stopAccessingSecurityScopedResource() + + DispatchQueue.main.async { + self.loadFiles() + } + } + } catch { + print("Error handling file: \(error)") + } + } + } + + func deleteFile(at index: Int) { + let fileToDelete = fileList[index] + let fileURL = documentsDirectory.appendingPathComponent(fileToDelete) + + do { + try fileManager.removeItem(at: fileURL) + fileList.remove(at: index) + fileListTableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .fade) + } catch { + print("Error deleting file: \(error)") + } + } + + func sortFiles() { + switch sortOrder { + case .name: + fileList.sort { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } + case .date: + // Need to implement file date retrieval and sorting + break + case .size: + // Need to implement file size retrieval and sorting + break + } + } + + // MARK: - UI Actions + @objc private func showMenu() { + let alertController = UIAlertController(title: "Sort By", message: nil, preferredStyle: .actionSheet) + + let sortByNameAction = UIAlertAction(title: "Name", style: .default) { [weak self] _ in + self?.sortOrder = .name + self?.sortFiles() + self?.fileListTableView.reloadData() + } + alertController.addAction(sortByNameAction) + + let sortByDateAction = UIAlertAction(title: "Date", style: .default) { [weak self] _ in + self?.sortOrder = .date + self?.sortFiles() + self?.fileListTableView.reloadData() + } + alertController.addAction(sortByDateAction) + + let sortBySizeAction = UIAlertAction(title: "Size", style: .default) { [weak self] _ in + self?.sortOrder = .size + self?.sortFiles() + self?.fileListTableView.reloadData() + } + alertController.addAction(sortBySizeAction) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + present(alertController, animated: true, completion: nil) + } + + // MARK: - UISearchResultsUpdating + func updateSearchResults(for searchController: UISearchController) { + guard let searchText = searchController.searchBar.text else { return } + filteredFileList = fileList.filter { $0.localizedCaseInsensitiveContains(searchText) } + fileListTableView.reloadData() + } + + // MARK: - UITableViewDragDelegate + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let item = self.fileList[indexPath.row] + let itemProvider = NSItemProvider(object: item as NSString) + let dragItem = UIDragItem(itemProvider: itemProvider) + return [dragItem] + } + + // MARK: - UITableViewDropDelegate + func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { + coordinator.session.loadObjects(ofClass: NSString.self) { items in + guard let string = items.first as? String else { return } + self.fileList.append(string as String) + self.fileListTableView.reloadData() + } + } + + func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { + return true + } + + // MARK: - UITableViewDelegate, UITableViewDataSource + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if searchController.isActive && searchController.searchBar.text != "" { + return filteredFileList.count + } + return fileList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "fileCell", for: indexPath) + let fileName: String + if searchController.isActive && searchController.searchBar.text != "" { + fileName = filteredFileList[indexPath.row] + } else { + fileName = fileList[indexPath.row] + } + cell.textLabel?.text = fileName + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let fileName: String + if searchController.isActive && searchController.searchBar.text != "" { + fileName = filteredFileList[indexPath.row] + } else { + fileName = fileList[indexPath.row] + } + + let fileURL = documentsDirectory.appendingPathComponent(fileName) + + let activityViewController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + present(activityViewController, animated: true, completion: nil) + } + + @objc private func addDirectory() { + let alertController = UIAlertController(title: "Add Directory", message: "Enter the name of the new directory", preferredStyle: .alert) + alertController.addTextField { (textField) in + textField.placeholder = "Directory Name" + } + + let createAction = UIAlertAction(title: "Create", style: .default) { [weak self] _ in + guard let textField = alertController.textFields?.first, let directoryName = textField.text, !directoryName.isEmpty else { return } + + let newDirectoryURL = self?.documentsDirectory.appendingPathComponent(directoryName) + + do { + try self?.fileManager.createDirectory(at: newDirectoryURL!, withIntermediateDirectories: false, attributes: nil) + self?.loadFiles() + } catch { + print("Error creating directory: \(error)") + } + } + alertController.addAction(createAction) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + present(alertController, animated: true, completion: nil) + } +} + +// MARK: - Extensions +extension HomeViewController: UIDocumentPickerDelegate { + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let selectedFileURL = urls.first else { + return + } + + handleImportedFile(url: selectedFileURL) + } +} + +extension FileManager { + func fileSize(at path: String) -> UInt64? { + do { + let attr = try FileManager.default.attributesOfItem(atPath: path) + return attr.fileSize() + } catch { + return nil + } + } + + func creationDate(at path: String) -> Date? { + do { + let attr = try FileManager.default.attributesOfItem(atPath: path) + return attr.fileCreationDate() + } catch { + return nil + } + } +} \ No newline at end of file From 22c8bb9e34517e85d8e1e5f6f3cbe8fd5bf3e242 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 04:50:13 -0400 Subject: [PATCH 252/391] Update AppDelegate.swift --- iOS/Delegates/AppDelegate.swift | 79 +++++++++++++++++---------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/iOS/Delegates/AppDelegate.swift b/iOS/Delegates/AppDelegate.swift index 45ded7fc..d4b7d1a7 100644 --- a/iOS/Delegates/AppDelegate.swift +++ b/iOS/Delegates/AppDelegate.swift @@ -1,11 +1,3 @@ -// -// AppDelegate.swift -// feather -// -// Created by samara on 5/17/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import BackgroundTasks import CoreData import Foundation @@ -18,7 +10,7 @@ var downloadTaskManager = DownloadTaskManager.shared class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControllerDelegate { static let isSideloaded = Bundle.main.bundleIdentifier != "com.bdg.backdoor" var window: UIWindow? - var loaderAlert = presentLoader() + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let userDefaults = UserDefaults.standard @@ -29,9 +21,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle userDefaults.signingOptions = UserDefaults.defaultSigningData } - createSourcesDirectory() + createSourcesDirectory() addDefaultRepos() - giveUserDefaultSSLCerts() + giveUserDefaultSSLCerts() imagePipline() setupLogFile() cleanTmp() @@ -64,18 +56,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle Debug.shared.log(message: "Model: \(UIDevice.current.model)") Debug.shared.log(message: "Backdoor Version: \(logAppVersionInfo())\n") - if Preferences.appUpdates { - // Register background task - BGTaskScheduler.shared.register(forTaskWithIdentifier: "kh.crysalis.feather.sourcerefresh", using: nil) { task in - self.handleAppRefresh(task: task as! BGAppRefreshTask) - } - scheduleAppRefresh() - - let backgroundQueue = OperationQueue() - backgroundQueue.qualityOfService = .background - let operation = SourceRefreshOperation() - backgroundQueue.addOperation(operation) - } + if Preferences.appUpdates { + // Register background task + BGTaskScheduler.shared.register(forTaskWithIdentifier: "kh.crysalis.feather.sourcerefresh", using: nil) { task in + self.handleAppRefresh(task: task as! BGAppRefreshTask) + } + scheduleAppRefresh() + + let backgroundQueue = OperationQueue() + backgroundQueue.qualityOfService = .background + let operation = SourceRefreshOperation() + backgroundQueue.addOperation(operation) + } return true } @@ -146,7 +138,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle } DispatchQueue.main.async { - rootViewController.present(self.loaderAlert, animated: true) + rootViewController.present(self.presentLoader(), animated: true) } DispatchQueue.global(qos: .background).async { @@ -163,7 +155,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle try handleIPAFile(destinationURL: destinationURL, uuid: uuid, dl: dl) DispatchQueue.main.async { - self.loaderAlert.dismiss(animated: true) { + self.presentLoader().dismiss(animated: true) { let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) @@ -190,7 +182,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() + navigationController.shouldPresentFullScreen() rootViewController.present(navigationController, animated: true) } @@ -199,7 +191,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle } } catch { DispatchQueue.main.async { - self.loaderAlert.dismiss(animated: true) + self.presentLoader().dismiss(animated: true) Debug.shared.log(message: "Failed to handle IPA file: \(error)", type: .error) } } @@ -219,7 +211,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle } DispatchQueue.main.async { - rootViewController.present(self.loaderAlert, animated: true) + rootViewController.present(self.presentLoader(), animated: true) } DispatchQueue.global(qos: .background).async { @@ -234,12 +226,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle try handleIPAFile(destinationURL: destinationURL, uuid: uuid, dl: dl) DispatchQueue.main.async { - self.loaderAlert.dismiss(animated: true) + self.presentLoader().dismiss(animated: true) Debug.shared.log(message: "Moved IPA file to: \(destinationURL)") } } catch { DispatchQueue.main.async { - self.loaderAlert.dismiss(animated: true) + self.presentLoader().dismiss(animated: true) Debug.shared.log(message: "Failed to move IPA file: \(error)") } } @@ -278,13 +270,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle } } } - - fileprivate func giveUserDefaultSSLCerts() { - if !Preferences.gotSSLCerts { - getCertificates() - Preferences.gotSSLCerts = true - } - } + + fileprivate func giveUserDefaultSSLCerts() { + if !Preferences.gotSSLCerts { + getCertificates() + Preferences.gotSSLCerts = true + } + } fileprivate static func generateRandomString(length: Int = 8) -> String { let characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" @@ -363,6 +355,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle } return "" } + + func presentLoader() -> UIAlertController { + let alert = UIAlertController(title: "Loading...", message: nil, preferredStyle: .alert) + + let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) + loadingIndicator.style = .large + loadingIndicator.startAnimating() + + alert.view.addSubview(loadingIndicator) + return alert + } } extension UIOnboardingViewConfiguration { @@ -418,4 +421,4 @@ extension UIOnboardingViewConfiguration { buttonConfiguration: .init(title: String.localized("ONBOARDING_CONTINUE_BUTTON"), backgroundColor: .tintColor) ) } -} +} \ No newline at end of file From eee9d60a0f58c83abf935e003e6242425784f39b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 04:51:58 -0400 Subject: [PATCH 253/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 511 ++++++--------------- 1 file changed, 137 insertions(+), 374 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 1d444ab3..8c964e45 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -6,8 +6,8 @@ class LibraryViewController: UITableViewController { var signedApps: [SignedApps]? var downloadedApps: [DownloadedApps]? - var filteredSignedApps: [SignedApps] = [] - var filteredDownloadedApps: [DownloadedApps] = [] + var filteredSignedApps: [SignedApps] = + var filteredDownloadedApps: [DownloadedApps] = var installer: Installer? @@ -44,416 +44,120 @@ class LibraryViewController: UITableViewController { ) } - @objc private func handleInstallNotification(_ notification: Notification) { - guard let downloadedApp = notification.userInfo?["downloadedApp"] as? DownloadedApps else { return } - - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - signingDataWrapper.signingOptions.installAfterSigned = true - - let ap = SigningsViewController( - signingDataWrapper: signingDataWrapper, - application: downloadedApp, - appsViewController: self - ) - - ap.signingCompletionHandler = { success in - if success { - Debug.shared.log(message: "Signing completed successfully", type: .success) + @objc func handleInstallNotification(_ notification: Notification) { + if let userInfo = notification.userInfo, + let app = userInfo["app"] as? DownloadedApps { + + DispatchQueue.main.async { + self.startInstallProcess(meow: app, filePath: app.filePath ?? "") } } - - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - - present(navigationController, animated: true) - } - - deinit { - NotificationCenter.default.removeObserver(self, name: Notification.Name("lfetch"), object: nil) - NotificationCenter.default.removeObserver(self, name: Notification.Name("InstallDownloadedApp"), object: nil) } fileprivate func setupNavigation() { - self.navigationController?.navigationBar.prefersLargeTitles = true - self.title = String.localized("TAB_LIBRARY") - } - - private func handleAppUpdate(for signedApp: SignedApps) { - guard let sourceURL = signedApp.originalSourceURL else { - Debug.shared.log(message: "Missing update version or source URL", type: .error) - return - } - - Debug.shared.log(message: "Fetching update from source: \(sourceURL.absoluteString)", type: .info) - - present(loaderAlert!, animated: true) - - // Create mock source if in debug mode - if isDebugMode { - let mockSource = SourceRefreshOperation() - mockSource.createMockSource { mockSourceData in - if let sourceData = mockSourceData { - self.handleSourceData(sourceData, for: signedApp) - } else { - Debug.shared.log(message: "Failed to create mock source", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - } - } - } else { - // Normal source fetch - SourceGET().downloadURL(from: sourceURL) { [weak self] result in - guard let self = self else { return } - - switch result { - case .success((let data, _)): - if case .success(let sourceData) = SourceGET().parse(data: data) { - self.handleSourceData(sourceData, for: signedApp) - } else { - Debug.shared.log(message: "Failed to parse source data", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - } - case .failure(let error): - Debug.shared.log(message: "Failed to fetch source: \(error)", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - } - } - } - } - - private func handleSourceData(_ sourceData: SourcesData, for signedApp: SignedApps) { - guard let bundleId = signedApp.bundleidentifier, - let updateVersion = signedApp.updateVersion, - let app = sourceData.apps.first(where: { $0.bundleIdentifier == bundleId }), - let versions = app.versions else { - Debug.shared.log(message: "Failed to find app in source", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - return - } - - // Look for the version that matches our update version - for version in versions { - if version.version == updateVersion { - // Found the matching version - Debug.shared.log(message: "Found matching version: \(version.version)", type: .info) - - let uuid = UUID().uuidString - - DispatchQueue.global(qos: .background).async { - do { - let tempDirectory = FileManager.default.temporaryDirectory - let destinationURL = tempDirectory.appendingPathComponent("\(uuid).ipa") - - // Download the file - if let data = try? Data(contentsOf: version.downloadURL) { - try data.write(to: destinationURL) - - let dl = AppDownload() - try handleIPAFile(destinationURL: destinationURL, uuid: uuid, dl: dl) - - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) { - // Force Sign & Install - let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - signingDataWrapper.signingOptions.installAfterSigned = true - - // Store the original signed app for deletion after update - let originalSignedApp = signedApp - - let ap = SigningsViewController( - signingDataWrapper: signingDataWrapper, - application: downloadedApp, - appsViewController: self - ) - - // Add completion handler to delete the original app after successful signing - ap.signingCompletionHandler = { [weak self] success in - if success { - CoreDataManager.shared.deleteAllSignedAppContent(for: originalSignedApp) - self?.fetchSources() - self?.tableView.reloadData() - } - } - - let navigationController = UINavigationController(rootViewController: ap) - - navigationController.shouldPresentFullScreen() - - self.present(navigationController, animated: true) - } - } - } - } - } catch { - Debug.shared.log(message: "Failed to handle update: \(error)", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } - } - } - return - } - } - - Debug.shared.log(message: "Could not find version \(updateVersion) in source", type: .error) - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - } + navigationItem.title = "Library" + navigationController?.navigationBar.prefersLargeTitles = true } - private var isDebugMode: Bool { - var isDebug = false - assert({ - isDebug = true - return true - }()) - return isDebug + fileprivate func setupSearchController() { + searchController = UISearchController(searchResultsController: nil) + searchController.searchResultsUpdater = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.searchBar.placeholder = "Search Apps..." + navigationItem.searchController = searchController + definesPresentationContext = true } - private func resignApp(certificate: Certificate, appPath: URL, completion: @escaping (Bool) -> Void) { - // Implement resignApp functionality here + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 2 } -} -extension LibraryViewController { - override func numberOfSections(in tableView: UITableView) -> Int { return 2 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - switch section { - case 0: - return isFiltering ? filteredSignedApps.count : signedApps?.count ?? 0 - case 1: - return isFiltering ? filteredDownloadedApps.count : downloadedApps?.count ?? 0 - default: - return 0 + if isFiltering() { + return section == 0 ? filteredSignedApps.count : filteredDownloadedApps.count } + return section == 0 ? signedApps?.count ?? 0 : downloadedApps?.count ?? 0 } - - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - switch section { - case 0: - let headerWithButton = GroupedSectionHeader( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS"), - subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL", arguments: String(signedApps?.count ?? 0)), - buttonTitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_BUTTON_IMPORT"), - buttonAction: { - self.startImporting() - } - ) - return headerWithButton - case 1: - let headerWithButton = GroupedSectionHeader( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS"), - subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL", arguments: String(downloadedApps?.count ?? 0)) + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "RoundedBackgroundCell", for: indexPath) as! AppsTableViewCell + + let app = isFiltering() + ? (indexPath.section == 0 ? filteredSignedApps[indexPath.row] : filteredDownloadedApps[indexPath.row]) + : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) + + if let app = app { + cell.configure( + image: UIImage(systemName: "questionmark.app.dashed"), // Placeholder image + title: app.name ?? "Unknown App", + subtitle: app.bundleIdentifier ?? "Unknown Bundle ID" ) - - return headerWithButton - default: - return nil } + + return cell } - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = AppsTableViewCell(style: .subtitle, reuseIdentifier: "RoundedBackgroundCell") - cell.selectionStyle = .default - cell.accessoryType = .disclosureIndicator - cell.backgroundColor = .clear - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - - if let iconURL = source!.value(forKey: "iconURL") as? String { - let imagePath = filePath!.appendingPathComponent(iconURL) - - if let image = CoreDataManager.shared.loadImage(from: imagePath) { - SectionIcons.sectionImage(to: cell, with: image) - } else { - SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) - } - } else { - SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) - } - - cell.configure(with: source!, filePath: filePath!) - return cell + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return section == 0 ? "Signed Apps" : "Downloaded Apps" } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: true) - let filePath2 = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: false) - let appName = "\((source!.value(forKey: "name") as? String ?? ""))" - switch indexPath.section { - case 0: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet + tableView.deselectRow(at: indexPath, animated: true) + + let app = isFiltering() + ? (indexPath.section == 0 ? filteredSignedApps[indexPath.row] : filteredDownloadedApps[indexPath.row]) + : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) + + if let app = app { + popupVC = PopupViewController() + + if indexPath.section == 0 { + // Handle Signed Apps action + let button1 = PopupViewController.PopupButton( + title: "Sign", + style: .normal + ) { + self.startSigning(meow: app) + } + + popupVC.configureButtons([button1]) - let hasUpdate = (source as? SignedApps)?.value(forKey: "hasUpdate") as? Bool ?? false - if let signedApp = source as? SignedApps, - hasUpdate { - // Update available menu - let updateButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), - color: .tintColor.withAlphaComponent(0.9), - titleColor: .white - ) - updateButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - self.handleAppUpdate(for: signedApp) - } - } - - let clearButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE"), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - clearButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - CoreDataManager.shared.clearUpdateState(for: signedApp) - self.tableView.reloadRows(at: [indexPath], with: .none) - } - - popupVC.configureButtons([updateButton, clearButton]) - } else { - // Regular menu - let button1 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), - color: .tintColor.withAlphaComponent(0.9) - ) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - } - - let button4 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - button4.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - if let workspace = LSApplicationWorkspace.default() { - let success = workspace.openApplication(withBundleID: "\((source!.value(forKey: "bundleidentifier") as? String ?? ""))") - if !success { - Debug.shared.log(message: "Unable to open, do you have the app installed?", type: .warning) - } - } - } - - let button3 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - button3.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - if let cert = CoreDataManager.shared.getCurrentCertificate() { - self.present(self.loaderAlert!, animated: true) - - self.resignApp(certificate: cert, appPath: filePath2!) { success in - if success { - CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - Debug.shared.log(message: "Done action??") - self.tableView.reloadRows(at: [indexPath], with: .left) - } - } - } else { - // Handle resigning failure - self.loaderAlert?.dismiss(animated: true) - Debug.shared.log(message: "Failed to resign the app", type: .error) - } - } - } else { - let alert = UIAlertController( - title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), - message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) - self.present(alert, animated: true) - } - } - } - - let button2 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.shareFile(meow: source!, filePath: filePath?.path ?? "") - } - - popupVC.configureButtons([button1, button4, button3, button2]) - } - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: hasUpdate ? 150.0 : 270.0) if let presentationController = popupVC.presentationController as? UISheetPresentationController { presentationController.detents = [ - detent2, .medium() ] presentationController.prefersGrabberVisible = true } self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } - case 1: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet - let signingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let button1 = PopupViewControllerButton( - title: signingData.signingOptions.installAfterSigned - ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) - : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: .tintColor.withAlphaComponent(0.9) - ) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startSigning(meow: source!) - } + } else { + // Handle Downloaded Apps actions - let button2 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor - ) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { + let button1 = PopupViewController.PopupButton( + title: "Install", + style: .normal + ) { + + if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { + let alertController = UIAlertController( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), - message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), + title: "Install App", + message: "Are you sure you want to install this app?", preferredStyle: .alert ) - let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + let confirmAction = UIAlertAction( + title: "Install", + style: .default + ) { _ in + self.startInstallProcess(meow: app, filePath: filePath.path) } - let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) alertController.addAction(confirmAction) alertController.addAction(cancelAction) @@ -462,6 +166,15 @@ extension LibraryViewController { } } + let button2 = PopupViewController.PopupButton( + title: "Share", + style: .normal + ) { + if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { + self.shareFile(meow: app, filePath: filePath.path) + } + } + popupVC.configureButtons([button1, button2]) let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) @@ -487,4 +200,54 @@ extension LibraryViewController { func startSigning(meow: Any) {} @objc func afetch() {} func presentLoader() -> UIAlertController? { return nil } +} + +extension LibraryViewController: UISearchResultsUpdating { + func updateSearchResults(for searchController: UISearchController) { + filterContentForSearchText(searchController.searchBar.text!) + } + + fileprivate func fetchSources() { + // Fetch Signed Apps + let signedAppsFetchRequest: NSFetchRequest = SignedApps.fetchRequest() + do { + signedApps = try CoreDataManager.shared.context.fetch(signedAppsFetchRequest) + } catch { + print("Failed to fetch signed apps: \(error)") + } + + // Fetch Downloaded Apps + let downloadedAppsFetchRequest: NSFetchRequest = DownloadedApps.fetchRequest() + do { + downloadedApps = try CoreDataManager.shared.context.fetch(downloadedAppsFetchRequest) + } catch { + print("Failed to fetch downloaded apps: \(error)") + } + + DispatchQueue.main.async { + self.tableView.reloadData() + self.loaderAlert?.dismiss(animated: true, completion: nil) + } + } + + fileprivate func isFiltering() -> Bool { + return searchController.isActive && !searchBarIsEmpty() + } + + fileprivate func searchBarIsEmpty() -> Bool { + return searchController.searchBar.text?.isEmpty ?? true + } + + fileprivate func filterContentForSearchText(_ searchText: String, scope: String = "All") { + + filteredSignedApps = signedApps?.filter { app in + return app.name?.lowercased().contains(searchText.lowercased()) ?? false + } ?? + + filteredDownloadedApps = downloadedApps?.filter { app in + return app.name?.lowercased().contains(searchText.lowercased()) ?? false + } ?? + + tableView.reloadData() + } } \ No newline at end of file From 543b802e82227c7ce68a519cb159f8ebb764fdea Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 05:12:31 -0400 Subject: [PATCH 254/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index f60d0a06..6809cb28 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -1,5 +1,6 @@ import UIKit import ZIPFoundation +import os.log protocol FileHandlingDelegate: AnyObject { var documentsDirectory: URL { get } @@ -170,12 +171,4 @@ class HomeViewFileHandlers { } // Add other functions here, using executeProcess and DispatchQueue.global().async for stability. -} - -class HomeViewUtilities { - func handleError(in viewController: UIViewController, error: Error, withTitle title: String) { - let alert = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - viewController.present(alert, animated: true, completion: nil) - } } \ No newline at end of file From c97b7ecbf724d735841a0cdca66669583e569e1c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 05:13:20 -0400 Subject: [PATCH 255/391] Update HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 379 ++++++++++++------------- 1 file changed, 189 insertions(+), 190 deletions(-) diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift index c1306c49..9cb946e9 100644 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -1,191 +1,190 @@ import UIKit - import ZIPFoundation - import os.log // Import os.log for logging - - // MARK: - Error Handling Enhancements - - /// Custom error hierarchy for file operations. - enum FileAppError: Error { - case fileNotFound(String) // File not found at path - case fileAlreadyExists(String) // File already exists - case invalidFileName(String) // File name contains invalid characters - case invalidFileType(String) // File type not supported - case permissionDenied(String) // Permission denied for operation - case directoryCreationFailed(String) // Directory creation failed - case fileCreationFailed(String) // File creation failed - case fileRenameFailed(String, String) // Renaming file failed (old, new) - case fileDeleteFailed(String) // Deleting file failed - case fileMoveFailed(String, String) // Moving file failed (old, new) - case fileUnzipFailed(String, String, Error?) // Unzipping failed (file, dest, error) - case fileZipFailed(String, String, Error?) // Zipping failed (file, dest, error) - case dylibListingFailed(String, Error?) // Listing dylibs failed (path, error) - case unknown(Error) // An unexpected error occurred - } - - // MARK: - Alert Configuration - - /// Structure to encapsulate alert configurations. - struct AlertConfig { - let title: String? - let message: String? - let style: UIAlertController.Style - let actions: [AlertActionConfig] - let preferredAction: Int? // Index of preferred action - let completionHandler: (() -> Void)? - } - - struct AlertActionConfig { - let title: String? - let style: UIAlertAction.Style - let handler: (() -> Void)? - } - - // MARK: - HomeViewUtilities Class - - class HomeViewUtilities { - - private let logger: Logger // Inject a logger dependency - - init(logger: Logger = Logger(subsystem: "com.example.FileApp", category: "Utilities")) { - self.logger = logger - } - - // MARK: - Error Handling - - /// Handles and presents an error to the user. - /// - /// - Parameters: - /// - viewController: The view controller to present the alert in. - /// - error: The error to handle. - /// - title: The title for the error alert. - func handleError(in viewController: UIViewController, error: Error, withTitle title: String) { - var message: String - var logType: OSLogType = .error - - if let fileError = error as? FileAppError { - switch fileError { - case .fileNotFound(let fileName): - message = "File not found: \(fileName). Please check the file name and try again." - logType = .info // Log as info, user error - case .fileAlreadyExists(let fileName): - message = "A file with the name \(fileName) already exists. Please choose a different name." - logType = .info - case .unknown(let underlyingError): - message = "An unknown error occurred: \(underlyingError.localizedDescription)" - logger.error("Unknown error: \(underlyingError.localizedDescription)") - // Log the underlyingError here using os_log - default: - message = error.localizedDescription - } - } else { - message = error.localizedDescription - logger.error("Unexpected error: \(error.localizedDescription)") - } - - // Present alert on main thread - DispatchQueue.main.async { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - viewController.present(alert, animated: true, completion: nil) - } - } - - // MARK: - Alert Presentation - - /// Presents a basic alert using the provided configuration. - /// - /// - Parameters: - /// - config: The alert configuration. - /// - viewController: The view controller to present the alert in. - func showAlert(config: AlertConfig, in viewController: UIViewController) { - let alert = UIAlertController(title: config.title, message: config.message, preferredStyle: config.style) - - for (index, actionConfig) in config.actions.enumerated() { - let action = UIAlertAction(title: actionConfig.title, style: actionConfig.style) { _ in - actionConfig.handler?() - } - alert.addAction(action) - if let preferredIndex = config.preferredAction, preferredIndex == index { - alert.preferredAction = alert.actions[preferredIndex] - } - } - - DispatchQueue.main.async { - viewController.present(alert, animated: true, completion: config.completionHandler) - } - } - - /// Presents a confirmation alert with "OK" and "Cancel" actions. - /// - /// - Parameters: - /// - title: The title for the alert. - /// - message: The message for the alert. - /// - okHandler: Handler to be executed when the "OK" action is tapped. - /// - cancelHandler: Handler to be executed when the "Cancel" action is tapped. - /// - viewController: The view controller to present the alert in. - func showConfirmationAlert(title: String?, message: String?, okHandler: (() -> Void)?, cancelHandler: (() -> Void)?, in viewController: UIViewController) { - let okAction = AlertActionConfig(title: "OK", style: .default, handler: okHandler) - let cancelAction = AlertActionConfig(title: "Cancel", style: .cancel, handler: cancelHandler) - let config = AlertConfig(title: title, message: message, style: .alert, actions: [okAction, cancelAction], preferredAction: nil, completionHandler: nil) - showAlert(config: config, in: viewController) - } - - /// Presents an alert with a text field for user input. - /// - /// - Parameters: - /// - title: The title for the alert. - /// - message: The message for the alert. - /// - textFieldHandler: Handler to configure the text field. - /// - okHandler: Handler to be executed when the "OK" action is tapped, with the text field's text. - /// - cancelHandler: Handler to be executed when the "Cancel" action is tapped. - /// - viewController: The view controller to present the alert in. - func showInputAlert(title: String?, message: String?, textFieldHandler: ((UITextField) -> Void)?, okHandler: ((String?) -> Void)?, cancelHandler: (() -> Void)?, in viewController: UIViewController) { - let okAction = AlertActionConfig(title: "OK", style: .default) { [weak alert] in - let textField = alert?.textFields?.first - okHandler(textField?.text) - } - let cancelAction = AlertActionConfig(title: "Cancel", style: .cancel, handler: cancelHandler) - let config = AlertConfig(title: title, message: message, style: .alert, actions: [okAction, cancelAction], preferredAction: nil, completionHandler: nil) - - let alert = UIAlertController(title: config.title, message: config.message, preferredStyle: config.style) - alert.addTextField(configurationHandler: textFieldHandler) - - for actionConfig in config.actions { - let action = UIAlertAction(title: actionConfig.title, style: actionConfig.style) { _ in - actionConfig.handler?() - } - alert.addAction(action) - } - - DispatchQueue.main.async { - viewController.present(alert, animated: true, completion: config.completionHandler) - } - } - - // MARK: - Haptic Feedback - - /// Generates haptic feedback using UIImpactFeedbackGenerator. - /// - /// - Parameter style: The style of the impact feedback. - func generateHapticFeedback(style: UIImpactFeedbackGenerator.FeedbackStyle) { - let generator = UIImpactFeedbackGenerator(style: style) - generator.prepare() - generator.impactOccurred() - } - - /// Generates haptic feedback using UINotificationFeedbackGenerator. - /// - /// - Parameter type: The type of the notification feedback. - func generateNotificationFeedback(type: UINotificationFeedbackGenerator.FeedbackType) { - let generator = UINotificationFeedbackGenerator() - generator.prepare() - generator.notificationOccurred(type) - } - - /// Generates haptic feedback using UISelectionFeedbackGenerator. - func generateSelectionFeedback() { - let generator = UISelectionFeedbackGenerator() - generator.prepare() - generator.selectionChanged() - } - } \ No newline at end of file +import os.log // Import os.log for logging + +// MARK: - Error Handling Enhancements + +/// Custom error hierarchy for file operations. +enum FileAppError: Error { + case fileNotFound(String) // File not found at path + case fileAlreadyExists(String) // File already exists + case invalidFileName(String) // File name contains invalid characters + case invalidFileType(String) // File type not supported + case permissionDenied(String) // Permission denied for operation + case directoryCreationFailed(String) // Directory creation failed + case fileCreationFailed(String) // File creation failed + case fileRenameFailed(String, String) // Renaming file failed (old, new) + case fileDeleteFailed(String) // Deleting file failed + case fileMoveFailed(String, String) // Moving file failed (old, new) + case fileUnzipFailed(String, String, Error?) // Unzipping failed (file, dest, error) + case fileZipFailed(String, String, Error?) // Zipping failed (file, dest, error) + case dylibListingFailed(String, Error?) // Listing dylibs failed (path, error) + case unknown(Error) // An unexpected error occurred +} + +// MARK: - Alert Configuration + +/// Structure to encapsulate alert configurations. +struct AlertConfig { + let title: String? + let message: String? + let style: UIAlertController.Style + let actions: [AlertActionConfig] + let preferredAction: Int? // Index of preferred action + let completionHandler: (() -> Void)? +} + +struct AlertActionConfig { + let title: String? + let style: UIAlertAction.Style + let handler: (() -> Void)? +} + +// MARK: - HomeViewUtilities Class + +class HomeViewUtilities { + + private let logger: Logger // Inject a logger dependency + + init(logger: Logger = Logger(subsystem: "com.example.FileApp", category: "Utilities")) { + self.logger = logger + } + + // MARK: - Error Handling + + /// Handles and presents an error to the user. + /// + /// - Parameters: + /// - viewController: The view controller to present the alert in. + /// - error: The error to handle. + /// - title: The title for the error alert. + func handleError(in viewController: UIViewController, error: Error, withTitle title: String) { + var message: String + var logType: OSLogType = .error + + if let fileError = error as? FileAppError { + switch fileError { + case .fileNotFound(let fileName): + message = "File not found: \(fileName). Please check the file name and try again." + logType = .info // Log as info, user error + case .fileAlreadyExists(let fileName): + message = "A file with the name \(fileName) already exists. Please choose a different name." + logType = .info + case .unknown(let underlyingError): + message = "An unknown error occurred: \(underlyingError.localizedDescription)" + logger.error("Unknown error: \(underlyingError.localizedDescription)") + // Log the underlyingError here using os_log + default: + message = error.localizedDescription + } + } else { + message = error.localizedDescription + logger.error("Unexpected error: \(error.localizedDescription)") + } + + // Present alert on main thread + DispatchQueue.main.async { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + viewController.present(alert, animated: true, completion: nil) + } + } + + // MARK: - Alert Presentation + + /// Presents a basic alert using the provided configuration. + /// + /// - Parameters: + /// - config: The alert configuration. + /// - viewController: The view controller to present the alert in. + func showAlert(config: AlertConfig, in viewController: UIViewController) { + let alert = UIAlertController(title: config.title, message: config.message, preferredStyle: config.style) + + for (index, actionConfig) in config.actions.enumerated() { + let action = UIAlertAction(title: actionConfig.title, style: actionConfig.style) { _ in + actionConfig.handler?() + } + alert.addAction(action) + if let preferredIndex = config.preferredAction, preferredIndex == index { + alert.preferredAction = alert.actions[preferredIndex] + } + } + + DispatchQueue.main.async { + viewController.present(alert, animated: true, completion: config.completionHandler) + } + } + + /// Presents a confirmation alert with "OK" and "Cancel" actions. + /// + /// - Parameters: + /// - title: The title for the alert. + /// - message: The message for the alert. + /// - okHandler: Handler to be executed when the "OK" action is tapped. + /// - cancelHandler: Handler to be executed when the "Cancel" action is tapped. + /// - viewController: The view controller to present the alert in. + func showConfirmationAlert(title: String?, message: String?, okHandler: (() -> Void)?, cancelHandler: (() -> Void)?, in viewController: UIViewController) { + let okAction = AlertActionConfig(title: "OK", style: .default, handler: okHandler) + let cancelAction = AlertActionConfig(title: "Cancel", style: .cancel, handler: cancelHandler) + let config = AlertConfig(title: title, message: message, style: .alert, actions: [okAction, cancelAction], preferredAction: nil, completionHandler: nil) + showAlert(config: config, in: viewController) + } + + /// Presents an alert with a text field for user input. + /// + /// - Parameters: + /// - title: The title for the alert. + /// - message: The message for the alert. + /// - textFieldHandler: Handler to configure the text field. + /// - okHandler: Handler to be executed when the "OK" action is tapped, with the text field's text. + /// - cancelHandler: Handler to be executed when the "Cancel" action is tapped. + /// - viewController: The view controller to present the alert in. + func showInputAlert(title: String?, message: String?, textFieldHandler: ((UITextField) -> Void)?, okHandler: ((String?) -> Void)?, cancelHandler: (() -> Void)?, in viewController: UIViewController) { + let okAction = AlertActionConfig(title: "OK", style: .default) { [weak alert] in + let textField = alert?.textFields?.first + okHandler?(textField?.text) + } + let cancelAction = AlertActionConfig(title: "Cancel", style: .cancel, handler: cancelHandler) + let config = AlertConfig(title: title, message: message, style: .alert, actions: [okAction, cancelAction], preferredAction: nil, completionHandler: nil) + + let alert = UIAlertController(title: config.title, message: config.message, preferredStyle: config.style) + alert.addTextField(configurationHandler: textFieldHandler) + + for actionConfig in config.actions { + let action = UIAlertAction(title: actionConfig.title, style: actionConfig.style) { _ in + actionConfig.handler?() + } + alert.addAction(action) + } + + DispatchQueue.main.async { + viewController.present(alert, animated: true, completion: config.completionHandler) + } + } + + // MARK: - Haptic Feedback + + /// Generates haptic feedback using UIImpactFeedbackGenerator. + /// + /// - Parameter style: The style of the impact feedback. + func generateHapticFeedback(style: UIImpactFeedbackGenerator.FeedbackStyle) { + let generator = UIImpactFeedbackGenerator(style: style) + generator.prepare() + generator.impactOccurred() + } + + /// Generates haptic feedback using UINotificationFeedbackGenerator. + /// + /// - Parameter type: The type of the notification feedback. + func generateNotificationFeedback(type: UINotificationFeedbackGenerator.FeedbackType) { + let generator = UINotificationFeedbackGenerator() + generator.prepare() + generator.notificationOccurred(type) + } + + /// Generates haptic feedback using UISelectionFeedbackGenerator. + func generateSelectionFeedback() { + let generator = UISelectionFeedbackGenerator() + generator.prepare() + generator.selectionChanged() + } +} \ No newline at end of file From 968cae81b232e49b6af9e15c0521be2ba2550949 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 05:17:08 -0400 Subject: [PATCH 256/391] Update SigningsDylibViewController.swift --- .../SigningViewController/SigningsDylibViewController.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift index 7cde502a..3231432e 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift @@ -1,4 +1,5 @@ import UIKit +import Foundation // Import Foundation for Process class class SigningsDylibViewController: UITableViewController { var applicationPath: URL @@ -49,11 +50,8 @@ class SigningsDylibViewController: UITableViewController { tableView.register(UITableViewCell.self, forCellReuseIdentifier: "dylibCell") let alertController = UIAlertController(title: "ADVANCED USERS ONLY", message: "This section can make installed applications UNUSABLE and potentially UNSTABLE. USE THIS SECTION WITH CAUTION.", preferredStyle: .alert) - let continueAction = UIAlertAction(title: "WHO CARES", style: .destructive, handler: nil) - alertController.addAction(continueAction) - present(alertController, animated: true, completion: nil) } From cee8fb88ed009ab4e4545d51dd59a19bbffe98c2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 05:21:30 -0400 Subject: [PATCH 257/391] Update main.yml --- .github/workflows/main.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f2bff300..c8b658c0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,7 +1,6 @@ name: Create New Release on: - workflow_dispatch: jobs: @@ -11,6 +10,11 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Clean Swift Environment + run: | + rm -rf ~/Library/Developer/Xcode/DerivedData + xcodebuild clean + - name: Install dependencies (packages) run: | curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 @@ -20,7 +24,8 @@ jobs: - name: Compile f run: | mkdir upload - make package SCHEME="'feather (Release)'" + # Set optimization level to -Onone + make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone mv packages/* upload/ - name: Get Version Number From 7a94f41ac49de0395763372edc9a31f856275cef Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 05:25:33 -0400 Subject: [PATCH 258/391] Update Makefile --- Makefile | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index c8ca6e66..61b06783 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ TARGET_CODESIGN = $(shell which ldid) PLATFORM = iphoneos NAME = feather -SCHEME ?= 'feather (Debug)' +SCHEME ?= 'feather (Release)' RELEASE = Release-iphoneos CONFIGURATION = Release @@ -13,33 +13,42 @@ APP_TMP = $(TMPDIR)/$(NAME) STAGE_DIR = $(APP_TMP)/stage APP_DIR = $(APP_TMP)/Build/Products/$(RELEASE)/$(NAME).app +OPTIMIZATION_LEVEL ?= -Onone + +# Export PATH to ensure tools like ldid are accessible +export PATH:=$(PATH):/usr/local/bin + all: package package: @rm -rf $(APP_TMP) + # Clean Swift environment only if necessary, comment out for faster subsequent builds + # @rm -rf ~/Library/Developer/Xcode/DerivedData + # @xcodebuild clean @set -o pipefail; \ xcodebuild \ - -jobs $(shell sysctl -n hw.ncpu) \ + -jobs $(shell sysctl -n hw.physicalcpu) \ -project '$(NAME).xcodeproj' \ -scheme $(SCHEME) \ -configuration $(CONFIGURATION) \ -arch arm64 -sdk $(PLATFORM) \ -derivedDataPath $(APP_TMP) \ CODE_SIGNING_ALLOWED=NO \ + CODE_SIGNING_REQUIRED=NO \ DSTROOT=$(APP_TMP)/install \ - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO - - @rm -rf Payload - @rm -rf $(STAGE_DIR)/ + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO \ + SWIFT_PACKAGE_MANAGER_ENABLE_XCODE_PROJECT_FORMATS=NO \ + OTHER_CFLAGS="$(OPTIMIZATION_LEVEL)" \ + OTHER_SWIFT_FLAGS="$(OPTIMIZATION_LEVEL)" | xcpretty + @mkdir -p $(STAGE_DIR)/Payload @mv $(APP_DIR) $(STAGE_DIR)/Payload/$(NAME).app - @echo $(APP_TMP) - @echo $(STAGE_DIR) + @echo "Build artifacts: $(APP_TMP)" + @echo "Staging directory: $(STAGE_DIR)" @rm -rf $(STAGE_DIR)/Payload/$(NAME).app/_CodeSignature @ln -sf $(STAGE_DIR)/Payload Payload - @rm -rf packages @mkdir -p packages ifeq ($(TIPA),1) @@ -55,7 +64,8 @@ clean: @rm -rf Payload @rm -rf apple-include @rm -rf $(APP_TMP) + # Clean DerivedData and xcodebuild only when necessary + # @rm -rf ~/Library/Developer/Xcode/DerivedData + # @xcodebuild clean -.PHONY: apple-include - - +.PHONY: apple-include \ No newline at end of file From a7fecdbbb11a2cc251e3854527307f0bdedffad1 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 05:26:33 -0400 Subject: [PATCH 259/391] Update main.yml --- .github/workflows/main.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c8b658c0..4bbbb698 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,22 +10,17 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Clean Swift Environment - run: | - rm -rf ~/Library/Developer/Xcode/DerivedData - xcodebuild clean - - - name: Install dependencies (packages) + - name: Install Dependencies run: | curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid - brew install 7zip gnu-sed + brew install 7zip gnu-sed xcpretty # Added xcpretty for better build logs - name: Compile f run: | mkdir upload - # Set optimization level to -Onone - make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone + # Assuming the Makefile is in the root directory of your repository + make package mv packages/* upload/ - name: Get Version Number From 86b995f9ebe0805436c547939bf9ccddc8c038a7 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 05:29:40 -0400 Subject: [PATCH 260/391] Update main.yml --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4bbbb698..b8ba0e1e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,8 @@ jobs: run: | curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid - brew install 7zip gnu-sed xcpretty # Added xcpretty for better build logs + brew install 7zip gnu-sed + brew install xcbeautify # xcbeautify instead of xcpretty - name: Compile f run: | From ecc1f8602f8d314a13752cacca4c01d8284fdd44 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:02:41 -0400 Subject: [PATCH 261/391] Update Makefile --- Makefile | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 61b06783..0cb72213 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ TARGET_CODESIGN = $(shell which ldid) PLATFORM = iphoneos NAME = feather -SCHEME ?= 'feather (Release)' +SCHEME ?= 'feather (Release)' # Changed to Release for consistency with optimization level RELEASE = Release-iphoneos CONFIGURATION = Release @@ -13,42 +13,39 @@ APP_TMP = $(TMPDIR)/$(NAME) STAGE_DIR = $(APP_TMP)/stage APP_DIR = $(APP_TMP)/Build/Products/$(RELEASE)/$(NAME).app -OPTIMIZATION_LEVEL ?= -Onone - -# Export PATH to ensure tools like ldid are accessible -export PATH:=$(PATH):/usr/local/bin +OPTIMIZATION_LEVEL ?= -Onone # Set default optimization level to -Onone all: package package: @rm -rf $(APP_TMP) - # Clean Swift environment only if necessary, comment out for faster subsequent builds - # @rm -rf ~/Library/Developer/Xcode/DerivedData - # @xcodebuild clean + @rm -rf ~/Library/Developer/Xcode/DerivedData # Clean Swift environment + @xcodebuild clean # Clean Xcode build @set -o pipefail; \ xcodebuild \ - -jobs $(shell sysctl -n hw.physicalcpu) \ + -jobs $(shell sysctl -n hw.ncpu) \ -project '$(NAME).xcodeproj' \ -scheme $(SCHEME) \ -configuration $(CONFIGURATION) \ -arch arm64 -sdk $(PLATFORM) \ -derivedDataPath $(APP_TMP) \ CODE_SIGNING_ALLOWED=NO \ - CODE_SIGNING_REQUIRED=NO \ DSTROOT=$(APP_TMP)/install \ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO \ - SWIFT_PACKAGE_MANAGER_ENABLE_XCODE_PROJECT_FORMATS=NO \ OTHER_CFLAGS="$(OPTIMIZATION_LEVEL)" \ - OTHER_SWIFT_FLAGS="$(OPTIMIZATION_LEVEL)" | xcpretty - + OTHER_SWIFT_FLAGS="$(OPTIMIZATION_LEVEL)" + + @rm -rf Payload + @rm -rf $(STAGE_DIR)/ @mkdir -p $(STAGE_DIR)/Payload @mv $(APP_DIR) $(STAGE_DIR)/Payload/$(NAME).app - @echo "Build artifacts: $(APP_TMP)" - @echo "Staging directory: $(STAGE_DIR)" + @echo $(APP_TMP) + @echo $(STAGE_DIR) @rm -rf $(STAGE_DIR)/Payload/$(NAME).app/_CodeSignature @ln -sf $(STAGE_DIR)/Payload Payload + @rm -rf packages @mkdir -p packages ifeq ($(TIPA),1) @@ -64,8 +61,7 @@ clean: @rm -rf Payload @rm -rf apple-include @rm -rf $(APP_TMP) - # Clean DerivedData and xcodebuild only when necessary - # @rm -rf ~/Library/Developer/Xcode/DerivedData - # @xcodebuild clean + @rm -rf ~/Library/Developer/Xcode/DerivedData # Clean Swift environment + @xcodebuild clean # Clean Xcode build .PHONY: apple-include \ No newline at end of file From 9c1cad276f2cb5504bde1b23ee49c1f5f04be6b4 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:04:17 -0400 Subject: [PATCH 262/391] Update main.yml --- .github/workflows/main.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b8ba0e1e..c8b658c0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,18 +10,22 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Install Dependencies + - name: Clean Swift Environment + run: | + rm -rf ~/Library/Developer/Xcode/DerivedData + xcodebuild clean + + - name: Install dependencies (packages) run: | curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 sudo install -m755 ldid_macosx_x86_64 /usr/local/bin/ldid brew install 7zip gnu-sed - brew install xcbeautify # xcbeautify instead of xcpretty - name: Compile f run: | mkdir upload - # Assuming the Makefile is in the root directory of your repository - make package + # Set optimization level to -Onone + make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone mv packages/* upload/ - name: Get Version Number From 5a9ab475694c582ee9ffd4f0336f1e11b2634a48 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:06:49 -0400 Subject: [PATCH 263/391] Update create.yml --- .github/workflows/create.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/create.yml b/.github/workflows/create.yml index 595e67d7..14b5e431 100644 --- a/.github/workflows/create.yml +++ b/.github/workflows/create.yml @@ -1,4 +1,4 @@ -name: Create Beta Build +name: Create New Release on: workflow_dispatch: @@ -10,6 +10,11 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Clean Swift Environment + run: | + rm -rf ~/Library/Developer/Xcode/DerivedData + xcodebuild clean + - name: Install dependencies (packages) run: | curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 @@ -17,15 +22,16 @@ jobs: brew install 7zip gnu-sed - name: Compile f - run: | + run: | mkdir upload - make package SCHEME="'feather (Release)'" + # Set optimization level to -Onone + make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone mv packages/* upload/ - name: Get Version Number id: get_version run: | - VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/feather.app/Info.plist) + VERSION=$( /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Payload/feather.app/Info.plist ) echo "VERSION=${VERSION}" >> $GITHUB_ENV - name: Setup @@ -41,7 +47,7 @@ jobs: files: | upload/*ipa generate_release_notes: true - fail_on_unmatched_files: true + fail_on_unmatched_files: false draft: true env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ env.WORKFLOW_SECRET }} \ No newline at end of file From 433b8523f34965f15c9df956255142546c71bb27 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:06:58 -0400 Subject: [PATCH 264/391] Update create.yml --- .github/workflows/create.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create.yml b/.github/workflows/create.yml index 14b5e431..836c2791 100644 --- a/.github/workflows/create.yml +++ b/.github/workflows/create.yml @@ -1,4 +1,4 @@ -name: Create New Release +name: Create New Beta on: workflow_dispatch: From 2c028159e7dff73d2016a12433a77de527f1230e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:17:12 -0400 Subject: [PATCH 265/391] Update main.yml --- .github/workflows/main.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c8b658c0..8c01ad98 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,7 @@ name: Create New Release on: + workflow_dispatch: jobs: @@ -10,11 +11,6 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Clean Swift Environment - run: | - rm -rf ~/Library/Developer/Xcode/DerivedData - xcodebuild clean - - name: Install dependencies (packages) run: | curl -LO https://github.com/ProcursusTeam/ldid/releases/download/v2.1.5-procursus7/ldid_macosx_x86_64 @@ -24,8 +20,7 @@ jobs: - name: Compile f run: | mkdir upload - # Set optimization level to -Onone - make package SCHEME="'feather (Release)'" OPTIMIZATION_LEVEL=-Onone + make package SCHEME="'feather (Release)'" mv packages/* upload/ - name: Get Version Number @@ -50,4 +45,4 @@ jobs: fail_on_unmatched_files: true draft: true env: - GITHUB_TOKEN: ${{ env.WORKFLOW_SECRET }} \ No newline at end of file + GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }} \ No newline at end of file From ae39ed014e20893e5c29c05996e2f0314263d568 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:17:59 -0400 Subject: [PATCH 266/391] Update Makefile --- Makefile | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 0cb72213..794e3814 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ TARGET_CODESIGN = $(shell which ldid) PLATFORM = iphoneos NAME = feather -SCHEME ?= 'feather (Release)' # Changed to Release for consistency with optimization level +SCHEME ?= 'feather (Debug)' RELEASE = Release-iphoneos CONFIGURATION = Release @@ -13,14 +13,10 @@ APP_TMP = $(TMPDIR)/$(NAME) STAGE_DIR = $(APP_TMP)/stage APP_DIR = $(APP_TMP)/Build/Products/$(RELEASE)/$(NAME).app -OPTIMIZATION_LEVEL ?= -Onone # Set default optimization level to -Onone - all: package package: @rm -rf $(APP_TMP) - @rm -rf ~/Library/Developer/Xcode/DerivedData # Clean Swift environment - @xcodebuild clean # Clean Xcode build @set -o pipefail; \ xcodebuild \ @@ -32,9 +28,7 @@ package: -derivedDataPath $(APP_TMP) \ CODE_SIGNING_ALLOWED=NO \ DSTROOT=$(APP_TMP)/install \ - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO \ - OTHER_CFLAGS="$(OPTIMIZATION_LEVEL)" \ - OTHER_SWIFT_FLAGS="$(OPTIMIZATION_LEVEL)" + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO @rm -rf Payload @rm -rf $(STAGE_DIR)/ @@ -61,7 +55,6 @@ clean: @rm -rf Payload @rm -rf apple-include @rm -rf $(APP_TMP) - @rm -rf ~/Library/Developer/Xcode/DerivedData # Clean Swift environment - @xcodebuild clean # Clean Xcode build -.PHONY: apple-include \ No newline at end of file +.PHONY: apple-include + From 658bdf85ecc4cad04ca463b58927fd147ae6c9d9 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:42:30 -0400 Subject: [PATCH 267/391] Update AR.swift --- Shared/Magic/decompression/AR.swift | 105 +++++++++++++++------------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/Shared/Magic/decompression/AR.swift b/Shared/Magic/decompression/AR.swift index 935a6232..8cb3924b 100644 --- a/Shared/Magic/decompression/AR.swift +++ b/Shared/Magic/decompression/AR.swift @@ -1,66 +1,73 @@ +// +// AR.swift +// feather +// +// Created by samara on 8/18/24. +// + import Foundation public struct ARFile { - var name: String - var modificationDate: Date - var ownerId: Int - var groupId: Int - var mode: Int - var size: Int - var content: Data + var name: String + var modificationDate: Date + var ownerId: Int + var groupId: Int + var mode: Int + var size: Int + var content: Data } func removePadding(_ paddedString: String) -> String { - let data = paddedString.data(using: .utf8)! - - guard let firstNonSpaceIndex = data.firstIndex(of: UInt8(ascii: " ")) else { - return paddedString - } - - let actualData = data[.. ARFile { - let size = Int(removePadding(String(data: data.subdata(in: offset+48.. [ARFile] { - if [UInt8](rawData.subdata(in: Range(0...7))) != [0x21, 0x3c, 0x61, 0x72, 0x63, 0x68, 0x3e, 0x0a] { - throw ARError.badArchive("Invalid magic") - } + if [UInt8](rawData.subdata(in: Range(0...7))) != [0x21, 0x3c, 0x61, 0x72, 0x63, 0x68, 0x3e, 0x0a] { + throw ARError.badArchive("Invalid magic") + } - let data = rawData.subdata(in: 8.. Date: Mon, 17 Mar 2025 06:43:03 -0400 Subject: [PATCH 268/391] Update Decompression.swift --- .../Magic/decompression/Decompression.swift | 126 ++++++++++-------- 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/Shared/Magic/decompression/Decompression.swift b/Shared/Magic/decompression/Decompression.swift index 4bae5e72..d7c7f8a8 100644 --- a/Shared/Magic/decompression/Decompression.swift +++ b/Shared/Magic/decompression/Decompression.swift @@ -1,65 +1,73 @@ +// +// Decompression.swift +// feather +// +// Created by samara on 21.08.2024. +// Copyright (c) 2024 Samara M (khcrysalis) +// + import Foundation import SWCompression import Compression func processFile(at packagesFile: inout URL) throws { - let succeededExtension = packagesFile.pathExtension.lowercased() - let fileManager = FileManager.default - - func readData(from url: URL) throws -> Data { - return try Data(contentsOf: url) - } - - func writeData(_ data: Data, to url: URL) throws { - try data.write(to: url) - } - - func handleCompressedFile(extension: String, decompressor: (Data) throws -> Data) throws { - let compressedData = try readData(from: packagesFile) - let decompressedData = try decompressor(compressedData) - let outputURL = packagesFile.deletingPathExtension() - try writeData(decompressedData, to: outputURL) - packagesFile = outputURL - } - - func handleTarFile() throws { - let tarData = try readData(from: packagesFile) - let tarContainer = try TarContainer.open(container: tarData) - - let extractionDirectory = packagesFile.deletingLastPathComponent().appendingPathComponent(UUID().uuidString) - try fileManager.createDirectory(at: extractionDirectory, withIntermediateDirectories: true, attributes: nil) - - for entry in tarContainer { - let entryPath = extractionDirectory.appendingPathComponent(entry.info.name) - if entry.info.type == .regular { - if let entryData = entry.data { - try writeData(entryData, to: entryPath) - } - } else if entry.info.type == .directory { - try fileManager.createDirectory(at: entryPath, withIntermediateDirectories: true, attributes: nil) - } - } - - packagesFile = extractionDirectory - } - - switch succeededExtension { - case "xz": - try handleCompressedFile(extension: succeededExtension, decompressor: XZArchive.unarchive) - - case "lzma": - try handleCompressedFile(extension: succeededExtension, decompressor: LZMA.decompress) - - case "bz2": - try handleCompressedFile(extension: succeededExtension, decompressor: BZip2.decompress) - - case "gz": - try handleCompressedFile(extension: succeededExtension, decompressor: GzipArchive.unarchive) - - case "tar": - try handleTarFile() - - default: - throw FileProcessingError.unsupportedFileExtension(succeededExtension) - } + let succeededExtension = packagesFile.pathExtension.lowercased() + let fileManager = FileManager.default + + func readData(from url: URL) throws -> Data { + return try Data(contentsOf: url) + } + + func writeData(_ data: Data, to url: URL) throws { + try data.write(to: url) + } + + func handleCompressedFile(extension: String, decompressor: (Data) throws -> Data) throws { + let compressedData = try readData(from: packagesFile) + let decompressedData = try decompressor(compressedData) + let outputURL = packagesFile.deletingPathExtension() + try writeData(decompressedData, to: outputURL) + packagesFile = outputURL + } + + func handleTarFile() throws { + let tarData = try readData(from: packagesFile) + let tarContainer = try TarContainer.open(container: tarData) + + let extractionDirectory = packagesFile.deletingLastPathComponent().appendingPathComponent(UUID().uuidString) + try fileManager.createDirectory(at: extractionDirectory, withIntermediateDirectories: true, attributes: nil) + + for entry in tarContainer { + let entryPath = extractionDirectory.appendingPathComponent(entry.info.name) + if entry.info.type == .regular { + if let entryData = entry.data { + try writeData(entryData, to: entryPath) + } + } else if entry.info.type == .directory { + try fileManager.createDirectory(at: entryPath, withIntermediateDirectories: true, attributes: nil) + } + } + + packagesFile = extractionDirectory + } + + switch succeededExtension { + case "xz": + try handleCompressedFile(extension: succeededExtension, decompressor: XZArchive.unarchive) + + case "lzma": + try handleCompressedFile(extension: succeededExtension, decompressor: LZMA.decompress) + + case "bz2": + try handleCompressedFile(extension: succeededExtension, decompressor: BZip2.decompress) + + case "gz": + try handleCompressedFile(extension: succeededExtension, decompressor: GzipArchive.unarchive) + + case "tar": + try handleTarFile() + + default: + throw FileProcessingError.unsupportedFileExtension(succeededExtension) + } } \ No newline at end of file From 91241c8db1903fa2609eec05b8172501a0a01400 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:44:01 -0400 Subject: [PATCH 269/391] Update TweakHandler.swift --- Shared/Magic/TweakHandler.swift | 610 ++++++++++++++++---------------- 1 file changed, 300 insertions(+), 310 deletions(-) diff --git a/Shared/Magic/TweakHandler.swift b/Shared/Magic/TweakHandler.swift index 981ebbc8..8c34c408 100644 --- a/Shared/Magic/TweakHandler.swift +++ b/Shared/Magic/TweakHandler.swift @@ -1,333 +1,323 @@ +// +// DylibHandler.swift +// feather +// +// Created by samara on 8/17/24. +// Copyright (c) 2024 Samara M (khcrysalis) +// + import Foundation import SWCompression enum FileProcessingError: Error { - case unsupportedFileExtension(String) - case decompressionFailed(String) - case missingFile(String) + case unsupportedFileExtension(String) + case decompressionFailed(String) + case missingFile(String) } class TweakHandler { - - let fileManager = FileManager.default - - private var urls: [String] - private let app: URL - private var urlsToInject: [URL] = [] - private var directoriesToCheck: [URL] = [] - - init(urls: [String], app: URL) { - self.urls = urls - self.app = app - } - - public func getInputFiles() throws { - guard !urls.isEmpty else { - Debug.shared.log(message: "No dylibs to inject, skipping!") - return - } - - let frameworksPath = app.appendingPathComponent("Frameworks").appendingPathComponent("CydiaSubstrate.framework") - if !fileManager.fileExists(atPath: frameworksPath.path) { - if let ellekitURL = Bundle.main.url(forResource: "ellekit", withExtension: "deb") { - self.urls.insert(ellekitURL.absoluteString, at: 0) - } else { - Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle ⁉️", type: .error) - return - } - } - - let baseTmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) - - do { - try TweakHandler.createDirectoryIfNeeded(at: app.appendingPathComponent("Frameworks")) - try TweakHandler.createDirectoryIfNeeded(at: baseTmpDir) - - for url in urls { - guard let urlf = URL(string: url) else { - Debug.shared.log(message: "Invalid URL: \(url), skipping.") - continue - } - switch urlf.pathExtension.lowercased() { - case "dylib": - try handleDylib(at: urlf) - case "deb": - try handleDeb(at: urlf, baseTmpDir: baseTmpDir) - default: - Debug.shared.log(message: "Unsupported file type: \(urlf.lastPathComponent), skipping.") - } - } - - if !directoriesToCheck.isEmpty { - try handleDirectories(at: directoriesToCheck) - if !urlsToInject.isEmpty { - try handleExtractedDirectoryContents(at: urlsToInject) - } - } - - } catch { - throw error - } - } - - private func handleExtractedDirectoryContents(at urls: [URL]) throws { - for url in urls { - switch url.pathExtension.lowercased() { - case "dylib": - try handleDylib(at: url) - case "framework": - let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - try handleDylib(framework: destinationURL) - case "bundle": - let destinationURL = app.appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - default: - Debug.shared.log(message: "Unsupported file type: \(url.lastPathComponent), skipping.") - } - } - } - - private func handleDylib(at url: URL) throws { - do { - let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - - _ = changeDylib( - filePath: destinationURL.path, - oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", - newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" - ) - - if let exe = try TweakHandler.findExecutable(at: app) { - _ = injectDylib( - filePath: exe.path, - dylibPath: "@executable_path/Frameworks/\(destinationURL.lastPathComponent)", - weakInject: true - ) - } - } catch { - throw error - } - } - - private func handleDylib(framework: URL) throws { - do { - if let fexe = try TweakHandler.findExecutable(at: framework) { - _ = changeDylib( - filePath: fexe.path, - oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", - newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" - ) - - if let appexe = try TweakHandler.findExecutable(at: app) { - _ = injectDylib( - filePath: appexe.path, - dylibPath: "@executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent)", - weakInject: true - ) - } - } - } catch { - throw error - } - } - - private func handleDeb(at url: URL, baseTmpDir: URL) throws { - let uniqueSubDir = baseTmpDir.appendingPathComponent(UUID().uuidString) - try TweakHandler.createDirectoryIfNeeded(at: uniqueSubDir) - - do { - let arFiles = try extractAR(try Data(contentsOf: url)) - - for arFile in arFiles { - let outputPath = uniqueSubDir.appendingPathComponent(arFile.name) - try arFile.content.write(to: outputPath) - - if ["data.tar.lzma", "data.tar.gz", "data.tar.xz", "data.tar.bz2"].contains(arFile.name) { - var fileToProcess = outputPath - try processFile(at: &fileToProcess) - try processFile(at: &fileToProcess) - directoriesToCheck.append(fileToProcess) - } - } - } catch { - Debug.shared.log(message: "Error handling file \(url): \(error)") - throw error - } - } - - private func handleDirectories(at urls: [URL]) throws { - let directoriesToCheck = [ - "Library/Frameworks/", "var/jb/Library/Frameworks/", - "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/", - "Library/Application Support/", "var/jb/Library/Application Support/" - ] - - for baseURL in urls { - for directory in directoriesToCheck { - let directoryURL = baseURL.appendingPathComponent(directory) - - guard fileManager.fileExists(atPath: directoryURL.path) else { - Debug.shared.log(message: "Directory does not exist: \(directoryURL.path). Skipping.") - continue - } - - switch directory { - case "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/": - let dylibFiles = try locateDylibFiles(in: directoryURL) - urlsToInject.append(contentsOf: dylibFiles) - - case "Library/Frameworks/", "var/jb/Library/Frameworks/": - let frameworkDirectories = try locateFrameworkDirectories(in: directoryURL) - urlsToInject.append(contentsOf: frameworkDirectories) - - case "Library/Application Support/", "var/jb/Library/Application Support/": - try searchForBundles(in: directoryURL) - - default: - Debug.shared.log(message: "Unexpected directory path: \(directoryURL.path)") - } - } - } - } -} + + let fileManager = FileManager.default + + private var urls: [String] + private let app: URL + private var urlsToInject: [URL] = [] + private var directoriesToCheck: [URL] = [] -// MARK: - Find correct files in debs -extension TweakHandler { - private func searchForBundles(in directory: URL) throws { - let allFiles = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) + init(urls: [String], app: URL) { + self.urls = urls + self.app = app + } - let bundleDirectories = allFiles.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "bundle" && url.hasDirectoryPath && !isSymlink - } - - urlsToInject.append(contentsOf: bundleDirectories) - - let directoriesToSearch = allFiles.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.hasDirectoryPath && !bundleDirectories.contains(url) && !isSymlink - } - - for dirURL in directoriesToSearch { - try searchForBundles(in: dirURL) - } - } + public func getInputFiles() throws { + guard !urls.isEmpty else { + Debug.shared.log(message: "No dylibs to inject, skipping!") + return + } + + let frameworksPath = app.appendingPathComponent("Frameworks").appendingPathComponent("CydiaSubstrate.framework") + if !fileManager.fileExists(atPath: frameworksPath.path) { + if let ellekitURL = Bundle.main.url(forResource: "ellekit", withExtension: "deb") { + self.urls.insert(ellekitURL.absoluteString, at: 0) + } else { + Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle ⁉️", type: .error) + return + } + } - private func locateDylibFiles(in directory: URL) throws -> [URL] { - let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) + let baseTmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) + + do { + try TweakHandler.createDirectoryIfNeeded(at: app.appendingPathComponent("Frameworks")) + try TweakHandler.createDirectoryIfNeeded(at: baseTmpDir) + + // check for appropriate files, if theres debs + // it will extract then add a url, if theres no url, i.e. + // you haven't added a deb, it will skip + for url in urls { + let urlf = URL(string: url) + switch urlf!.pathExtension.lowercased() { + case "dylib": + try handleDylib(at: urlf!) + case "deb": + try handleDeb(at: urlf!, baseTmpDir: baseTmpDir) + default: + Debug.shared.log(message: "Unsupported file type: \(urlf!.lastPathComponent), skipping.") + } + } + + // check contents of data.tar's extracted from debs + if !directoriesToCheck.isEmpty { + try handleDirectories(at: directoriesToCheck) + if !urlsToInject.isEmpty { + try handleExtractedDirectoryContents(at: urlsToInject) + } + } + + } catch { + throw error + } + } + + // finally, handle extracted contents + private func handleExtractedDirectoryContents(at urls: [URL]) throws { + for url in urls { + switch url.pathExtension.lowercased() { + case "dylib": + try handleDylib(at: url) + case "framework": + let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + try handleDylib(framework: destinationURL) + case "bundle": + let destinationURL = app.appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + default: + Debug.shared.log(message: "Unsupported file type: \(url.lastPathComponent), skipping.") + } + } + } + + // Inject imported dylib file + private func handleDylib(at url: URL) throws { + do { + let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + + // change paths because some tweaks hardlink, which is not ideal. + // this is not a good solution, at most this would work for basic tweaks + // we recommend you use newer theos to compile, and make sure it works + // using the ellekit framework + _ = changeDylib( + filePath: destinationURL.path, + oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", + newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" + ) + + // inject if there's a valid app main executable + if let exe = try TweakHandler.findExecutable(at: app) { + _ = injectDylib( + filePath: exe.path, + dylibPath: "@executable_path/Frameworks/\(destinationURL.lastPathComponent)", + weakInject: true + ) + } + } catch { + throw error + } + } + + // Inject imported framework dir + private func handleDylib(framework: URL) throws { + do { + if let fexe = try TweakHandler.findExecutable(at: framework) { + + // change paths because some tweaks hardlink, which is not ideal. + // this is not a good solution, at most this would work for basic tweaks + // we recommend you use newer theos to compile, and make sure it works + // using the ellekit framework + _ = changeDylib( + filePath: fexe.path, + oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", + newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" + ) + + // inject if there's a valid app main executable + if let appexe = try TweakHandler.findExecutable(at: app) { + _ = injectDylib( + filePath: appexe.path, + dylibPath: "@executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent)", + weakInject: true + ) + } + } + + + } catch { + throw error + } + } + + // Extracy imported deb file + private func handleDeb(at url: URL, baseTmpDir: URL) throws { + let uniqueSubDir = baseTmpDir.appendingPathComponent(UUID().uuidString) + try TweakHandler.createDirectoryIfNeeded(at: uniqueSubDir) + + // I don't particularly like this code + // but it somehow works well enough, + // do note large lzma's are slow as hell + do { + let arFiles = try extractAR(try Data(contentsOf: url)) + + for arFile in arFiles { + let outputPath = uniqueSubDir.appendingPathComponent(arFile.name) + try arFile.content.write(to: outputPath) + + if ["data.tar.lzma", "data.tar.gz", "data.tar.xz", "data.tar.bz2"].contains(arFile.name) { + var fileToProcess = outputPath + try processFile(at: &fileToProcess) + try processFile(at: &fileToProcess) + directoriesToCheck.append(fileToProcess) + } + } + } catch { + Debug.shared.log(message: "Error handling file \(url): \(error)") + throw error + } + } + + // Read extracted deb file, locate all neccessary contents to copy over to the .app + private func handleDirectories(at urls: [URL]) throws { + let directoriesToCheck = [ + "Library/Frameworks/", "var/jb/Library/Frameworks/", + "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/", + "Library/Application Support/", "var/jb/Library/Application Support/" + ] + + let fileManager = FileManager.default + + for baseURL in urls { + for directory in directoriesToCheck { + let directoryURL = baseURL.appendingPathComponent(directory) + + guard fileManager.fileExists(atPath: directoryURL.path) else { + Debug.shared.log(message: "Directory does not exist: \(directoryURL.path). Skipping.") + continue + } + + switch directory { + case "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/": + let dylibFiles = try locateDylibFiles(in: directoryURL) + for fileURL in dylibFiles { + urlsToInject.append(fileURL) + } + + case "Library/Frameworks/", "var/jb/Library/Frameworks/": + let frameworkDirectories = try locateFrameworkDirectories(in: directoryURL) + for frameworkURL in frameworkDirectories { + urlsToInject.append(frameworkURL) + } + + case "Library/Application Support/", "var/jb/Library/Application Support/": + try searchForBundles(in: directoryURL) + + default: + Debug.shared.log(message: "Unexpected directory path: \(directoryURL.path)") + } + } + } + } +} - return files.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "dylib" && !isSymlink - } - } - private func locateFrameworkDirectories(in directory: URL) throws -> [URL] { - let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) - return files.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "framework" && url.hasDirectoryPath && !isSymlink - } - } -} -// MARK: - File management +// MARK: - Find correct files in debs extension TweakHandler { - private static func createDirectoryIfNeeded(at url: URL) throws { - let fileManager = FileManager.default - if !fileManager.fileExists(atPath: url.path) { - try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) - } - } - - public static func findExecutable(at frameworkURL: URL) throws -> URL? { - let infoPlistURL = frameworkURL.appendingPathComponent("Info.plist") - - let plistData = try Data(contentsOf: infoPlistURL) - if let plist = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any], - let executableName = plist["CFBundleExecutable"] as? String { - let executableURL = frameworkURL.appendingPathComponent(executableName) - return executableURL - } else { - Debug.shared.log(message: "CFBundleExecutable not found in Info.plist") - return nil - } - } + private func searchForBundles(in directory: URL) throws { + let fileManager = FileManager.default + let allFiles = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) - private static func moveFile(from sourceURL: URL, to destinationURL: URL) throws { - let fileManager = FileManager.default - if fileManager.fileExists(atPath: destinationURL.path) { - Debug.shared.log(message: "File already exists at destination: \(destinationURL)") - } else { - try fileManager.moveItem(at: sourceURL, to: destinationURL) - } - } -} + let bundleDirectories = allFiles.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "bundle" && url.hasDirectoryPath && !isSymlink + } + + for bundleURL in bundleDirectories { + urlsToInject.append(bundleURL) + } + + let directoriesToSearch = allFiles.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.hasDirectoryPath && !bundleDirectories.contains(url) && !isSymlink + } + + for dirURL in directoriesToSearch { + try searchForBundles(in: dirURL) + } + } -// MARK: - External functions -func changeDylib(filePath: String, oldPath: String, newPath: String) -> Bool { - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/install_name_tool") - process.arguments = ["-change", oldPath, newPath, filePath] + private func locateDylibFiles(in directory: URL) throws -> [URL] { + let fileManager = FileManager.default + let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) - do { - try process.run() - process.waitUntilExit() - return process.terminationStatus == 0 - } catch { - print("Error changing dylib path: \(error)") - return false - } -} + let dylibFiles = files.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "dylib" && !isSymlink + } + + return dylibFiles + } -func injectDylib(filePath: String, dylibPath: String, weakInject: Bool) -> Bool { - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/install_name_tool") - process.arguments = weakInject ? ["-add_rpath", dylibPath, filePath] : ["-add_rpath", dylibPath, filePath] + private func locateFrameworkDirectories(in directory: URL) throws -> [URL] { + let fileManager = FileManager.default + let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) - do { - try process.run() - process.waitUntilExit() - return process.terminationStatus == 0 - } catch { - print("Error injecting dylib: \(error)") - return false - } + let frameworkDirectories = files.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "framework" && url.hasDirectoryPath && !isSymlink + } + + return frameworkDirectories + } } -func extractAR(_ data: Data) throws -> [ARFile] { - let arContainer = try ARContainer.open(container: data) - return arContainer.contents -} -func processFile(at url: inout URL) throws { - let fileManager = FileManager.default - - switch url.pathExtension.lowercased() { - case "lzma": - let decompressedURL = url.deletingPathExtension() - try SWCompression.decompress(file: url, to: decompressedURL, compressionType: .lzma) - url = decompressedURL - case "gz": - let decompressedURL = url.deletingPathExtension() - try SWCompression.decompress(file: url, to: decompressedURL, compressionType: .gzip) - url = decompressedURL - case "xz": - let decompressedURL = url.deletingPathExtension() - try SWCompression.decompress(file: url, to: decompressedURL, compressionType: .xz) - url = decompressedURL - case "bz2": - let decompressedURL = url.deletingPathExtension() - try SWCompression.decompress(file: url, to: decompressedURL, compressionType: .bzip2) - url = decompressedURL - default: - throw FileProcessingError.unsupportedFileExtension(url.pathExtension) - } -} \ No newline at end of file + +// MARK: - File management +extension TweakHandler { + private static func createDirectoryIfNeeded(at url: URL) throws { + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: url.path) { + try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + } + } + + public static func findExecutable(at frameworkURL: URL) throws -> URL? { + + let infoPlistURL = frameworkURL.appendingPathComponent("Info.plist") + + let plistData = try Data(contentsOf: infoPlistURL) + if let plist = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any], + let executableName = plist["CFBundleExecutable"] as? String { + let executableURL = frameworkURL.appendingPathComponent(executableName) + return executableURL + } else { + Debug.shared.log(message: "CFBundleExecutable not found in Info.plist") + return nil + } + } + + private static func moveFile(from sourceURL: URL, to destinationURL: URL) throws { + let fileManager = FileManager.default + if fileManager.fileExists(atPath: destinationURL.path) { + Debug.shared.log(message: "File already exists at destination: \(destinationURL)") + } else { + try fileManager.moveItem(at: sourceURL, to: destinationURL) + } + } +} From 32cadbf1fd2d0bafb83c13d8829e3888bff1c834 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:57:11 -0400 Subject: [PATCH 270/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 6809cb28..e7458cf1 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -1,6 +1,7 @@ import UIKit import ZIPFoundation import os.log +import Foundation // Add this import protocol FileHandlingDelegate: AnyObject { var documentsDirectory: URL { get } From d983298bd5d3554810d0919d5504cf7ea77cbce8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 06:59:45 -0400 Subject: [PATCH 271/391] Update SigningsDylibViewController.swift --- .../SigningsDylibViewController.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift index 3231432e..56d5add1 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift @@ -1,4 +1,4 @@ -import UIKit + import UIKit import Foundation // Import Foundation for Process class class SigningsDylibViewController: UITableViewController { @@ -148,4 +148,9 @@ class SigningsDylibViewController: UITableViewController { } return nil } + + // Ensure fetchSources is accessible + internal func fetchSources() { + // Implementation of fetchSources + } } \ No newline at end of file From f06bfeac70201234c793817f3f3bd3e1d8ca59d8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:04:20 -0400 Subject: [PATCH 272/391] Update SigningsViewController.swift --- .../SigningViewController/SigningsViewController.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift index 18e47b0e..9a8483c5 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift @@ -1,10 +1,3 @@ -// -// SigningsViewController.swift -// feather -// -// Created by samara on 26.10.2024. -// - import UIKit import CoreData From e2aa67bfc76cf5807f9ff9f93e2641d08c91f720 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:08:04 -0400 Subject: [PATCH 273/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 8c964e45..b93304e9 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -6,8 +6,8 @@ class LibraryViewController: UITableViewController { var signedApps: [SignedApps]? var downloadedApps: [DownloadedApps]? - var filteredSignedApps: [SignedApps] = - var filteredDownloadedApps: [DownloadedApps] = + var filteredSignedApps: [SignedApps] = [] + var filteredDownloadedApps: [DownloadedApps] = [] var installer: Installer? @@ -242,11 +242,11 @@ extension LibraryViewController: UISearchResultsUpdating { filteredSignedApps = signedApps?.filter { app in return app.name?.lowercased().contains(searchText.lowercased()) ?? false - } ?? + } ?? [] filteredDownloadedApps = downloadedApps?.filter { app in return app.name?.lowercased().contains(searchText.lowercased()) ?? false - } ?? + } ?? [] tableView.reloadData() } From 3afaf6e57e50718d175dba6596f25b129dd27052 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:12:46 -0400 Subject: [PATCH 274/391] Update HomeViewUtilities.swift --- iOS/Views/Home/HomeViewUtilities.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/iOS/Views/Home/HomeViewUtilities.swift b/iOS/Views/Home/HomeViewUtilities.swift index 9cb946e9..6b4487ff 100644 --- a/iOS/Views/Home/HomeViewUtilities.swift +++ b/iOS/Views/Home/HomeViewUtilities.swift @@ -59,20 +59,18 @@ class HomeViewUtilities { /// - title: The title for the error alert. func handleError(in viewController: UIViewController, error: Error, withTitle title: String) { var message: String - var logType: OSLogType = .error if let fileError = error as? FileAppError { switch fileError { case .fileNotFound(let fileName): message = "File not found: \(fileName). Please check the file name and try again." - logType = .info // Log as info, user error + logger.info("File not found: \(fileName).") case .fileAlreadyExists(let fileName): message = "A file with the name \(fileName) already exists. Please choose a different name." - logType = .info + logger.info("File already exists: \(fileName).") case .unknown(let underlyingError): message = "An unknown error occurred: \(underlyingError.localizedDescription)" logger.error("Unknown error: \(underlyingError.localizedDescription)") - // Log the underlyingError here using os_log default: message = error.localizedDescription } @@ -139,16 +137,16 @@ class HomeViewUtilities { /// - cancelHandler: Handler to be executed when the "Cancel" action is tapped. /// - viewController: The view controller to present the alert in. func showInputAlert(title: String?, message: String?, textFieldHandler: ((UITextField) -> Void)?, okHandler: ((String?) -> Void)?, cancelHandler: (() -> Void)?, in viewController: UIViewController) { - let okAction = AlertActionConfig(title: "OK", style: .default) { [weak alert] in - let textField = alert?.textFields?.first + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addTextField(configurationHandler: textFieldHandler) + + let okAction = AlertActionConfig(title: "OK", style: .default) { + let textField = alert.textFields?.first okHandler?(textField?.text) } let cancelAction = AlertActionConfig(title: "Cancel", style: .cancel, handler: cancelHandler) let config = AlertConfig(title: title, message: message, style: .alert, actions: [okAction, cancelAction], preferredAction: nil, completionHandler: nil) - let alert = UIAlertController(title: config.title, message: config.message, preferredStyle: config.style) - alert.addTextField(configurationHandler: textFieldHandler) - for actionConfig in config.actions { let action = UIAlertAction(title: actionConfig.title, style: actionConfig.style) { _ in actionConfig.handler?() From 17e28204e3e760827a74c58eff9b49d2123590b4 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:14:54 -0400 Subject: [PATCH 275/391] Update AppSigner.swift --- Shared/Magic/AppSigner.swift | 564 ++++++++++++++++++----------------- 1 file changed, 283 insertions(+), 281 deletions(-) diff --git a/Shared/Magic/AppSigner.swift b/Shared/Magic/AppSigner.swift index 902ca48b..c5895c79 100644 --- a/Shared/Magic/AppSigner.swift +++ b/Shared/Magic/AppSigner.swift @@ -1,323 +1,325 @@ +// +// AppSigner.swift +// feather +// +// Created by HAHALOSAH on 7/17/24. +// Copyright (c) 2024 Samara M (khcrysalis) +// + import Foundation import UIKit import AlertKit import CoreData func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, signingOptions: SigningDataWrapper, appPath: URL, completion: @escaping (Result<(URL, NSManagedObject), Error>) -> Void) { - UIApplication.shared.isIdleTimerDisabled = true - DispatchQueue(label: "Signing").async { - let fileManager = FileManager.default - let tmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) - let tmpDirApp = tmpDir.appendingPathComponent(appPath.lastPathComponent) - var iconURL = "" - - do { - Debug.shared.log(message: "============================================") - Debug.shared.log(message: "\(mainOptions.mainOptions)") - Debug.shared.log(message: "============================================") - Debug.shared.log(message: "\(signingOptions.signingOptions)") - Debug.shared.log(message: "============================================") - try fileManager.createDirectory(at: tmpDir, withIntermediateDirectories: true) - try fileManager.copyItem(at: appPath, to: tmpDirApp) - - if let info = NSDictionary(contentsOf: tmpDirApp.appendingPathComponent("Info.plist"))?.mutableCopy() as? NSMutableDictionary { - try updateInfoPlist(infoDict: info, main: mainOptions, options: signingOptions, icon: mainOptions.mainOptions.iconURL, app: tmpDirApp) - - if let iconsDict = info["CFBundleIcons"] as? [String: Any], - let primaryIconsDict = iconsDict["CFBundlePrimaryIcon"] as? [String: Any], - let iconFiles = primaryIconsDict["CFBundleIconFiles"] as? [String], - let iconFileName = iconFiles.first { - iconURL = iconFileName - } - } else { - throw NSError(domain: "AppSigner", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to read Info.plist"]) - } - - let handler = TweakHandler(urls: signingOptions.signingOptions.toInject, app: tmpDirApp) - try handler.getInputFiles() - - if !mainOptions.mainOptions.removeInjectPaths.isEmpty { - if let appexe = try? TweakHandler.findExecutable(at: tmpDirApp) { - _ = uninstallDylibs(filePath: appexe.path, dylibPaths: mainOptions.mainOptions.removeInjectPaths) - } - } - - try updatePlugIns(options: signingOptions, app: tmpDirApp) - try removeDumbAssPlaceHolderExtension(options: signingOptions, app: tmpDirApp) - try updateMobileProvision(app: tmpDirApp) - - let certPath = try CoreDataManager.shared.getCertifcatePath(source: mainOptions.mainOptions.certificate) - let provisionPath = certPath.appendingPathComponent(mainOptions.mainOptions.certificate?.provisionPath ?? "").path - let p12Path = certPath.appendingPathComponent(mainOptions.mainOptions.certificate?.p12Path ?? "").path - - Debug.shared.log(message: "🦋 Start Signing 🦋") - - try signAppWithZSign(tmpDirApp: tmpDirApp, certPaths: (provisionPath, p12Path), password: mainOptions.mainOptions.certificate?.password ?? "", main: mainOptions, options: signingOptions) - - Debug.shared.log(message: "🦋 End Signing 🦋") - - let signedUUID = UUID().uuidString - try fileManager.createDirectory(at: getDocumentsDirectory().appendingPathComponent("Apps/Signed"), withIntermediateDirectories: true) - let signedPath = getDocumentsDirectory().appendingPathComponent("Apps/Signed").appendingPathComponent(signedUUID) - try fileManager.moveItem(at: tmpDir, to: signedPath) - - DispatchQueue.main.async { - var signedAppObject: NSManagedObject? = nil - - CoreDataManager.shared.addToSignedApps( - version: mainOptions.mainOptions.version ?? bundle.version ?? "", - name: mainOptions.mainOptions.name ?? bundle.name ?? "", - bundleidentifier: mainOptions.mainOptions.bundleId ?? bundle.bundleId ?? "", - iconURL: iconURL, - uuid: signedUUID, - appPath: appPath.lastPathComponent, - timeToLive: mainOptions.mainOptions.certificate?.certData?.expirationDate ?? Date(), - teamName: mainOptions.mainOptions.certificate?.certData?.name ?? "", - originalSourceURL: bundle.sourceURL - ) { result in - switch result { - case .success(let signedApp): - signedAppObject = signedApp - case .failure(let error): - Debug.shared.log(message: "signApp: \(error)", type: .error) - completion(.failure(error)) - return - } - - Debug.shared.log(message: String.localized("SUCCESS_SIGNED", arguments: [mainOptions.mainOptions.name ?? bundle.name ?? String.localized("UNKNOWN")]), type: .success) - Debug.shared.log(message: "============================================") - - UIApplication.shared.isIdleTimerDisabled = false - if let signedAppObject = signedAppObject { - completion(.success((signedPath, signedAppObject))) - } else { - completion(.failure(NSError(domain: "AppSigner", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to create signed app object"]))) - } - } - } - } catch { - DispatchQueue.main.async { - UIApplication.shared.isIdleTimerDisabled = false - Debug.shared.log(message: "signApp: \(error)", type: .critical) - completion(.failure(error)) - } - } - } + UIApplication.shared.isIdleTimerDisabled = true + DispatchQueue(label: "Signing").async { + let fileManager = FileManager.default + let tmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) + let tmpDirApp = tmpDir.appendingPathComponent(appPath.lastPathComponent) + var iconURL = "" + + + do { + Debug.shared.log(message: "============================================") + Debug.shared.log(message: "\(mainOptions.mainOptions)") + Debug.shared.log(message: "============================================") + Debug.shared.log(message: "\(signingOptions.signingOptions)") + Debug.shared.log(message: "============================================") + try fileManager.createDirectory(at: tmpDir, withIntermediateDirectories: true) + try fileManager.copyItem(at: appPath, to: tmpDirApp) + + if let info = NSDictionary(contentsOf: tmpDirApp.appendingPathComponent("Info.plist"))!.mutableCopy() as? NSMutableDictionary { + try updateInfoPlist(infoDict: info, main: mainOptions, options: signingOptions, icon: mainOptions.mainOptions.iconURL, app: tmpDirApp) + + if let iconsDict = info["CFBundleIcons"] as? [String: Any], + let primaryIconsDict = iconsDict["CFBundlePrimaryIcon"] as? [String: Any], + let iconFiles = primaryIconsDict["CFBundleIconFiles"] as? [String], + let iconFileName = iconFiles.first { + iconURL = iconFileName + } + } + + let handler = TweakHandler(urls: signingOptions.signingOptions.toInject, app: tmpDirApp) + try handler.getInputFiles() + + if !mainOptions.mainOptions.removeInjectPaths.isEmpty { + if let appexe = try? TweakHandler.findExecutable(at: tmpDirApp) { + _ = uninstallDylibs(filePath: appexe.path, dylibPaths: mainOptions.mainOptions.removeInjectPaths) + } + } + + try updatePlugIns(options: signingOptions, app: tmpDirApp) + try removeDumbAssPlaceHolderExtension(options: signingOptions, app: tmpDirApp) + try updateMobileProvision(app: tmpDirApp) + + let certPath = try CoreDataManager.shared.getCertifcatePath(source: mainOptions.mainOptions.certificate) + let provisionPath = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.provisionPath ?? "")").path + let p12Path = certPath.appendingPathComponent("\(mainOptions.mainOptions.certificate?.p12Path ?? "")").path + + Debug.shared.log(message: "🦋 Start Signing 🦋") + + try signAppWithZSign(tmpDirApp: tmpDirApp, certPaths: (provisionPath, p12Path), password: mainOptions.mainOptions.certificate?.password ?? "", main: mainOptions, options: signingOptions) + + Debug.shared.log(message: "🦋 End Signing 🦋") + + let signedUUID = UUID().uuidString + try fileManager.createDirectory(at: getDocumentsDirectory().appendingPathComponent("Apps/Signed"), withIntermediateDirectories: true) + let signedPath = getDocumentsDirectory().appendingPathComponent("Apps/Signed").appendingPathComponent(signedUUID) + try fileManager.moveItem(at: tmpDir, to: signedPath) + + DispatchQueue.main.async { + var signedAppObject: NSManagedObject? = nil + + CoreDataManager.shared.addToSignedApps( + version: (mainOptions.mainOptions.version ?? bundle.version)!, + name: (mainOptions.mainOptions.name ?? bundle.name)!, + bundleidentifier: (mainOptions.mainOptions.bundleId ?? bundle.bundleId)!, + iconURL: iconURL, + uuid: signedUUID, + appPath: appPath.lastPathComponent, + timeToLive: mainOptions.mainOptions.certificate?.certData?.expirationDate ?? Date(), + teamName: mainOptions.mainOptions.certificate?.certData?.name ?? "", + originalSourceURL: bundle.sourceURL + ) { result in + + + switch result { + case .success(let signedApp): + signedAppObject = signedApp + case .failure(let error): + Debug.shared.log(message: "signApp: \(error)", type: .error) + completion(.failure(error)) + } + } + + Debug.shared.log(message: String.localized("SUCCESS_SIGNED", arguments: "\((mainOptions.mainOptions.name ?? bundle.name) ?? String.localized("UNKNOWN"))"), type: .success) + Debug.shared.log(message: "============================================") + + UIApplication.shared.isIdleTimerDisabled = false + completion(.success((signedPath, signedAppObject!))) + } + } catch { + DispatchQueue.main.async { + UIApplication.shared.isIdleTimerDisabled = false + Debug.shared.log(message: "signApp: \(error)", type: .critical) + completion(.failure(error)) + } + } + } } + func resignApp(certificate: Certificate, appPath: URL, completion: @escaping (Bool) -> Void) { - UIApplication.shared.isIdleTimerDisabled = true - DispatchQueue(label: "Resigning").async { - do { + UIApplication.shared.isIdleTimerDisabled = true + DispatchQueue(label: "Resigning").async { + do { let certPath = try CoreDataManager.shared.getCertifcatePath(source: certificate) - let provisionPath = certPath.appendingPathComponent(certificate.provisionPath ?? "").path - let p12Path = certPath.appendingPathComponent(certificate.p12Path ?? "").path - - Debug.shared.log(message: "============================================") - Debug.shared.log(message: "🦋 Start Resigning 🦋") - - try signAppWithZSign(tmpDirApp: appPath, certPaths: (provisionPath, p12Path), password: certificate.password ?? "") - - Debug.shared.log(message: "🦋 End Resigning 🦋") - DispatchQueue.main.async { - UIApplication.shared.isIdleTimerDisabled = false - Debug.shared.log(message: String.localized("SUCCESS_RESIGN"), type: .success) - completion(true) - } - Debug.shared.log(message: "============================================") - } catch { - Debug.shared.log(message: "\(error)", type: .warning) - completion(false) - } - } + let provisionPath = certPath.appendingPathComponent("\(certificate.provisionPath ?? "")").path + let p12Path = certPath.appendingPathComponent("\(certificate.p12Path ?? "")").path + + Debug.shared.log(message: "============================================") + Debug.shared.log(message: "🦋 Start Resigning 🦋") + + try signAppWithZSign(tmpDirApp: appPath, certPaths: (provisionPath, p12Path), password: certificate.password ?? "") + + Debug.shared.log(message: "🦋 End Resigning 🦋") + DispatchQueue.main.async { + UIApplication.shared.isIdleTimerDisabled = false + Debug.shared.log(message: String.localized("SUCCESS_RESIGN"), type: .success) + } + Debug.shared.log(message: "============================================") + completion(true) + } catch { + Debug.shared.log(message: "\(error)", type: .warning) + completion(false) + } + } } private func signAppWithZSign(tmpDirApp: URL, certPaths: (provisionPath: String, p12Path: String), password: String, main: SigningMainDataWrapper? = nil, options: SigningDataWrapper? = nil) throws { - if zsign(tmpDirApp.path, - certPaths.provisionPath, - certPaths.p12Path, - password, - main?.mainOptions.bundleId ?? "", - main?.mainOptions.name ?? "", - main?.mainOptions.version ?? "", - options?.signingOptions.removeProvisioningFile ?? true - ) != 0 { - throw NSError(domain: "AppSigningErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: String.localized("ERROR_ZSIGN_FAILED")]) - } + if zsign(tmpDirApp.path, + certPaths.provisionPath, + certPaths.p12Path, + password, + main?.mainOptions.bundleId ?? "", + main?.mainOptions.name ?? "", + main?.mainOptions.version ?? "", + options?.signingOptions.removeProvisioningFile ?? true + ) != 0 { + throw NSError(domain: "AppSigningErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: String.localized("ERROR_ZSIGN_FAILED")]) + } } func injectDylib(filePath: String, dylibPath: String, weakInject: Bool) -> Bool { - let bCreate: Bool = false - let success = InjectDyLib(filePath, dylibPath, weakInject, bCreate) - return success + let bCreate: Bool = false + let success = InjectDyLib(filePath, dylibPath, weakInject, bCreate) + return success } func changeDylib(filePath: String, oldPath: String, newPath: String) -> Bool { - let success = ChangeDylibPath(filePath, oldPath, newPath) - return success + let success = ChangeDylibPath(filePath, oldPath, newPath) + return success } func updateMobileProvision(app: URL) throws { - let provisioningFilePath = app.appendingPathComponent("embedded.mobileprovision") - if FileManager.default.fileExists(atPath: provisioningFilePath.path) { - do { - try FileManager.default.removeItem(at: provisioningFilePath) - Debug.shared.log(message: "Embedded.mobileprovision file removed successfully!") - } catch { - throw error - } - } else { - Debug.shared.log(message: "Could not find any mobileprovision to remove. ") - } + let provisioningFilePath = app.appendingPathComponent("embedded.mobileprovision") + if FileManager.default.fileExists(atPath: provisioningFilePath.path) { + do { + try FileManager.default.removeItem(at: provisioningFilePath) + Debug.shared.log(message: "Embedded.mobileprovision file removed successfully!") + } catch { + throw error + } + } else { + Debug.shared.log(message: "Could not find any mobileprovision to remove. ") + } } func listDylibs(filePath: String) -> [String]? { - let dylibPathsArray = NSMutableArray() - - let success = ListDylibs(filePath, dylibPathsArray) - - if success { - let dylibPaths = dylibPathsArray as! [String] - return dylibPaths - } else { - Debug.shared.log(message: "Failed to list dylibs.") - return nil - } + let dylibPathsArray = NSMutableArray() + + let success = ListDylibs(filePath, dylibPathsArray) + + if success { + let dylibPaths = dylibPathsArray as! [String] + return dylibPaths + } else { + Debug.shared.log(message: "Failed to list dylibs.") + return nil + } } func uninstallDylibs(filePath: String, dylibPaths: [String]) -> Bool { - return UninstallDylibs(filePath, dylibPaths) + return UninstallDylibs(filePath, dylibPaths) } + func updatePlugIns(options: SigningDataWrapper, app: URL) throws { - if options.signingOptions.removePlugins { - let fileManager = FileManager.default - let path = app.appendingPathComponent("PlugIns") - if fileManager.fileExists(atPath: path.path) { - do { - try fileManager.removeItem(at: path) - Debug.shared.log(message: "Removed PlugIns!") - } catch { - throw error - } - } else { - Debug.shared.log(message: "Could not find any PlugIns to remove.") - } - } + if options.signingOptions.removePlugins { + let filemanager = FileManager.default + let path = app.appendingPathComponent("PlugIns") + if filemanager.fileExists(atPath: path.path) { + do { + try filemanager.removeItem(at: path) + Debug.shared.log(message: "Removed PlugIns!") + } catch { + throw error + } + } else { + Debug.shared.log(message: "Could not find any PlugIns to remove.") + } + } } func removeDumbAssPlaceHolderExtension(options: SigningDataWrapper, app: URL) throws { - if options.signingOptions.removeWatchPlaceHolder { - let fileManager = FileManager.default - let path = app.appendingPathComponent("com.apple.WatchPlaceholder") - if fileManager.fileExists(atPath: path.path) { - do { - try fileManager.removeItem(at: path) - Debug.shared.log(message: "Removed placeholder watch app!") - } catch { - throw error - } - } else { - Debug.shared.log(message: "Placeholder watch app not found.") - } - } + if options.signingOptions.removeWatchPlaceHolder { + let filemanager = FileManager.default + let path = app.appendingPathComponent("com.apple.WatchPlaceholder") + if filemanager.fileExists(atPath: path.path) { + do { + try filemanager.removeItem(at: path) + Debug.shared.log(message: "Removed placeholder watch app!") + } catch { + throw error + } + } else { + Debug.shared.log(message: "Placeholder watch app not found.") + } + } } func updateInfoPlist(infoDict: NSMutableDictionary, main: SigningMainDataWrapper, options: SigningDataWrapper, icon: UIImage?, app: URL) throws { - if let iconURL = main.mainOptions.iconURL { - - let imageSizes = [ - (width: 120, height: 120, name: "FRIcon60x60@2x.png"), - (width: 152, height: 152, name: "FRIcon76x76@2x~ipad.png") - ] - - for imageSize in imageSizes { - let resizedImage = iconURL.resize(imageSize.width, imageSize.height) - let imageData = resizedImage.pngData() - let fileURL = app.appendingPathComponent(imageSize.name) - - do { - try imageData?.write(to: fileURL) - Debug.shared.log(message: "Saved image to: \(fileURL)") - } catch { - Debug.shared.log(message: "Failed to save image: \(imageSize.name), error: \(error)") - throw error - } - } - - let cfBundleIcons: [String: Any] = [ - "CFBundlePrimaryIcon": [ - "CFBundleIconFiles": ["FRIcon60x60"], - "CFBundleIconName": "FRIcon" - ] - ] - - let cfBundleIconsIpad: [String: Any] = [ - "CFBundlePrimaryIcon": [ - "CFBundleIconFiles": ["FRIcon60x60", "FRIcon76x76"], - "CFBundleIconName": "FRIcon" - ] - ] - - infoDict["CFBundleIcons"] = cfBundleIcons - infoDict["CFBundleIcons~ipad"] = cfBundleIconsIpad - - } else { - Debug.shared.log(message: "updateInfoPlist.updateicon: Does not include an icon, skipping!") - } - - if options.signingOptions.forceTryToLocalize && (main.mainOptions.name != nil) { - if let displayName = infoDict.value(forKey: "CFBundleDisplayName") as? String { - if displayName != main.mainOptions.name { - updateLocalizedInfoPlist(in: app, newDisplayName: main.mainOptions.name!) - } - } else { - Debug.shared.log(message: "updateInfoPlist.displayName: CFBundleDisplayName not found, skipping!") - } - } + if (main.mainOptions.iconURL != nil) { + + let imageSizes = [ + (width: 120, height: 120, name: "FRIcon60x60@2x.png"), + (width: 152, height: 152, name: "FRIcon76x76@2x~ipad.png") + ] + + for imageSize in imageSizes { + let resizedImage = main.mainOptions.iconURL!.resize(imageSize.width, imageSize.height) + let imageData = resizedImage.pngData() + let fileURL = app.appendingPathComponent(imageSize.name) + + do { + try imageData?.write(to: fileURL) + Debug.shared.log(message: "Saved image to: \(fileURL)") + } catch { + Debug.shared.log(message: "Failed to save image: \(imageSize.name), error: \(error)") + throw error + } + } + + let cfBundleIcons: [String: Any] = [ + "CFBundlePrimaryIcon": [ + "CFBundleIconFiles": ["FRIcon60x60"], + "CFBundleIconName": "FRIcon" + ] + ] + + let cfBundleIconsIpad: [String: Any] = [ + "CFBundlePrimaryIcon": [ + "CFBundleIconFiles": ["FRIcon60x60", "FRIcon76x76"], + "CFBundleIconName": "FRIcon" + ] + ] + + infoDict["CFBundleIcons"] = cfBundleIcons + infoDict["CFBundleIcons~ipad"] = cfBundleIconsIpad + + } else { + Debug.shared.log(message: "updateInfoPlist.updateicon: Does not include an icon, skipping!") + } + + if options.signingOptions.forceTryToLocalize && (main.mainOptions.name != nil) { + if let displayName = infoDict.value(forKey: "CFBundleDisplayName") as? String { + if displayName != main.mainOptions.name { + updateLocalizedInfoPlist(in: app, newDisplayName: main.mainOptions.name!) + } + } else { + Debug.shared.log(message: "updateInfoPlist.displayName: CFBundleDisplayName not found, skipping!") + } + } - if options.signingOptions.forceFileSharing { infoDict.setObject(true, forKey: "UISupportsDocumentBrowser" as NSCopying) } - if options.signingOptions.forceiTunesFileSharing { infoDict.setObject(true, forKey: "UIFileSharingEnabled" as NSCopying) } - if options.signingOptions.removeSupportedDevices { infoDict.removeObject(forKey: "UISupportedDevices") } - if options.signingOptions.removeURLScheme { infoDict.removeObject(forKey: "CFBundleURLTypes") } - if options.signingOptions.forceProMotion { infoDict.setObject(true, forKey: "CADisableMinimumFrameDurationOnPhone" as NSCopying)} - if options.signingOptions.forceGameMode { infoDict.setObject(true, forKey: "GCSupportsGameMode" as NSCopying)} - if options.signingOptions.forceForceFullScreen { infoDict.setObject(true, forKey: "UIRequiresFullScreen" as NSCopying) } - if options.signingOptions.forceMinimumVersion != "Automatic" { infoDict.setObject(options.signingOptions.forceMinimumVersion, forKey: "MinimumOSVersion" as NSCopying) } - if options.signingOptions.forceLightDarkAppearence != "Automatic" { infoDict.setObject(options.signingOptions.forceLightDarkAppearence, forKey: "UIUserInterfaceStyle" as NSCopying)} - try infoDict.write(to: app.appendingPathComponent("Info.plist")) + if options.signingOptions.forceFileSharing { infoDict.setObject(true, forKey: "UISupportsDocumentBrowser" as NSCopying) } + if options.signingOptions.forceiTunesFileSharing { infoDict.setObject(true, forKey: "UIFileSharingEnabled" as NSCopying) } + if options.signingOptions.removeSupportedDevices { infoDict.removeObject(forKey: "UISupportedDevices") } + if options.signingOptions.removeURLScheme { infoDict.removeObject(forKey: "CFBundleURLTypes") } + if options.signingOptions.forceProMotion { infoDict.setObject(true, forKey: "CADisableMinimumFrameDurationOnPhone" as NSCopying)} + if options.signingOptions.forceGameMode { infoDict.setObject(true, forKey: "GCSupportsGameMode" as NSCopying)} + if options.signingOptions.forceForceFullScreen { infoDict.setObject(true, forKey: "UIRequiresFullScreen" as NSCopying) } + if options.signingOptions.forceMinimumVersion != "Automatic" { infoDict.setObject(options.signingOptions.forceMinimumVersion, forKey: "MinimumOSVersion" as NSCopying) } + if options.signingOptions.forceLightDarkAppearence != "Automatic" { infoDict.setObject(options.signingOptions.forceLightDarkAppearence, forKey: "UIUserInterfaceStyle" as NSCopying)} + try infoDict.write(to: app.appendingPathComponent("Info.plist")) } func updateLocalizedInfoPlist(in appDirectory: URL, newDisplayName: String) { - let fileManager = FileManager.default - do { - let contents = try fileManager.contentsOfDirectory(at: appDirectory, includingPropertiesForKeys: nil) - let localizationBundles = contents.filter { $0.pathExtension == "lproj" } - - guard !localizationBundles.isEmpty else { - Debug.shared.log(message: "No .lproj directories found in \(appDirectory.path), skipping!") - return - } - - for localizationBundle in localizationBundles { - let infoPlistStringsURL = localizationBundle.appendingPathComponent("InfoPlist.strings") - - if fileManager.fileExists(atPath: infoPlistStringsURL.path) { - var localizedStrings = try String(contentsOf: infoPlistStringsURL, encoding: .utf8) - let localizedDict = NSDictionary(contentsOf: infoPlistStringsURL) as? [String: String] - - if localizedDict?["CFBundleDisplayName"] != newDisplayName { - localizedStrings = localizedStrings.replacingOccurrences(of: localizedDict?["CFBundleDisplayName"] ?? "", with: newDisplayName) - try localizedStrings.write(to: infoPlistStringsURL, atomically: true, encoding: .utf8) - Debug.shared.log(message: "Updated CFBundleDisplayName in \(infoPlistStringsURL.path)") - } - } - } - } catch { - Debug.shared.log(message: "Unable to localize, skipping!", type: .debug) - } -} - -func getDocumentsDirectory() -> URL { - return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! + let fileManager = FileManager.default + do { + let contents = try fileManager.contentsOfDirectory(at: appDirectory, includingPropertiesForKeys: nil) + let localizationBundles = contents.filter { $0.pathExtension == "lproj" } + + guard !localizationBundles.isEmpty else { + Debug.shared.log(message: "No .lproj directories found in \(appDirectory.path), skipping!") + return + } + + for localizationBundle in localizationBundles { + let infoPlistStringsURL = localizationBundle.appendingPathComponent("InfoPlist.strings") + + if fileManager.fileExists(atPath: infoPlistStringsURL.path) { + var localizedStrings = try String(contentsOf: infoPlistStringsURL, encoding: .utf8) + let localizedDict = NSDictionary(contentsOf: infoPlistStringsURL) as! [String: String] + + if localizedDict["CFBundleDisplayName"] != newDisplayName { + localizedStrings = localizedStrings.replacingOccurrences(of: localizedDict["CFBundleDisplayName"] ?? "", with: newDisplayName) + try localizedStrings.write(to: infoPlistStringsURL, atomically: true, encoding: .utf8) + Debug.shared.log(message: "Updated CFBundleDisplayName in \(infoPlistStringsURL.path)") + } + } + } + } catch { + Debug.shared.log(message: "Unable to localize, skipping!", type: .debug) + } } \ No newline at end of file From e09b80543908890bfa49622bd3c5966821b621c3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:40:03 -0400 Subject: [PATCH 276/391] Create FileHelpers.swift --- iOS/Views/Extra/FileHelpers.swift | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 iOS/Views/Extra/FileHelpers.swift diff --git a/iOS/Views/Extra/FileHelpers.swift b/iOS/Views/Extra/FileHelpers.swift new file mode 100644 index 00000000..8065ab8d --- /dev/null +++ b/iOS/Views/Extra/FileHelpers.swift @@ -0,0 +1,7 @@ +import Foundation + +/// Returns the URL for the app's Documents directory. +func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] +} \ No newline at end of file From a89ebbe941a87327a4c45c9e9a04f565aea39ae4 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:42:07 -0400 Subject: [PATCH 277/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index e7458cf1..c6d41909 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -2,6 +2,7 @@ import UIKit import ZIPFoundation import os.log import Foundation // Add this import +import fileHelpers protocol FileHandlingDelegate: AnyObject { var documentsDirectory: URL { get } From 8d7cd44047151feef4240803bc9ff43fe749204f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:42:20 -0400 Subject: [PATCH 278/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index c6d41909..38ed9364 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -2,7 +2,7 @@ import UIKit import ZIPFoundation import os.log import Foundation // Add this import -import fileHelpers +import FileHelpers protocol FileHandlingDelegate: AnyObject { var documentsDirectory: URL { get } From 4cf373a9786727717bc81e412d6a470abf8d57d2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:42:44 -0400 Subject: [PATCH 279/391] Update ResetDataClass.swift --- iOS/Views/Settings/Reset/ResetDataClass.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Views/Settings/Reset/ResetDataClass.swift b/iOS/Views/Settings/Reset/ResetDataClass.swift index d631ffe4..7c6a47fd 100644 --- a/iOS/Views/Settings/Reset/ResetDataClass.swift +++ b/iOS/Views/Settings/Reset/ResetDataClass.swift @@ -7,6 +7,7 @@ import Foundation import Nuke +import FileHelpers class ResetDataClass { static let shared = ResetDataClass() From 0ab7eefee86681d5eafcbbb021522f4be6d93b79 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:43:01 -0400 Subject: [PATCH 280/391] Update LogsViewController.swift --- iOS/Views/Settings/View Logs/LogsViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Views/Settings/View Logs/LogsViewController.swift b/iOS/Views/Settings/View Logs/LogsViewController.swift index b4bcee46..7b2f7be9 100644 --- a/iOS/Views/Settings/View Logs/LogsViewController.swift +++ b/iOS/Views/Settings/View Logs/LogsViewController.swift @@ -1,4 +1,5 @@ import UIKit +import FileHelpers class LogsViewController: UIViewController { private var tableView: UITableView! From d189158858c7f722bece3bbc4cba45ea63bd143e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:43:33 -0400 Subject: [PATCH 281/391] Update SettingsViewController.swift --- iOS/Views/Settings/SettingsViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Views/Settings/SettingsViewController.swift b/iOS/Views/Settings/SettingsViewController.swift index 4a4bc610..0ca0b32f 100644 --- a/iOS/Views/Settings/SettingsViewController.swift +++ b/iOS/Views/Settings/SettingsViewController.swift @@ -9,6 +9,7 @@ import UIKit import Nuke import SwiftUI +import FileHelpers class SettingsViewController: FRSTableViewController { let aboutSection = [ From 0c034adb1a463885c252b47b73ba133e6914dc3e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:44:18 -0400 Subject: [PATCH 282/391] Update SigningsDylibViewController.swift --- .../SigningViewController/SigningsDylibViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift index 56d5add1..4b38b628 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift @@ -1,5 +1,6 @@ import UIKit import Foundation // Import Foundation for Process class +import FileHelpers class SigningsDylibViewController: UITableViewController { var applicationPath: URL From 14ad562b4a228d6c5da570ceb2cc79071af32248 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:44:38 -0400 Subject: [PATCH 283/391] Update SigningsViewController.swift --- .../Signing/SigningViewController/SigningsViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift index 9a8483c5..3acb8e17 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift @@ -1,5 +1,6 @@ import UIKit import CoreData +import FileHelpers struct BundleOptions { var name: String? From 7df6486659c54a8ddb6f5d55d09fceffb022bd7f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:45:18 -0400 Subject: [PATCH 284/391] Update DownloadCertificate.swift --- Shared/Server/DownloadCertificate.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Shared/Server/DownloadCertificate.swift b/Shared/Server/DownloadCertificate.swift index f1da3d4a..eef13b9a 100644 --- a/Shared/Server/DownloadCertificate.swift +++ b/Shared/Server/DownloadCertificate.swift @@ -1,4 +1,5 @@ import Foundation +import FileHelpers func getCertificates() { let sourceGET = SourceGET() From 338a7c6a8714c4320d88326e151151f2430e1159 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:45:53 -0400 Subject: [PATCH 285/391] Update Server+TLS.swift --- Shared/Server/Server+TLS.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Shared/Server/Server+TLS.swift b/Shared/Server/Server+TLS.swift index d2bba2b3..07a7745f 100644 --- a/Shared/Server/Server+TLS.swift +++ b/Shared/Server/Server+TLS.swift @@ -12,6 +12,7 @@ import NIOSSL import NIOTLS import Vapor import SystemConfiguration.CaptiveNetwork +import FileHelpers func getLocalIPAddress() -> String? { var address: String? From 6a72c0a899f424ab6ab154d5f81a72dada599cda Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:46:09 -0400 Subject: [PATCH 286/391] Update Logger.swift --- Shared/Logging/Logger.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Shared/Logging/Logger.swift b/Shared/Logging/Logger.swift index 8a2adec7..3bfc878b 100644 --- a/Shared/Logging/Logger.swift +++ b/Shared/Logging/Logger.swift @@ -9,6 +9,7 @@ import AlertKit import Foundation import OSLog +import FileHelpers public enum LogType { /// Default From 3ba11fb12bce280125ee6bb41f02dee96f48c8f3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:46:25 -0400 Subject: [PATCH 287/391] Update AppSigner.swift --- Shared/Magic/AppSigner.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Shared/Magic/AppSigner.swift b/Shared/Magic/AppSigner.swift index c5895c79..f8ae1bb0 100644 --- a/Shared/Magic/AppSigner.swift +++ b/Shared/Magic/AppSigner.swift @@ -10,6 +10,7 @@ import Foundation import UIKit import AlertKit import CoreData +import FileHelpers func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, signingOptions: SigningDataWrapper, appPath: URL, completion: @escaping (Result<(URL, NSManagedObject), Error>) -> Void) { UIApplication.shared.isIdleTimerDisabled = true From 33fa76a39a024ecbded49e56107e7d9bc4852fef Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:54:29 -0400 Subject: [PATCH 288/391] Update PopupViewController.swift --- iOS/Views/Extra/PopupViewController.swift | 147 +++++++++++----------- 1 file changed, 72 insertions(+), 75 deletions(-) diff --git a/iOS/Views/Extra/PopupViewController.swift b/iOS/Views/Extra/PopupViewController.swift index 12ebc5cb..341466c1 100644 --- a/iOS/Views/Extra/PopupViewController.swift +++ b/iOS/Views/Extra/PopupViewController.swift @@ -2,24 +2,90 @@ import Foundation import UIKit class PopupViewController: UIViewController { - + + // Nested PopupButton class + class PopupButton: UIButton { + var onTap: (() -> Void)? + private var originalBackgroundColor: UIColor? + + init(title: String, color: UIColor, titleColor: UIColor? = .white) { + super.init(frame: .zero) + setupButton(title: title, color: color, titlecolor: titleColor!) + addTarget(self, action: #selector(buttonPressed), for: .touchDown) + addTarget(self, action: #selector(buttonReleased), for: .touchUpInside) + addTarget(self, action: #selector(buttonReleased), for: .touchUpOutside) + addTarget(self, action: #selector(buttonCancelled), for: .touchCancel) + addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupButton(title: String.localized("DEFAULT"), color: .systemBlue, titlecolor: .white) + addTarget(self, action: #selector(buttonPressed), for: .touchDown) + addTarget(self, action: #selector(buttonReleased), for: .touchUpInside) + addTarget(self, action: #selector(buttonReleased), for: .touchUpOutside) + addTarget(self, action: #selector(buttonCancelled), for: .touchCancel) + addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + } + + private func setupButton(title: String, color: UIColor, titlecolor: UIColor) { + setTitle(title, for: .normal) + originalBackgroundColor = color + backgroundColor = color + setTitleColor(titlecolor, for: .normal) + titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) + layer.cornerRadius = 12 + layer.cornerCurve = .continuous + layer.masksToBounds = true + if #available(iOS 15.0, *) { + var config = UIButton.Configuration.filled() + config.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) + self.configuration = config + } else { + contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) + } + } + + @objc private func buttonPressed() { + UIView.animate(withDuration: 0.1) { + self.backgroundColor = self.originalBackgroundColor?.withAlphaComponent(0.6) + } + } + + @objc private func buttonReleased() { + UIView.animate(withDuration: 0.1) { + self.backgroundColor = self.originalBackgroundColor + } + } + + @objc private func buttonCancelled() { + UIView.animate(withDuration: 0.1) { + self.backgroundColor = self.originalBackgroundColor + } + } + + @objc private func buttonTapped() { + onTap?() + } + } + private let stackView = UIStackView() - + override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground setupStackView() } - + private func setupStackView() { stackView.axis = .vertical stackView.spacing = 10 stackView.alignment = .fill stackView.distribution = .fillEqually stackView.translatesAutoresizingMaskIntoConstraints = false - + view.addSubview(stackView) - + NSLayoutConstraint.activate([ stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), @@ -27,79 +93,10 @@ class PopupViewController: UIViewController { ]) } - - func configureButtons(_ buttons: [UIButton]) { + func configureButtons(_ buttons: [PopupButton]) { stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } buttons.forEach { button in stackView.addArrangedSubview(button) } } -} - -class PopupViewControllerButton: UIButton { - var onTap: (() -> Void)? - private var originalBackgroundColor: UIColor? - - init(title: String, color: UIColor, titleColor: UIColor? = .white) { - super.init(frame: .zero) - setupButton(title: title, color: color, titlecolor: titleColor!) - addTarget(self, action: #selector(buttonPressed), for: .touchDown) - addTarget(self, action: #selector(buttonReleased), for: .touchUpInside) - addTarget(self, action: #selector(buttonReleased), for: .touchUpOutside) - addTarget(self, action: #selector(buttonCancelled), for: .touchCancel) - addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) - - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - setupButton(title: String.localized("DEFAULT"), color: .systemBlue, titlecolor: .white) - addTarget(self, action: #selector(buttonPressed), for: .touchDown) - addTarget(self, action: #selector(buttonReleased), for: .touchUpInside) - addTarget(self, action: #selector(buttonReleased), for: .touchUpOutside) - addTarget(self, action: #selector(buttonCancelled), for: .touchCancel) - addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) - - } - - private func setupButton(title: String, color: UIColor, titlecolor: UIColor) { - setTitle(title, for: .normal) - originalBackgroundColor = color - backgroundColor = color - setTitleColor(titlecolor, for: .normal) - titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) - layer.cornerRadius = 12 - layer.cornerCurve = .continuous - layer.masksToBounds = true - if #available(iOS 15.0, *) { - var config = UIButton.Configuration.filled() - config.contentInsets = NSDirectionalEdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20) - self.configuration = config - } else { - // Fallback on earlier versions - contentEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 20) - } - } - - @objc private func buttonPressed() { - UIView.animate(withDuration: 0.1) { - self.backgroundColor = self.originalBackgroundColor?.withAlphaComponent(0.6) - } - } - - @objc private func buttonReleased() { - UIView.animate(withDuration: 0.1) { - self.backgroundColor = self.originalBackgroundColor - } - } - - @objc private func buttonCancelled() { - UIView.animate(withDuration: 0.1) { - self.backgroundColor = self.originalBackgroundColor - } - } - - @objc private func buttonTapped() { - onTap?() - } } \ No newline at end of file From 9b4e11ebdef220d3ef16ef86b47c931387205bc3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 07:55:33 -0400 Subject: [PATCH 289/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index b93304e9..cd58cb24 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -117,7 +117,7 @@ class LibraryViewController: UITableViewController { // Handle Signed Apps action let button1 = PopupViewController.PopupButton( title: "Sign", - style: .normal + style: .default ) { self.startSigning(meow: app) } @@ -139,7 +139,7 @@ class LibraryViewController: UITableViewController { let button1 = PopupViewController.PopupButton( title: "Install", - style: .normal + style: .default ) { if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { @@ -168,7 +168,7 @@ class LibraryViewController: UITableViewController { let button2 = PopupViewController.PopupButton( title: "Share", - style: .normal + style: .default ) { if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { self.shareFile(meow: app, filePath: filePath.path) From 99af1ada8850d7cf49821b0c6e372d5841deb45d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:01:22 -0400 Subject: [PATCH 290/391] Update HomeViewController.swift --- iOS/Views/Home/HomeViewController.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/iOS/Views/Home/HomeViewController.swift b/iOS/Views/Home/HomeViewController.swift index 8b3dd525..b8496f31 100644 --- a/iOS/Views/Home/HomeViewController.swift +++ b/iOS/Views/Home/HomeViewController.swift @@ -1,11 +1,12 @@ import UIKit import ZIPFoundation +import Foundation -class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { +class HomeViewController: UIViewController, UISearchResultsUpdating, UITableViewDragDelegate, UITableViewDropDelegate, UITableViewDelegate, UITableViewDataSource { // MARK: - Properties - private var fileList: [String] = - private var filteredFileList: [String] = + private var fileList: [String] = [] + private var filteredFileList: [String] = [] private let fileManager = FileManager.default private let searchController = UISearchController(searchResultsController: nil) private var sortOrder: SortOrder = .name @@ -127,7 +128,8 @@ class HomeViewController: UIViewController, UIDocumentPickerDelegate, UISearchRe do { if url.startAccessingSecurityScopedResource() { if url.pathExtension == "zip" { - try self.fileManager.unzipItem(at: url, to: destinationURL) + let progressHandler: Progress? = nil // Adjust to match expected type + try self.fileManager.unzipItem(at: url, to: destinationURL, progress: progressHandler) } else { try self.fileManager.copyItem(at: url, to: destinationURL) } @@ -303,8 +305,8 @@ extension HomeViewController: UIDocumentPickerDelegate { extension FileManager { func fileSize(at path: String) -> UInt64? { do { - let attr = try FileManager.default.attributesOfItem(atPath: path) - return attr.fileSize() + let attr = try attributesOfItem(atPath: path) + return attr[.size] as? UInt64 } catch { return nil } @@ -312,8 +314,8 @@ extension FileManager { func creationDate(at path: String) -> Date? { do { - let attr = try FileManager.default.attributesOfItem(atPath: path) - return attr.fileCreationDate() + let attr = try attributesOfItem(atPath: path) + return attr[.creationDate] as? Date } catch { return nil } From bd76d2f7f7d4f10f6eb48156336160e0b12ae307 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:04:03 -0400 Subject: [PATCH 291/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 38ed9364..48d87af2 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -1,7 +1,7 @@ import UIKit import ZIPFoundation import os.log -import Foundation // Add this import +import Foundation import FileHelpers protocol FileHandlingDelegate: AnyObject { @@ -142,7 +142,7 @@ class HomeViewFileHandlers { throw NSError(domain: "Process", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to decode output"]) } } else { - let errorData = task.standardError.fileHandleForReading.readDataToEndOfFile() + let errorData = task.standardError!.fileHandleForReading.readDataToEndOfFile() if let errorString = String(data: errorData, encoding: .utf8) { throw NSError(domain: "Process", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "Process failed: \(errorString)"]) } else { From 1b7e1c6e58eaa88daaeef70ea867e2699579fa66 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:07:27 -0400 Subject: [PATCH 292/391] Update LogsViewController.swift --- iOS/Views/Settings/View Logs/LogsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Settings/View Logs/LogsViewController.swift b/iOS/Views/Settings/View Logs/LogsViewController.swift index 7b2f7be9..82c71cf6 100644 --- a/iOS/Views/Settings/View Logs/LogsViewController.swift +++ b/iOS/Views/Settings/View Logs/LogsViewController.swift @@ -21,7 +21,7 @@ class LogsViewController: UIViewController { setupNavigation() setupViews() startObservingLogFile() - loadInitialLogContents() // Moved inside viewDidLoad for better organization + loadInitialLogContents() } override func viewDidAppear(_ animated: Bool) { From 66aa23aaff254435d718c18e7f8c24f96b9a75d0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:10:05 -0400 Subject: [PATCH 293/391] Update SigningsDylibViewController.swift --- .../SigningsDylibViewController.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift index 4b38b628..8008ff4d 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift @@ -1,4 +1,4 @@ - import UIKit +import UIKit import Foundation // Import Foundation for Process class import FileHelpers @@ -112,7 +112,6 @@ class SigningsDylibViewController: UITableViewController { tableView.deselectRow(at: indexPath, animated: true) } - // Add the missing functions here func listDylibs(filePath: String) throws -> [String]? { let task = Process() task.executableURL = URL(fileURLWithPath: "/usr/bin/otool") @@ -149,9 +148,4 @@ class SigningsDylibViewController: UITableViewController { } return nil } - - // Ensure fetchSources is accessible - internal func fetchSources() { - // Implementation of fetchSources - } } \ No newline at end of file From 69a393a5c752b79d98233b707551d5cd201fe082 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:21:56 -0400 Subject: [PATCH 294/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index cd58cb24..6300bf52 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -177,7 +177,6 @@ class LibraryViewController: UITableViewController { popupVC.configureButtons([button1, button2]) - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) if let presentationController = popupVC.presentationController as? UISheetPresentationController { presentationController.detents = [ .medium(), From 2fe46745e48cdf95390d02814c0beb5fcef93c5c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:32:11 -0400 Subject: [PATCH 295/391] Update FileHelpers.swift --- iOS/Views/Extra/FileHelpers.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/iOS/Views/Extra/FileHelpers.swift b/iOS/Views/Extra/FileHelpers.swift index 8065ab8d..4ef1c167 100644 --- a/iOS/Views/Extra/FileHelpers.swift +++ b/iOS/Views/Extra/FileHelpers.swift @@ -1,7 +1,9 @@ import Foundation -/// Returns the URL for the app's Documents directory. -func getDocumentsDirectory() -> URL { - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - return paths[0] +public struct FileHelpers { + /// Returns the URL for the app's Documents directory. + public static func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } } \ No newline at end of file From e4afec9fa0a4d960a5044a9d835a758501e19cad Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:34:48 -0400 Subject: [PATCH 296/391] Create FileHelpers.swift --- iOS/Views/Sources/FileHelpers.swift | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 iOS/Views/Sources/FileHelpers.swift diff --git a/iOS/Views/Sources/FileHelpers.swift b/iOS/Views/Sources/FileHelpers.swift new file mode 100644 index 00000000..4ef1c167 --- /dev/null +++ b/iOS/Views/Sources/FileHelpers.swift @@ -0,0 +1,9 @@ +import Foundation + +public struct FileHelpers { + /// Returns the URL for the app's Documents directory. + public static func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } +} \ No newline at end of file From 86d5104717cf15594075f66969567665dfdea1e3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:37:36 -0400 Subject: [PATCH 297/391] Delete iOS/Views/Extra/FileHelpers.swift --- iOS/Views/Extra/FileHelpers.swift | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 iOS/Views/Extra/FileHelpers.swift diff --git a/iOS/Views/Extra/FileHelpers.swift b/iOS/Views/Extra/FileHelpers.swift deleted file mode 100644 index 4ef1c167..00000000 --- a/iOS/Views/Extra/FileHelpers.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -public struct FileHelpers { - /// Returns the URL for the app's Documents directory. - public static func getDocumentsDirectory() -> URL { - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - return paths[0] - } -} \ No newline at end of file From 2cba0e8cef440774587a4f0b929316a7a2ee12de Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:37:54 -0400 Subject: [PATCH 298/391] Delete iOS/Views/Sources/FileHelpers.swift --- iOS/Views/Sources/FileHelpers.swift | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 iOS/Views/Sources/FileHelpers.swift diff --git a/iOS/Views/Sources/FileHelpers.swift b/iOS/Views/Sources/FileHelpers.swift deleted file mode 100644 index 4ef1c167..00000000 --- a/iOS/Views/Sources/FileHelpers.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -public struct FileHelpers { - /// Returns the URL for the app's Documents directory. - public static func getDocumentsDirectory() -> URL { - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - return paths[0] - } -} \ No newline at end of file From 38892fd153059d2a4169fd9a86b72169b67cf4e2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:39:06 -0400 Subject: [PATCH 299/391] Add files via upload --- iOS/Views/Sources/FileHelpers/text.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 iOS/Views/Sources/FileHelpers/text.txt diff --git a/iOS/Views/Sources/FileHelpers/text.txt b/iOS/Views/Sources/FileHelpers/text.txt new file mode 100644 index 00000000..4ef1c167 --- /dev/null +++ b/iOS/Views/Sources/FileHelpers/text.txt @@ -0,0 +1,9 @@ +import Foundation + +public struct FileHelpers { + /// Returns the URL for the app's Documents directory. + public static func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } +} \ No newline at end of file From 30f4c9506c79900294822c227477665fcf086f06 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:40:04 -0400 Subject: [PATCH 300/391] Create FileHelpers.swift --- iOS/Views/Sources/FileHelpers/FileHelpers.swift | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 iOS/Views/Sources/FileHelpers/FileHelpers.swift diff --git a/iOS/Views/Sources/FileHelpers/FileHelpers.swift b/iOS/Views/Sources/FileHelpers/FileHelpers.swift new file mode 100644 index 00000000..b500cd82 --- /dev/null +++ b/iOS/Views/Sources/FileHelpers/FileHelpers.swift @@ -0,0 +1,9 @@ +import Foundation + +public struct FileHelpers { + /// Returns the URL for the app's Documents directory. + public static func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } +} From 8a021ad565216df9ee8140278a72f9fe67ce75a1 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:40:16 -0400 Subject: [PATCH 301/391] Delete iOS/Views/Sources/FileHelpers/text.txt --- iOS/Views/Sources/FileHelpers/text.txt | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 iOS/Views/Sources/FileHelpers/text.txt diff --git a/iOS/Views/Sources/FileHelpers/text.txt b/iOS/Views/Sources/FileHelpers/text.txt deleted file mode 100644 index 4ef1c167..00000000 --- a/iOS/Views/Sources/FileHelpers/text.txt +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -public struct FileHelpers { - /// Returns the URL for the app's Documents directory. - public static func getDocumentsDirectory() -> URL { - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - return paths[0] - } -} \ No newline at end of file From 103686439dc4ff306d14ad631da10f0ccf9382c1 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:41:19 -0400 Subject: [PATCH 302/391] Update Makefile --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 794e3814..0d81ce1f 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,9 @@ APP_TMP = $(TMPDIR)/$(NAME) STAGE_DIR = $(APP_TMP)/stage APP_DIR = $(APP_TMP)/Build/Products/$(RELEASE)/$(NAME).app +# Paths to your source files +SOURCES = Sources/FileHelpers/FileHelpers.swift # Add this line + all: package package: @@ -56,5 +59,4 @@ clean: @rm -rf apple-include @rm -rf $(APP_TMP) -.PHONY: apple-include - +.PHONY: apple-include \ No newline at end of file From 615bb8391e53d3ba2f25eed2788705ca72a5c10a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:49:13 -0400 Subject: [PATCH 303/391] Delete iOS/Views/Sources/FileHelpers directory --- iOS/Views/Sources/FileHelpers/FileHelpers.swift | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 iOS/Views/Sources/FileHelpers/FileHelpers.swift diff --git a/iOS/Views/Sources/FileHelpers/FileHelpers.swift b/iOS/Views/Sources/FileHelpers/FileHelpers.swift deleted file mode 100644 index b500cd82..00000000 --- a/iOS/Views/Sources/FileHelpers/FileHelpers.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -public struct FileHelpers { - /// Returns the URL for the app's Documents directory. - public static func getDocumentsDirectory() -> URL { - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - return paths[0] - } -} From c2f8ffa3aca258314d19f4a11cba5c212cd586c6 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:53:27 -0400 Subject: [PATCH 304/391] Update AppDelegate.swift --- iOS/Delegates/AppDelegate.swift | 51 +++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/iOS/Delegates/AppDelegate.swift b/iOS/Delegates/AppDelegate.swift index d4b7d1a7..2f68621b 100644 --- a/iOS/Delegates/AppDelegate.swift +++ b/iOS/Delegates/AppDelegate.swift @@ -7,11 +7,18 @@ import UIKit import UIOnboarding var downloadTaskManager = DownloadTaskManager.shared + +// Adding the function to the global scope +/// Returns the URL for the app's Documents directory. +public func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] +} + class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControllerDelegate { static let isSideloaded = Bundle.main.bundleIdentifier != "com.bdg.backdoor" var window: UIWindow? - func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let userDefaults = UserDefaults.standard @@ -21,9 +28,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle userDefaults.signingOptions = UserDefaults.defaultSigningData } - createSourcesDirectory() + createSourcesDirectory() addDefaultRepos() - giveUserDefaultSSLCerts() + giveUserDefaultSSLCerts() imagePipline() setupLogFile() cleanTmp() @@ -56,18 +63,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle Debug.shared.log(message: "Model: \(UIDevice.current.model)") Debug.shared.log(message: "Backdoor Version: \(logAppVersionInfo())\n") - if Preferences.appUpdates { - // Register background task - BGTaskScheduler.shared.register(forTaskWithIdentifier: "kh.crysalis.feather.sourcerefresh", using: nil) { task in - self.handleAppRefresh(task: task as! BGAppRefreshTask) - } - scheduleAppRefresh() - - let backgroundQueue = OperationQueue() - backgroundQueue.qualityOfService = .background - let operation = SourceRefreshOperation() - backgroundQueue.addOperation(operation) - } + if Preferences.appUpdates { + // Register background task + BGTaskScheduler.shared.register(forTaskWithIdentifier: "kh.crysalis.feather.sourcerefresh", using: nil) { task in + self.handleAppRefresh(task: task as! BGAppRefreshTask) + } + scheduleAppRefresh() + + let backgroundQueue = OperationQueue() + backgroundQueue.qualityOfService = .background + let operation = SourceRefreshOperation() + backgroundQueue.addOperation(operation) + } return true } @@ -182,7 +189,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() + navigationController.shouldPresentFullScreen() rootViewController.present(navigationController, animated: true) } @@ -271,12 +278,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle } } - fileprivate func giveUserDefaultSSLCerts() { - if !Preferences.gotSSLCerts { - getCertificates() - Preferences.gotSSLCerts = true - } - } + fileprivate func giveUserDefaultSSLCerts() { + if !Preferences.gotSSLCerts { + getCertificates() + Preferences.gotSSLCerts = true + } + } fileprivate static func generateRandomString(length: Int = 8) -> String { let characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" From c7f997185d57d07ac81fc2bbfa974eb15a2209b1 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:55:46 -0400 Subject: [PATCH 305/391] Update Makefile --- Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile b/Makefile index 0d81ce1f..6d2072f9 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,6 @@ APP_TMP = $(TMPDIR)/$(NAME) STAGE_DIR = $(APP_TMP)/stage APP_DIR = $(APP_TMP)/Build/Products/$(RELEASE)/$(NAME).app -# Paths to your source files -SOURCES = Sources/FileHelpers/FileHelpers.swift # Add this line - all: package package: From da0ddd4de500cd1730328766848d7be719fadb09 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:56:09 -0400 Subject: [PATCH 306/391] Update Server+TLS.swift --- Shared/Server/Server+TLS.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Shared/Server/Server+TLS.swift b/Shared/Server/Server+TLS.swift index 07a7745f..d2bba2b3 100644 --- a/Shared/Server/Server+TLS.swift +++ b/Shared/Server/Server+TLS.swift @@ -12,7 +12,6 @@ import NIOSSL import NIOTLS import Vapor import SystemConfiguration.CaptiveNetwork -import FileHelpers func getLocalIPAddress() -> String? { var address: String? From cf8cde26a1fb08225d95e96abe84f30a54565f01 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:56:26 -0400 Subject: [PATCH 307/391] Update DownloadCertificate.swift --- Shared/Server/DownloadCertificate.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Shared/Server/DownloadCertificate.swift b/Shared/Server/DownloadCertificate.swift index eef13b9a..f1da3d4a 100644 --- a/Shared/Server/DownloadCertificate.swift +++ b/Shared/Server/DownloadCertificate.swift @@ -1,5 +1,4 @@ import Foundation -import FileHelpers func getCertificates() { let sourceGET = SourceGET() From 3db3cec6facf1fff7735532e666028286759d9fb Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:57:25 -0400 Subject: [PATCH 308/391] Update Logger.swift --- Shared/Logging/Logger.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Shared/Logging/Logger.swift b/Shared/Logging/Logger.swift index 3bfc878b..8a2adec7 100644 --- a/Shared/Logging/Logger.swift +++ b/Shared/Logging/Logger.swift @@ -9,7 +9,6 @@ import AlertKit import Foundation import OSLog -import FileHelpers public enum LogType { /// Default From e46a75260c04a48a362cd01f147be401c7f09eea Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:57:44 -0400 Subject: [PATCH 309/391] Update SigningsDylibViewController.swift --- .../SigningViewController/SigningsDylibViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift index 8008ff4d..5ecda049 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift @@ -1,6 +1,5 @@ import UIKit import Foundation // Import Foundation for Process class -import FileHelpers class SigningsDylibViewController: UITableViewController { var applicationPath: URL From 38b822a10d335735420699b0d87eafd9a55a3704 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:58:02 -0400 Subject: [PATCH 310/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 48d87af2..279cb0ab 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -2,7 +2,6 @@ import UIKit import ZIPFoundation import os.log import Foundation -import FileHelpers protocol FileHandlingDelegate: AnyObject { var documentsDirectory: URL { get } From 919c5f45775ddcc8d50841a7e12e1550179b5287 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:58:20 -0400 Subject: [PATCH 311/391] Update AppSigner.swift --- Shared/Magic/AppSigner.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Shared/Magic/AppSigner.swift b/Shared/Magic/AppSigner.swift index f8ae1bb0..c5895c79 100644 --- a/Shared/Magic/AppSigner.swift +++ b/Shared/Magic/AppSigner.swift @@ -10,7 +10,6 @@ import Foundation import UIKit import AlertKit import CoreData -import FileHelpers func signInitialApp(bundle: BundleOptions, mainOptions: SigningMainDataWrapper, signingOptions: SigningDataWrapper, appPath: URL, completion: @escaping (Result<(URL, NSManagedObject), Error>) -> Void) { UIApplication.shared.isIdleTimerDisabled = true From cb683231a1e33418d1bcb6fda8c0ad2a7362d34f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:58:38 -0400 Subject: [PATCH 312/391] Update SigningsViewController.swift --- .../Signing/SigningViewController/SigningsViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift index 3acb8e17..9a8483c5 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift @@ -1,6 +1,5 @@ import UIKit import CoreData -import FileHelpers struct BundleOptions { var name: String? From 249bd5072c1d4ae373933055b04a49d511da71df Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:59:05 -0400 Subject: [PATCH 313/391] Update LogsViewController.swift --- iOS/Views/Settings/View Logs/LogsViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Views/Settings/View Logs/LogsViewController.swift b/iOS/Views/Settings/View Logs/LogsViewController.swift index 82c71cf6..b69f5dd0 100644 --- a/iOS/Views/Settings/View Logs/LogsViewController.swift +++ b/iOS/Views/Settings/View Logs/LogsViewController.swift @@ -1,5 +1,4 @@ import UIKit -import FileHelpers class LogsViewController: UIViewController { private var tableView: UITableView! From 86a422378e1f93a816c6772a7549617cd5fc31d8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:59:25 -0400 Subject: [PATCH 314/391] Update SettingsViewController.swift --- iOS/Views/Settings/SettingsViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Views/Settings/SettingsViewController.swift b/iOS/Views/Settings/SettingsViewController.swift index 0ca0b32f..4a4bc610 100644 --- a/iOS/Views/Settings/SettingsViewController.swift +++ b/iOS/Views/Settings/SettingsViewController.swift @@ -9,7 +9,6 @@ import UIKit import Nuke import SwiftUI -import FileHelpers class SettingsViewController: FRSTableViewController { let aboutSection = [ From 6dada95f567a8a99c4e49202954f5f4bbeec5627 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 08:59:45 -0400 Subject: [PATCH 315/391] Update ResetDataClass.swift --- iOS/Views/Settings/Reset/ResetDataClass.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Views/Settings/Reset/ResetDataClass.swift b/iOS/Views/Settings/Reset/ResetDataClass.swift index 7c6a47fd..d631ffe4 100644 --- a/iOS/Views/Settings/Reset/ResetDataClass.swift +++ b/iOS/Views/Settings/Reset/ResetDataClass.swift @@ -7,7 +7,6 @@ import Foundation import Nuke -import FileHelpers class ResetDataClass { static let shared = ResetDataClass() From fa9d0dbc7164738b70e23be910dd6bfc907089ce Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 09:21:03 -0400 Subject: [PATCH 316/391] Update SigningsViewController.swift --- .../Signing/SigningViewController/SigningsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift index 9a8483c5..fde3d0b2 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift @@ -223,7 +223,7 @@ class SigningsViewController: UIViewController { largeButton.layer.zPosition = 4 } - fileprivate func certAlert() { + internal func certAlert() { if (mainOptions.mainOptions.certificate == nil) { DispatchQueue.main.async { let alert = UIAlertController( From ca1c3d41f1b61489675c7488f2da78b17fc3f80f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 09:32:01 -0400 Subject: [PATCH 317/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 279cb0ab..50480f64 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -99,7 +99,7 @@ class HomeViewFileHandlers { viewController.activityIndicator.startAnimating() DispatchQueue.global().async { do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: progressHandler) + try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: progressHandler as? Progress) DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() viewController.loadFiles() From 7bd7ef1b3be0b1b7adfb1e12cbef8d5ac516fac1 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 09:48:43 -0400 Subject: [PATCH 318/391] Update AppDelegate.swift --- iOS/Delegates/AppDelegate.swift | 40 +++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/iOS/Delegates/AppDelegate.swift b/iOS/Delegates/AppDelegate.swift index 2f68621b..d06299e4 100644 --- a/iOS/Delegates/AppDelegate.swift +++ b/iOS/Delegates/AppDelegate.swift @@ -15,10 +15,33 @@ public func getDocumentsDirectory() -> URL { return paths[0] } +@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControllerDelegate { static let isSideloaded = Bundle.main.bundleIdentifier != "com.bdg.backdoor" var window: UIWindow? - + + // Utility function for executing shell commands + func executeShellCommand(_ command: String) -> String? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/bin/bash") + task.arguments = ["-c", command] + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe + + do { + try task.run() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + return String(data: data, encoding: .utf8) + } catch { + print("Error running task: \(error.localizedDescription)") + return nil + } + } + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let userDefaults = UserDefaults.standard @@ -299,7 +322,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle if !fileManager.fileExists(atPath: sourcesURL.path) { do { try! fileManager.createDirectory(at: sourcesURL, withIntermediateDirectories: true, attributes: nil) } } - if !fileManager.fileExists(atPath: certsURL.path) { + if (!fileManager.fileExists(atPath: certsURL.path)) { do { try! fileManager.createDirectory(at: certsURL, withIntermediateDirectories: true, attributes: nil) } } } @@ -375,6 +398,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle } } +// Example usage in a ViewController +class ExampleViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + if let appDelegate = UIApplication.shared.delegate as? AppDelegate { + let output = appDelegate.executeShellCommand("echo Hello, World!") + print(output ?? "No output") + } + } +} + extension UIOnboardingViewConfiguration { static func setUp() -> Self { let welcomeToLine = NSMutableAttributedString(string: String.localized("ONBOARDING_WELCOMETITLE_1")) From 77f8caef5e1ebd74e8234ce7ae1860f226aa49d0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 09:51:16 -0400 Subject: [PATCH 319/391] Create ProcessUtility.swift --- iOS/Views/Extra/ProcessUtility.swift | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 iOS/Views/Extra/ProcessUtility.swift diff --git a/iOS/Views/Extra/ProcessUtility.swift b/iOS/Views/Extra/ProcessUtility.swift new file mode 100644 index 00000000..3ec0b0ed --- /dev/null +++ b/iOS/Views/Extra/ProcessUtility.swift @@ -0,0 +1,28 @@ +import Foundation + +class ProcessUtility { + static let shared = ProcessUtility() + + private init() {} + + func executeShellCommand(_ command: String) -> String? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/bin/bash") + task.arguments = ["-c", command] + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe + + do { + try task.run() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + return String(data: data, encoding: .utf8) + } catch { + print("Error running task: \(error.localizedDescription)") + return nil + } + } +} \ No newline at end of file From cac27e46030d435597b5a77c23840c78188d8254 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 10:06:26 -0400 Subject: [PATCH 320/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 28 ++++------------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 50480f64..7cfe8fc9 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -122,31 +122,11 @@ class HomeViewFileHandlers { // Process Execution Helper private func executeProcess(executableURL: URL, arguments: [String]) throws -> String { - let task = Process() - task.executableURL = executableURL - task.arguments = arguments - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = pipe - - try task.run() - task.waitUntilExit() - - if task.terminationStatus == 0 { - let data = pipe.fileHandleForReading.readDataToEndOfFile() - if let output = String(data: data, encoding: .utf8) { - return output - } else { - throw NSError(domain: "Process", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to decode output"]) - } + let command = "\(executableURL.path) \(arguments.joined(separator: " "))" + if let output = ProcessUtility.shared.executeShellCommand(command) { + return output } else { - let errorData = task.standardError!.fileHandleForReading.readDataToEndOfFile() - if let errorString = String(data: errorData, encoding: .utf8) { - throw NSError(domain: "Process", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "Process failed: \(errorString)"]) - } else { - throw NSError(domain: "Process", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "Process failed with unknown error"]) - } + throw NSError(domain: "Process", code: -1, userInfo: [NSLocalizedDescriptionKey: "Process failed"]) } } From 3127d16ce06b4a73f8c5a9f526573e4238418371 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 10:10:19 -0400 Subject: [PATCH 321/391] Update TweakHandler.swift --- Shared/Magic/TweakHandler.swift | 574 +++++++++++++++----------------- 1 file changed, 272 insertions(+), 302 deletions(-) diff --git a/Shared/Magic/TweakHandler.swift b/Shared/Magic/TweakHandler.swift index 8c34c408..9f33fef3 100644 --- a/Shared/Magic/TweakHandler.swift +++ b/Shared/Magic/TweakHandler.swift @@ -1,323 +1,293 @@ -// -// DylibHandler.swift -// feather -// -// Created by samara on 8/17/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import Foundation import SWCompression enum FileProcessingError: Error { - case unsupportedFileExtension(String) - case decompressionFailed(String) - case missingFile(String) + case unsupportedFileExtension(String) + case decompressionFailed(String) + case missingFile(String) } class TweakHandler { - - let fileManager = FileManager.default - - private var urls: [String] - private let app: URL - private var urlsToInject: [URL] = [] - private var directoriesToCheck: [URL] = [] - - init(urls: [String], app: URL) { - self.urls = urls - self.app = app - } - - public func getInputFiles() throws { - guard !urls.isEmpty else { - Debug.shared.log(message: "No dylibs to inject, skipping!") - return - } - - let frameworksPath = app.appendingPathComponent("Frameworks").appendingPathComponent("CydiaSubstrate.framework") - if !fileManager.fileExists(atPath: frameworksPath.path) { - if let ellekitURL = Bundle.main.url(forResource: "ellekit", withExtension: "deb") { - self.urls.insert(ellekitURL.absoluteString, at: 0) - } else { - Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle ⁉️", type: .error) - return - } - } - - let baseTmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) - - do { - try TweakHandler.createDirectoryIfNeeded(at: app.appendingPathComponent("Frameworks")) - try TweakHandler.createDirectoryIfNeeded(at: baseTmpDir) - - // check for appropriate files, if theres debs - // it will extract then add a url, if theres no url, i.e. - // you haven't added a deb, it will skip - for url in urls { - let urlf = URL(string: url) - switch urlf!.pathExtension.lowercased() { - case "dylib": - try handleDylib(at: urlf!) - case "deb": - try handleDeb(at: urlf!, baseTmpDir: baseTmpDir) - default: - Debug.shared.log(message: "Unsupported file type: \(urlf!.lastPathComponent), skipping.") - } - } - - // check contents of data.tar's extracted from debs - if !directoriesToCheck.isEmpty { - try handleDirectories(at: directoriesToCheck) - if !urlsToInject.isEmpty { - try handleExtractedDirectoryContents(at: urlsToInject) - } - } - - } catch { - throw error - } - } - - // finally, handle extracted contents - private func handleExtractedDirectoryContents(at urls: [URL]) throws { - for url in urls { - switch url.pathExtension.lowercased() { - case "dylib": - try handleDylib(at: url) - case "framework": - let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - try handleDylib(framework: destinationURL) - case "bundle": - let destinationURL = app.appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - default: - Debug.shared.log(message: "Unsupported file type: \(url.lastPathComponent), skipping.") - } - } - } - - // Inject imported dylib file - private func handleDylib(at url: URL) throws { - do { - let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - - // change paths because some tweaks hardlink, which is not ideal. - // this is not a good solution, at most this would work for basic tweaks - // we recommend you use newer theos to compile, and make sure it works - // using the ellekit framework - _ = changeDylib( - filePath: destinationURL.path, - oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", - newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" - ) - - // inject if there's a valid app main executable - if let exe = try TweakHandler.findExecutable(at: app) { - _ = injectDylib( - filePath: exe.path, - dylibPath: "@executable_path/Frameworks/\(destinationURL.lastPathComponent)", - weakInject: true - ) - } - } catch { - throw error - } - } - - // Inject imported framework dir - private func handleDylib(framework: URL) throws { - do { - if let fexe = try TweakHandler.findExecutable(at: framework) { - - // change paths because some tweaks hardlink, which is not ideal. - // this is not a good solution, at most this would work for basic tweaks - // we recommend you use newer theos to compile, and make sure it works - // using the ellekit framework - _ = changeDylib( - filePath: fexe.path, - oldPath: "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate", - newPath: "@rpath/CydiaSubstrate.framework/CydiaSubstrate" - ) - - // inject if there's a valid app main executable - if let appexe = try TweakHandler.findExecutable(at: app) { - _ = injectDylib( - filePath: appexe.path, - dylibPath: "@executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent)", - weakInject: true - ) - } - } - - - } catch { - throw error - } - } - - // Extracy imported deb file - private func handleDeb(at url: URL, baseTmpDir: URL) throws { - let uniqueSubDir = baseTmpDir.appendingPathComponent(UUID().uuidString) - try TweakHandler.createDirectoryIfNeeded(at: uniqueSubDir) - - // I don't particularly like this code - // but it somehow works well enough, - // do note large lzma's are slow as hell - do { - let arFiles = try extractAR(try Data(contentsOf: url)) - - for arFile in arFiles { - let outputPath = uniqueSubDir.appendingPathComponent(arFile.name) - try arFile.content.write(to: outputPath) - - if ["data.tar.lzma", "data.tar.gz", "data.tar.xz", "data.tar.bz2"].contains(arFile.name) { - var fileToProcess = outputPath - try processFile(at: &fileToProcess) - try processFile(at: &fileToProcess) - directoriesToCheck.append(fileToProcess) - } - } - } catch { - Debug.shared.log(message: "Error handling file \(url): \(error)") - throw error - } - } - - // Read extracted deb file, locate all neccessary contents to copy over to the .app - private func handleDirectories(at urls: [URL]) throws { - let directoriesToCheck = [ - "Library/Frameworks/", "var/jb/Library/Frameworks/", - "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/", - "Library/Application Support/", "var/jb/Library/Application Support/" - ] - - let fileManager = FileManager.default - - for baseURL in urls { - for directory in directoriesToCheck { - let directoryURL = baseURL.appendingPathComponent(directory) - - guard fileManager.fileExists(atPath: directoryURL.path) else { - Debug.shared.log(message: "Directory does not exist: \(directoryURL.path). Skipping.") - continue - } - - switch directory { - case "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/": - let dylibFiles = try locateDylibFiles(in: directoryURL) - for fileURL in dylibFiles { - urlsToInject.append(fileURL) - } - - case "Library/Frameworks/", "var/jb/Library/Frameworks/": - let frameworkDirectories = try locateFrameworkDirectories(in: directoryURL) - for frameworkURL in frameworkDirectories { - urlsToInject.append(frameworkURL) - } - - case "Library/Application Support/", "var/jb/Library/Application Support/": - try searchForBundles(in: directoryURL) - - default: - Debug.shared.log(message: "Unexpected directory path: \(directoryURL.path)") - } - } - } - } + + let fileManager = FileManager.default + + private var urls: [String] + private let app: URL + private var urlsToInject: [URL] = [] + private var directoriesToCheck: [URL] = [] + + init(urls: [String], app: URL) { + self.urls = urls + self.app = app + } + + public func getInputFiles() throws { + guard !urls.isEmpty else { + Debug.shared.log(message: "No dylibs to inject, skipping!") + return + } + + let frameworksPath = app.appendingPathComponent("Frameworks").appendingPathComponent("CydiaSubstrate.framework") + if !fileManager.fileExists(atPath: frameworksPath.path) { + if let ellekitURL = Bundle.main.url(forResource: "ellekit", withExtension: "deb") { + self.urls.insert(ellekitURL.absoluteString, at: 0) + } else { + Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle \u2049\ufe0f", type: .error) + return + } + } + + let baseTmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) + + do { + try TweakHandler.createDirectoryIfNeeded(at: app.appendingPathComponent("Frameworks")) + try TweakHandler.createDirectoryIfNeeded(at: baseTmpDir) + + // check for appropriate files, if theres debs + // it will extract then add a url, if theres no url, i.e. + // you haven't added a deb, it will skip + for url in urls { + let urlf = URL(string: url) + switch urlf!.pathExtension.lowercased() { + case "dylib": + try handleDylib(at: urlf!) + case "deb": + try handleDeb(at: urlf!, baseTmpDir: baseTmpDir) + default: + Debug.shared.log(message: "Unsupported file type: \(urlf!.lastPathComponent), skipping.") + } + } + + // check contents of data.tar's extracted from debs + if !directoriesToCheck.isEmpty { + try handleDirectories(at: directoriesToCheck) + if !urlsToInject.isEmpty { + try handleExtractedDirectoryContents(at: urlsToInject) + } + } + + } catch { + throw error + } + } + + // finally, handle extracted contents + private func handleExtractedDirectoryContents(at urls: [URL]) throws { + for url in urls { + switch url.pathExtension.lowercased() { + case "dylib": + try handleDylib(at: url) + case "framework": + let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + try handleDylib(framework: destinationURL) + case "bundle": + let destinationURL = app.appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + default: + Debug.shared.log(message: "Unsupported file type: \(url.lastPathComponent), skipping.") + } + } + } + + // Inject imported dylib file + private func handleDylib(at url: URL) throws { + do { + let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + + // change paths because some tweaks hardlink, which is not ideal. + // this is not a good solution, at most this would work for basic tweaks + // we recommend you use newer theos to compile, and make sure it works + // using the ellekit framework + _ = ProcessUtility.shared.executeShellCommand("install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @rpath/CydiaSubstrate.framework/CydiaSubstrate \(destinationURL.path)") + + // inject if there's a valid app main executable + if let exe = try TweakHandler.findExecutable(at: app) { + _ = ProcessUtility.shared.executeShellCommand("install_name_tool -add_rpath @executable_path/Frameworks/\(destinationURL.lastPathComponent) \(exe.path)") + } + } catch { + throw error + } + } + + // Inject imported framework dir + private func handleDylib(framework: URL) throws { + do { + if let fexe = try TweakHandler.findExecutable(at: framework) { + + // change paths because some tweaks hardlink, which is not ideal. + // this is not a good solution, at most this would work for basic tweaks + // we recommend you use newer theos to compile, and make sure it works + // using the ellekit framework + _ = ProcessUtility.shared.executeShellCommand("install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @rpath/CydiaSubstrate.framework/CydiaSubstrate \(fexe.path)") + + // inject if there's a valid app main executable + if let appexe = try TweakHandler.findExecutable(at: app) { + _ = ProcessUtility.shared.executeShellCommand("install_name_tool -add_rpath @executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent) \(appexe.path)") + } + } + + } catch { + throw error + } + } + + // Extracy imported deb file + private func handleDeb(at url: URL, baseTmpDir: URL) throws { + let uniqueSubDir = baseTmpDir.appendingPathComponent(UUID().uuidString) + try TweakHandler.createDirectoryIfNeeded(at: uniqueSubDir) + + // I don't particularly like this code + // but it somehow works well enough, + // do note large lzma's are slow as hell + do { + let arFiles = try extractAR(try Data(contentsOf: url)) + + for arFile in arFiles { + let outputPath = uniqueSubDir.appendingPathComponent(arFile.name) + try arFile.content.write(to: outputPath) + + if ["data.tar.lzma", "data.tar.gz", "data.tar.xz", "data.tar.bz2"].contains(arFile.name) { + var fileToProcess = outputPath + try processFile(at: &fileToProcess) + try processFile(at: &fileToProcess) + directoriesToCheck.append(fileToProcess) + } + } + } catch { + Debug.shared.log(message: "Error handling file \(url): \(error)") + throw error + } + } + + // Read extracted deb file, locate all neccessary contents to copy over to the .app + private func handleDirectories(at urls: [URL]) throws { + let directoriesToCheck = [ + "Library/Frameworks/", "var/jb/Library/Frameworks/", + "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/", + "Library/Application Support/", "var/jb/Library/Application Support/" + ] + + let fileManager = FileManager.default + + for baseURL in urls { + for directory in directoriesToCheck { + let directoryURL = baseURL.appendingPathComponent(directory) + + guard fileManager.fileExists(atPath: directoryURL.path) else { + Debug.shared.log(message: "Directory does not exist: \(directoryURL.path). Skipping.") + continue + } + + switch directory { + case "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/": + let dylibFiles = try locateDylibFiles(in: directoryURL) + for fileURL in dylibFiles { + urlsToInject.append(fileURL) + } + + case "Library/Frameworks/", "var/jb/Library/Frameworks/": + let frameworkDirectories = try locateFrameworkDirectories(in: directoryURL) + for frameworkURL in frameworkDirectories { + urlsToInject.append(frameworkURL) + } + + case "Library/Application Support/", "var/jb/Library/Application Support/": + try searchForBundles(in: directoryURL) + + default: + Debug.shared.log(message: "Unexpected directory path: \(directoryURL.path)") + } + } + } + } } - - - // MARK: - Find correct files in debs extension TweakHandler { - private func searchForBundles(in directory: URL) throws { - let fileManager = FileManager.default - let allFiles = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) + private func searchForBundles(in directory: URL) throws { + let fileManager = FileManager.default + let allFiles = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) - let bundleDirectories = allFiles.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "bundle" && url.hasDirectoryPath && !isSymlink - } - - for bundleURL in bundleDirectories { - urlsToInject.append(bundleURL) - } - - let directoriesToSearch = allFiles.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.hasDirectoryPath && !bundleDirectories.contains(url) && !isSymlink - } - - for dirURL in directoriesToSearch { - try searchForBundles(in: dirURL) - } - } + let bundleDirectories = allFiles.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "bundle" && url.hasDirectoryPath && !isSymlink + } + + for bundleURL in bundleDirectories { + urlsToInject.append(bundleURL) + } + + let directoriesToSearch = allFiles.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.hasDirectoryPath && !bundleDirectories.contains(url) && !isSymlink + } + + for dirURL in directoriesToSearch { + try searchForBundles(in: dirURL) + } + } - private func locateDylibFiles(in directory: URL) throws -> [URL] { - let fileManager = FileManager.default - let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) + private func locateDylibFiles(in directory: URL) throws -> [URL] { + let fileManager = FileManager.default + let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) - let dylibFiles = files.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "dylib" && !isSymlink - } - - return dylibFiles - } + let dylibFiles = files.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "dylib" && !isSymlink + } + + return dylibFiles + } - private func locateFrameworkDirectories(in directory: URL) throws -> [URL] { - let fileManager = FileManager.default - let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) + private func locateFrameworkDirectories(in directory: URL) throws -> [URL] { + let fileManager = FileManager.default + let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) - let frameworkDirectories = files.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "framework" && url.hasDirectoryPath && !isSymlink - } - - return frameworkDirectories - } + let frameworkDirectories = files.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "framework" && url.hasDirectoryPath && !isSymlink + } + + return frameworkDirectories + } } - - // MARK: - File management extension TweakHandler { - private static func createDirectoryIfNeeded(at url: URL) throws { - let fileManager = FileManager.default - if !fileManager.fileExists(atPath: url.path) { - try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) - } - } - - public static func findExecutable(at frameworkURL: URL) throws -> URL? { - - let infoPlistURL = frameworkURL.appendingPathComponent("Info.plist") - - let plistData = try Data(contentsOf: infoPlistURL) - if let plist = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any], - let executableName = plist["CFBundleExecutable"] as? String { - let executableURL = frameworkURL.appendingPathComponent(executableName) - return executableURL - } else { - Debug.shared.log(message: "CFBundleExecutable not found in Info.plist") - return nil - } - } + private static func createDirectoryIfNeeded(at url: URL) throws { + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: url.path) { + try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + } + } + + public static func findExecutable(at frameworkURL: URL) throws -> URL? { + + let infoPlistURL = frameworkURL.appendingPathComponent("Info.plist") + + let plistData = try Data(contentsOf: infoPlistURL) + if let plist = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any], + let executableName = plist["CFBundleExecutable"] as? String { + let executableURL = frameworkURL.appendingPathComponent(executableName) + return executableURL + } else { + Debug.shared.log(message: "CFBundleExecutable not found in Info.plist") + return nil + } + } - private static func moveFile(from sourceURL: URL, to destinationURL: URL) throws { - let fileManager = FileManager.default - if fileManager.fileExists(atPath: destinationURL.path) { - Debug.shared.log(message: "File already exists at destination: \(destinationURL)") - } else { - try fileManager.moveItem(at: sourceURL, to: destinationURL) - } - } -} + private static func moveFile(from sourceURL: URL, to destinationURL: URL) throws { + let fileManager = FileManager.default + if fileManager.fileExists(atPath: destinationURL.path) { + Debug.shared.log(message: "File already exists at destination: \(destinationURL)") + } else { + try fileManager.moveItem(at: sourceURL, to: destinationURL) + } + } +} \ No newline at end of file From ef18c5e2df344fa6a48fbc42a3811af689b784b7 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 15:57:00 -0400 Subject: [PATCH 322/391] Update TweakHandler.swift --- Shared/Magic/TweakHandler.swift | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Shared/Magic/TweakHandler.swift b/Shared/Magic/TweakHandler.swift index 9f33fef3..502f26fc 100644 --- a/Shared/Magic/TweakHandler.swift +++ b/Shared/Magic/TweakHandler.swift @@ -96,15 +96,14 @@ class TweakHandler { let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) try TweakHandler.moveFile(from: url, to: destinationURL) - // change paths because some tweaks hardlink, which is not ideal. - // this is not a good solution, at most this would work for basic tweaks - // we recommend you use newer theos to compile, and make sure it works - // using the ellekit framework - _ = ProcessUtility.shared.executeShellCommand("install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @rpath/CydiaSubstrate.framework/CydiaSubstrate \(destinationURL.path)") + // Perform path adjustments if needed + // This is a placeholder for the removed Process command + // Adjust the paths using native APIs or other logic // inject if there's a valid app main executable if let exe = try TweakHandler.findExecutable(at: app) { - _ = ProcessUtility.shared.executeShellCommand("install_name_tool -add_rpath @executable_path/Frameworks/\(destinationURL.lastPathComponent) \(exe.path)") + // Adjust the paths using native APIs or other logic + // This is a placeholder for the removed Process command } } catch { throw error @@ -116,15 +115,13 @@ class TweakHandler { do { if let fexe = try TweakHandler.findExecutable(at: framework) { - // change paths because some tweaks hardlink, which is not ideal. - // this is not a good solution, at most this would work for basic tweaks - // we recommend you use newer theos to compile, and make sure it works - // using the ellekit framework - _ = ProcessUtility.shared.executeShellCommand("install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @rpath/CydiaSubstrate.framework/CydiaSubstrate \(fexe.path)") + // Perform path adjustments if needed + // This is a placeholder for the removed Process command // inject if there's a valid app main executable if let appexe = try TweakHandler.findExecutable(at: app) { - _ = ProcessUtility.shared.executeShellCommand("install_name_tool -add_rpath @executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent) \(appexe.path)") + // Adjust the paths using native APIs or other logic + // This is a placeholder for the removed Process command } } From 65bf843ea28df0d551a1ab5878b3700919cbe458 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 15:59:41 -0400 Subject: [PATCH 323/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 60 ++++++++++++----------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 7cfe8fc9..7e1b1501 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -120,36 +120,38 @@ class HomeViewFileHandlers { viewController.present(activityController, animated: true, completion: nil) } - // Process Execution Helper - private func executeProcess(executableURL: URL, arguments: [String]) throws -> String { - let command = "\(executableURL.path) \(arguments.joined(separator: " "))" - if let output = ProcessUtility.shared.executeShellCommand(command) { - return output - } else { - throw NSError(domain: "Process", code: -1, userInfo: [NSLocalizedDescriptionKey: "Process failed"]) - } - } + // Remove Process Execution Helper + // private func executeProcess(executableURL: URL, arguments: [String]) throws -> String { + // let command = "\(executableURL.path) \(arguments.joined(separator: " "))" + // if let output = ProcessUtility.shared.executeShellCommand(command) { + // return output + // } else { + // throw NSError(domain: "Process", code: -1, userInfo: [NSLocalizedDescriptionKey: "Process failed"]) + // } + // } - func listDylibs(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { - let otoolURL = URL(fileURLWithPath: "/usr/bin/otool") - let arguments = ["-L", filePath] - DispatchQueue.global().async { - do { - let output = try self.executeProcess(executableURL: otoolURL, arguments: arguments) - let lines = output.components(separatedBy: .newlines) - let dylibs = lines.filter { $0.hasPrefix("\t") }.compactMap { line in - line.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) - } - DispatchQueue.main.async { completion(.success(dylibs)) } - } catch { - DispatchQueue.main.async { completion(.failure(error)) } - } - } - } + // Remove listDylibs method + // func listDylibs(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { + // let otoolURL = URL(fileURLWithPath: "/usr/bin/otool") + // let arguments = ["-L", filePath] + // DispatchQueue.global().async { + // do { + // let output = try self.executeProcess(executableURL: otoolURL, arguments: arguments) + // let lines = output.components(separatedBy: .newlines) + // let dylibs = lines.filter { $0.hasPrefix("\t") }.compactMap { line in + // line.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) + // } + // DispatchQueue.main.async { completion(.success(dylibs)) } + // } catch { + // DispatchQueue.main.async { completion(.failure(error)) } + // } + // } + // } - func listDylibsFromApp(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { - listDylibs(filePath: filePath, completion: completion) - } + // Remove listDylibsFromApp method + // func listDylibsFromApp(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { + // listDylibs(filePath: filePath, completion: completion) + // } - // Add other functions here, using executeProcess and DispatchQueue.global().async for stability. + // Add other functions here, using DispatchQueue.global().async for stability. } \ No newline at end of file From 33059c2477060b8ac4d4b4f79197f04bfab384d0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 16:40:34 -0400 Subject: [PATCH 324/391] Update ProcessUtility.swift --- iOS/Views/Extra/ProcessUtility.swift | 53 ++++++++++++++++++---------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/iOS/Views/Extra/ProcessUtility.swift b/iOS/Views/Extra/ProcessUtility.swift index 3ec0b0ed..12002cf4 100644 --- a/iOS/Views/Extra/ProcessUtility.swift +++ b/iOS/Views/Extra/ProcessUtility.swift @@ -5,24 +5,41 @@ class ProcessUtility { private init() {} - func executeShellCommand(_ command: String) -> String? { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/bin/bash") - task.arguments = ["-c", command] - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = pipe - - do { - try task.run() - task.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - return String(data: data, encoding: .utf8) - } catch { - print("Error running task: \(error.localizedDescription)") - return nil + /// Executes a shell command on the backend server and returns the output. + /// - Parameters: + /// - command: The shell command to be executed. + /// - completion: A closure to be called with the command's output or an error message. + func executeShellCommand(_ command: String, completion: @escaping (String?) -> Void) { + // Ensure the URL is valid + guard let url = URL(string: "https://backdoor-backend.onrender.com/execute-command") else { + completion("Invalid URL") + return } + + // Create a URL request and set the HTTP method to POST + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + // Create the request body with the shell command + let body = ["command": command] + request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: []) + + // Create a data task to execute the request + let task = URLSession.shared.dataTask(with: request) { data, response, error in + // Handle network errors + guard let data = data, error == nil else { + print("Network error: \(error?.localizedDescription ?? "Unknown error")") + completion("Network error: \(error?.localizedDescription ?? "Unknown error")") + return + } + + // Parse the response data + let result = String(data: data, encoding: .utf8) + completion(result) + } + + // Start the data task + task.resume() } } \ No newline at end of file From e012ce8513ba4700051fffba5cca8c5f1b1e6e87 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:06:52 -0400 Subject: [PATCH 325/391] Update AppDelegate.swift --- iOS/Delegates/AppDelegate.swift | 36 +++++++-------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/iOS/Delegates/AppDelegate.swift b/iOS/Delegates/AppDelegate.swift index d06299e4..aeeaaa37 100644 --- a/iOS/Delegates/AppDelegate.swift +++ b/iOS/Delegates/AppDelegate.swift @@ -20,29 +20,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle static let isSideloaded = Bundle.main.bundleIdentifier != "com.bdg.backdoor" var window: UIWindow? - // Utility function for executing shell commands - func executeShellCommand(_ command: String) -> String? { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/bin/bash") - task.arguments = ["-c", command] - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = pipe - - do { - try task.run() - task.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - return String(data: data, encoding: .utf8) - } catch { - print("Error running task: \(error.localizedDescription)") - return nil - } - } - - func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let userDefaults = UserDefaults.standard userDefaults.set(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, forKey: "currentVersion") @@ -102,7 +80,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle return true } - func applicationWillEnterForeground(_: UIApplication) { + func applicationWillEnterForeground(_ application: UIApplication) { let backgroundQueue = OperationQueue() backgroundQueue.qualityOfService = .background let operation = SourceRefreshOperation() @@ -139,7 +117,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle backgroundQueue.addOperation(operation) } - func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { if url.scheme == "feather" { // I know this is super hacky, honestly // I don't *exactly* care as it just works :shrug: @@ -288,7 +266,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle } fileprivate func addDefaultRepos() { - if !Preferences.defaultRepos { + if (!Preferences.defaultRepos) { CoreDataManager.shared.saveSource( name: "Backdoor Repository", id: "com.bdg.backdoor-repo", @@ -302,7 +280,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControlle } fileprivate func giveUserDefaultSSLCerts() { - if !Preferences.gotSSLCerts { + if (!Preferences.gotSSLCerts) { getCertificates() Preferences.gotSSLCerts = true } @@ -404,8 +382,8 @@ class ExampleViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - if let appDelegate = UIApplication.shared.delegate as? AppDelegate { - let output = appDelegate.executeShellCommand("echo Hello, World!") + // Use ProcessUtility to execute a shell command + ProcessUtility.shared.executeShellCommand("echo Hello, World!") { output in print(output ?? "No output") } } From 2b012da840c5d25221341e51dc9637cece47f40b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:29:40 -0400 Subject: [PATCH 326/391] Update TweakHandler.swift --- Shared/Magic/TweakHandler.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Shared/Magic/TweakHandler.swift b/Shared/Magic/TweakHandler.swift index 502f26fc..3e699dc5 100644 --- a/Shared/Magic/TweakHandler.swift +++ b/Shared/Magic/TweakHandler.swift @@ -28,11 +28,11 @@ class TweakHandler { } let frameworksPath = app.appendingPathComponent("Frameworks").appendingPathComponent("CydiaSubstrate.framework") - if !fileManager.fileExists(atPath: frameworksPath.path) { + if (!fileManager.fileExists(atPath: frameworksPath.path)) { if let ellekitURL = Bundle.main.url(forResource: "ellekit", withExtension: "deb") { self.urls.insert(ellekitURL.absoluteString, at: 0) } else { - Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle \u2049\ufe0f", type: .error) + Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle \u{2049}\u{fe0f}", type: .error) return } } From 7848a4124ea614a9c1fe55a2e50ebcf4750c353f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:36:01 -0400 Subject: [PATCH 327/391] Update AppDelegate.swift --- iOS/Delegates/AppDelegate.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Delegates/AppDelegate.swift b/iOS/Delegates/AppDelegate.swift index aeeaaa37..4c3e383d 100644 --- a/iOS/Delegates/AppDelegate.swift +++ b/iOS/Delegates/AppDelegate.swift @@ -15,7 +15,6 @@ public func getDocumentsDirectory() -> URL { return paths[0] } -@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UIOnboardingViewControllerDelegate { static let isSideloaded = Bundle.main.bundleIdentifier != "com.bdg.backdoor" var window: UIWindow? From d7408d1f40769176d612fea02032d435772491d2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:39:39 -0400 Subject: [PATCH 328/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 6300bf52..a783f141 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -69,25 +69,25 @@ class LibraryViewController: UITableViewController { } // MARK: - Table view data source - + override func numberOfSections(in tableView: UITableView) -> Int { return 2 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if isFiltering() { return section == 0 ? filteredSignedApps.count : filteredDownloadedApps.count } return section == 0 ? signedApps?.count ?? 0 : downloadedApps?.count ?? 0 } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "RoundedBackgroundCell", for: indexPath) as! AppsTableViewCell - + let app = isFiltering() ? (indexPath.section == 0 ? filteredSignedApps[indexPath.row] : filteredDownloadedApps[indexPath.row]) : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) - + if let app = app { cell.configure( image: UIImage(systemName: "questionmark.app.dashed"), // Placeholder image @@ -95,7 +95,7 @@ class LibraryViewController: UITableViewController { subtitle: app.bundleIdentifier ?? "Unknown Bundle ID" ) } - + return cell } @@ -193,7 +193,6 @@ class LibraryViewController: UITableViewController { // Placeholder methods for undefined functions in this context func getApplication(row: Int, section: Int) -> Any? { return nil } func getApplicationFilePath(with source: Any, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { return nil } - func startImporting() {} func startInstallProcess(meow: Any, filePath: String) {} func shareFile(meow: Any, filePath: String) {} func startSigning(meow: Any) {} From 799eba3d9786878391178878ffa0ab3ce3f5bfd9 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:41:30 -0400 Subject: [PATCH 329/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 7e1b1501..20809947 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -99,7 +99,11 @@ class HomeViewFileHandlers { viewController.activityIndicator.startAnimating() DispatchQueue.global().async { do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: progressHandler as? Progress) + try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: { progress in + if let progressHandler = progressHandler { + progressHandler(Double(progress.completedUnitCount) / Double(progress.totalUnitCount)) + } + }) DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() viewController.loadFiles() @@ -119,7 +123,7 @@ class HomeViewFileHandlers { let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) viewController.present(activityController, animated: true, completion: nil) } - + // Remove Process Execution Helper // private func executeProcess(executableURL: URL, arguments: [String]) throws -> String { // let command = "\(executableURL.path) \(arguments.joined(separator: " "))" @@ -129,7 +133,7 @@ class HomeViewFileHandlers { // throw NSError(domain: "Process", code: -1, userInfo: [NSLocalizedDescriptionKey: "Process failed"]) // } // } - + // Remove listDylibs method // func listDylibs(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { // let otoolURL = URL(fileURLWithPath: "/usr/bin/otool") @@ -147,11 +151,11 @@ class HomeViewFileHandlers { // } // } // } - + // Remove listDylibsFromApp method // func listDylibsFromApp(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { // listDylibs(filePath: filePath, completion: completion) // } - + // Add other functions here, using DispatchQueue.global().async for stability. } \ No newline at end of file From 4c995aeab9ac5a05a2cc4e70c53068a304306df9 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:45:50 -0400 Subject: [PATCH 330/391] Update SigningsDylibViewController.swift --- .../SigningsDylibViewController.swift | 61 ++++++++----------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift index 5ecda049..d66ba6f9 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsDylibViewController.swift @@ -1,5 +1,5 @@ import UIKit -import Foundation // Import Foundation for Process class +import Foundation class SigningsDylibViewController: UITableViewController { var applicationPath: URL @@ -20,10 +20,13 @@ class SigningsDylibViewController: UITableViewController { do { if let executable = try TweakHandler.findExecutable(at: applicationPath) { - if let dylibs = try listDylibs(filePath: executable.path) { - groupDylibs(dylibs) - } else { - print("Failed to list dylibs") + listDylibs(filePath: executable.path) { result in + switch result { + case .success(let dylibs): + self.groupDylibs(dylibs) + case .failure(let error): + print("Failed to list dylibs: \(error)") + } } } else { print("Failed to find executable") @@ -111,40 +114,26 @@ class SigningsDylibViewController: UITableViewController { tableView.deselectRow(at: indexPath, animated: true) } - func listDylibs(filePath: String) throws -> [String]? { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/usr/bin/otool") - task.arguments = ["-L", filePath] - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = Pipe() // Capture standard error too - - try task.run() - task.waitUntilExit() - - if task.terminationStatus == 0 { - let data = pipe.fileHandleForReading.readDataToEndOfFile() - if let output = String(data: data, encoding: .utf8) { - let lines = output.components(separatedBy: .newlines) - var dylibs: [String] = [] - - for line in lines { - let trimmedLine = line.trimmingCharacters(in: .whitespaces) - if trimmedLine.hasPrefix("\t") { - if let dylib = trimmedLine.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) { - dylibs.append(dylib) - } + func listDylibs(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { + let command = "/usr/bin/otool -L \(filePath)" + ProcessUtility.shared.executeShellCommand(command) { output in + guard let output = output else { + completion(.failure(NSError(domain: "ProcessUtility", code: -1, userInfo: [NSLocalizedDescriptionKey: "No output from process"]))) + return + } + + let lines = output.components(separatedBy: .newlines) + var dylibs: [String] = [] + + for line in lines { + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + if trimmedLine.hasPrefix("\t") { + if let dylib = trimmedLine.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) { + dylibs.append(dylib) } } - return dylibs } - } else { - let errorData = task.standardError as! Pipe - let errorString = String(data: errorData.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) - print("otool error: \(errorString ?? "Unknown error")") - throw NSError(domain: "otool", code: Int(task.terminationStatus), userInfo: [NSLocalizedDescriptionKey: "otool failed with status \(task.terminationStatus)"]) + completion(.success(dylibs)) } - return nil } } \ No newline at end of file From 81bb910b968b19896b0118c41b9e279126b23829 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:49:40 -0400 Subject: [PATCH 331/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 570 ++++++++++++++------- 1 file changed, 372 insertions(+), 198 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index a783f141..0783501b 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -1,251 +1,425 @@ -import Foundation +import UIKit import CoreData -import UniformTypeIdentifiers -class LibraryViewController: UITableViewController { - var signedApps: [SignedApps]? - var downloadedApps: [DownloadedApps]? +struct BundleOptions { + var name: String? + var bundleId: String? + var version: String? + var sourceURL: URL? +} + +class SigningsViewController: UIViewController { + + var tableData = [ + [ + "AppIcon", + String.localized("APPS_INFORMATION_TITLE_NAME"), + String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"), + String.localized("APPS_INFORMATION_TITLE_VERSION"), + ], + [ + "Signing", + ], + [ + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), + ], + [ String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES") ], + ] + + var sectionTitles = [ + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_CUSTOMIZATION"), + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_SIGNING"), + String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_ADVANCED"), + "", + ] - var filteredSignedApps: [SignedApps] = [] - var filteredDownloadedApps: [DownloadedApps] = [] + public var application: NSManagedObject? + private var appsViewController: LibraryViewController? + + var signingDataWrapper: SigningDataWrapper + var mainOptions = SigningMainDataWrapper(mainOptions: MainSigningOptions()) + + var bundle: BundleOptions? - var installer: Installer? + var tableView: UITableView! + private var variableBlurView: UIVariableBlurView? + private var largeButton = ActivityIndicatorButton() + private var iconCell = IconImageViewCell() + var signingCompletionHandler: ((Bool) -> Void)? + + init(signingDataWrapper: SigningDataWrapper, application: NSManagedObject, appsViewController: LibraryViewController) { + self.signingDataWrapper = signingDataWrapper + self.application = application + self.appsViewController = appsViewController + super.init(nibName: nil, bundle: nil) + + if let name = application.value(forKey: "name") as? String, + let bundleId = application.value(forKey: "bundleidentifier") as? String, + let version = application.value(forKey: "version") as? String { + let sourceLocation = application.value(forKey: "oSU") as? String + let sourceURL = sourceLocation != nil ? URL(string: sourceLocation!) : nil + self.bundle = BundleOptions( + name: name, + bundleId: bundleId, + version: version, + sourceURL: sourceURL + ) + } + + if let hasGotCert = CoreDataManager.shared.getCurrentCertificate() { self.mainOptions.mainOptions.certificate = hasGotCert } + if let uuid = application.value(forKey: "uuid") as? String { self.mainOptions.mainOptions.uuid = uuid } + + if signingDataWrapper.signingOptions.ppqCheckProtection && + mainOptions.mainOptions.certificate?.certData?.pPQCheck == true { + + if !signingDataWrapper.signingOptions.dynamicProtection { + mainOptions.mainOptions.bundleId = (bundle?.bundleId)!+"."+Preferences.pPQCheckString + } + } + + if let currentBundleId = bundle?.bundleId, + let newBundleId = signingDataWrapper.signingOptions.bundleIdConfig[currentBundleId] { + mainOptions.mainOptions.bundleId = newBundleId + } + + if let currentName = bundle?.name, + let newName = signingDataWrapper.signingOptions.displayNameConfig[currentName] { + mainOptions.mainOptions.name = newName + } + + if signingDataWrapper.signingOptions.dynamicProtection { + Task { + await checkDynamicProtection() + } + } + } - public var searchController: UISearchController! - var popupVC: PopupViewController! - var loaderAlert: UIAlertController? + private func checkDynamicProtection() async { + guard signingDataWrapper.signingOptions.ppqCheckProtection, + mainOptions.mainOptions.certificate?.certData?.pPQCheck == true, + let bundleId = bundle?.bundleId else { + return + } + + let shouldModify = await BundleIdChecker.shouldModifyBundleId(originalBundleId: bundleId) + if shouldModify { + mainOptions.mainOptions.bundleId = bundleId+"."+Preferences.pPQCheckString + } + } - init() { super.init(style: .grouped) } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } override func viewDidLoad() { super.viewDidLoad() + setupNavigation() setupViews() - setupSearchController() - fetchSources() - loaderAlert = presentLoader() + setupToolbar() + #if !targetEnvironment(simulator) + certAlert() + #endif + + let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) + swipeLeft.direction = .left + let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) + swipeRight.direction = .right + tableView.addGestureRecognizer(swipeLeft) + tableView.addGestureRecognizer(swipeRight) + NotificationCenter.default.addObserver(self, selector: #selector(fetch), name: Notification.Name("reloadSigningController"), object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self, name: Notification.Name("reloadSigningController"), object: nil) + } + + @objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) { + let location = gesture.location(in: tableView) + if let indexPath = tableView.indexPathForRow(at: location), + indexPath.section == 1 && indexPath.row == 0 { + let certificates = CoreDataManager.shared.getDatedCertificate() + guard certificates.count > 1 else { return } + + let currentIndex = certificates.firstIndex { $0 == mainOptions.mainOptions.certificate } ?? 0 + var newIndex = currentIndex + + switch gesture.direction { + case .left: + newIndex = (currentIndex + 1) % certificates.count + case .right: + newIndex = (currentIndex - 1 + certificates.count) % certificates.count + default: + break + } + + let feedbackGenerator = UISelectionFeedbackGenerator() + feedbackGenerator.prepare() + feedbackGenerator.selectionChanged() + + Preferences.selectedCert = newIndex + mainOptions.mainOptions.certificate = certificates[newIndex] + tableView.reloadRows(at: [indexPath], with: gesture.direction == .left ? .left : .right) + } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - setupNavigation() + self.tableView.reloadData() + } + + fileprivate func setupNavigation() { + let logoImageView = UIImageView(image: UIImage(named: "feather_glyph")) + logoImageView.contentMode = .scaleAspectFit + navigationItem.titleView = logoImageView + self.navigationController?.navigationBar.prefersLargeTitles = false + + self.isModalInPresentation = true + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: String.localized("DISMISS"), style: .done, target: self, action: #selector(closeSheet)) } fileprivate func setupViews() { + self.tableView = UITableView(frame: .zero, style: .insetGrouped) + self.tableView.translatesAutoresizingMaskIntoConstraints = false self.tableView.dataSource = self self.tableView.delegate = self - tableView.register(AppsTableViewCell.self, forCellReuseIdentifier: "RoundedBackgroundCell") - NotificationCenter.default.addObserver(self, selector: #selector(afetch), name: Notification.Name("lfetch"), object: nil) - NotificationCenter.default.addObserver( - self, - selector: #selector(handleInstallNotification(_:)), - name: Notification.Name("InstallDownloadedApp"), - object: nil - ) + self.tableView.showsHorizontalScrollIndicator = false + self.tableView.showsVerticalScrollIndicator = false + self.tableView.contentInset.bottom = 70 + + self.view.addSubview(tableView) + self.tableView.constraintCompletely(to: view) } - @objc func handleInstallNotification(_ notification: Notification) { - if let userInfo = notification.userInfo, - let app = userInfo["app"] as? DownloadedApps { + fileprivate func setupToolbar() { + largeButton.translatesAutoresizingMaskIntoConstraints = false + largeButton.addTarget(self, action: #selector(startSign), for: .touchUpInside) + + let gradientMask = VariableBlurViewConstants.defaultGradientMask + variableBlurView = UIVariableBlurView(frame: .zero) + variableBlurView?.gradientMask = gradientMask + variableBlurView?.transform = CGAffineTransform(rotationAngle: CGFloat.pi) + variableBlurView?.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(variableBlurView!) + view.addSubview(largeButton) + + var height = 80.0 + if UIDevice.current.userInterfaceIdiom == .pad { height = 65.0 } + + NSLayoutConstraint.activate([ + variableBlurView!.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + variableBlurView!.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + variableBlurView!.bottomAnchor.constraint(equalTo: view.bottomAnchor), + variableBlurView!.heightAnchor.constraint(equalToConstant: height), + largeButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), + largeButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), + largeButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -17), + largeButton.heightAnchor.constraint(equalToConstant: 50) + ]) + + variableBlurView?.layer.zPosition = 3 + largeButton.layer.zPosition = 4 + } + + internal func certAlert() { + if (mainOptions.mainOptions.certificate == nil) { DispatchQueue.main.async { - self.startInstallProcess(meow: app, filePath: app.filePath ?? "") + let alert = UIAlertController( + title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), + message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default) { _ in + self.dismiss(animated: true) + } + ) + self.present(alert, animated: true, completion: nil) } } } - fileprivate func setupNavigation() { - navigationItem.title = "Library" - navigationController?.navigationBar.prefersLargeTitles = true - } - - fileprivate func setupSearchController() { - searchController = UISearchController(searchResultsController: nil) - searchController.searchResultsUpdater = self - searchController.obscuresBackgroundDuringPresentation = false - searchController.searchBar.placeholder = "Search Apps..." - navigationItem.searchController = searchController - definesPresentationContext = true - } - - // MARK: - Table view data source - - override func numberOfSections(in tableView: UITableView) -> Int { - return 2 + @objc func closeSheet() { + dismiss(animated: true, completion: nil) } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if isFiltering() { - return section == 0 ? filteredSignedApps.count : filteredDownloadedApps.count - } - return section == 0 ? signedApps?.count ?? 0 : downloadedApps?.count ?? 0 + @objc func fetch() { + self.tableView.reloadData() } - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "RoundedBackgroundCell", for: indexPath) as! AppsTableViewCell - - let app = isFiltering() - ? (indexPath.section == 0 ? filteredSignedApps[indexPath.row] : filteredDownloadedApps[indexPath.row]) - : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) - - if let app = app { - cell.configure( - image: UIImage(systemName: "questionmark.app.dashed"), // Placeholder image - title: app.name ?? "Unknown App", - subtitle: app.bundleIdentifier ?? "Unknown Bundle ID" - ) + @objc func startSign() { + self.navigationItem.leftBarButtonItem = nil + largeButton.showLoadingIndicator() + signInitialApp( + bundle: bundle!, + mainOptions: mainOptions, + signingOptions: signingDataWrapper, + appPath:getFilesForDownloadedApps(app: application as! DownloadedApps, getuuidonly: false)) + { result in + switch result { + case .success(let (signedPath, signedApp)): + self.appsViewController?.fetchSources() + self.appsViewController?.tableView.reloadData() + Debug.shared.log(message: signedPath.path) + if self.signingDataWrapper.signingOptions.installAfterSigned { + self.appsViewController?.startInstallProcess(meow: signedApp, filePath: signedPath.path) + self.signingCompletionHandler?(true) + } + + case .failure(let error): + Debug.shared.log(message: "Signing failed: \(error.localizedDescription)", type: .error) + self.signingCompletionHandler?(false) + } + + self.dismiss(animated: true) } - - return cell } +} + +extension SigningsViewController: UITableViewDataSource, UITableViewDelegate { + func numberOfSections(in tableView: UITableView) -> Int { return sectionTitles.count } + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableData[section].count } + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionTitles[section] } + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return sectionTitles[section].isEmpty ? 0 : 40 } - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return section == 0 ? "Signed Apps" : "Downloaded Apps" + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let title = sectionTitles[section] + let headerView = InsetGroupedSectionHeader(title: title) + return headerView } - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let reuseIdentifier = "Cell" + let cell = UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifier) + cell.accessoryType = .none + cell.selectionStyle = .gray - let app = isFiltering() - ? (indexPath.section == 0 ? filteredSignedApps[indexPath.row] : filteredDownloadedApps[indexPath.row]) - : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) + let cellText = tableData[indexPath.section][indexPath.row] + cell.textLabel?.text = cellText - if let app = app { - popupVC = PopupViewController() + switch cellText { + case "AppIcon": + let cell = iconCell - if indexPath.section == 0 { - // Handle Signed Apps action - let button1 = PopupViewController.PopupButton( - title: "Sign", - style: .default - ) { - self.startSigning(meow: app) - } - - popupVC.configureButtons([button1]) - - - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - .medium() - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) - + if (mainOptions.mainOptions.iconURL != nil) { + cell.configure(with: mainOptions.mainOptions.iconURL) } else { - // Handle Downloaded Apps actions - - let button1 = PopupViewController.PopupButton( - title: "Install", - style: .default - ) { - - if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { - - let alertController = UIAlertController( - title: "Install App", - message: "Are you sure you want to install this app?", - preferredStyle: .alert - ) - - let confirmAction = UIAlertAction( - title: "Install", - style: .default - ) { _ in - self.startInstallProcess(meow: app, filePath: filePath.path) - } - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - self.present(alertController, animated: true, completion: nil) - } - } - - let button2 = PopupViewController.PopupButton( - title: "Share", - style: .default - ) { - if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { - self.shareFile(meow: app, filePath: filePath.path) - } - } - - popupVC.configureButtons([button1, button2]) - - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - .medium(), - .large() - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) + cell.configure(with: CoreDataManager.shared.loadImage(from: getIconURL(for: application as! DownloadedApps))) + } + + cell.accessoryType = .disclosureIndicator + return cell + case String.localized("APPS_INFORMATION_TITLE_NAME"): + cell.textLabel?.text = String.localized("APPS_INFORMATION_TITLE_NAME") + cell.detailTextLabel?.text = mainOptions.mainOptions.name ?? bundle?.name + cell.accessoryType = .disclosureIndicator + case String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"): + cell.textLabel?.text = String.localized("APPS_INFORMATION_TITLE_IDENTIFIER") + cell.detailTextLabel?.text = mainOptions.mainOptions.bundleId ?? bundle?.bundleId + cell.accessoryType = .disclosureIndicator + case String.localized("APPS_INFORMATION_TITLE_VERSION"): + cell.detailTextLabel?.text = mainOptions.mainOptions.version ?? bundle?.version + cell.accessoryType = .disclosureIndicator + case "Signing": + if let hasGotCert = mainOptions.mainOptions.certificate { + let cell = CertificateViewTableViewCell() + cell.configure(with: hasGotCert, isSelected: false) + cell.selectionStyle = .none + return cell + } else { + cell.textLabel?.text = String.localized("SETTINGS_VIEW_CONTROLLER_CELL_CURRENT_CERTIFICATE_NOSELECTED") + cell.textLabel?.textColor = .secondaryLabel + cell.selectionStyle = .none } + case "Change Certificate", String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), String.localized("APP_SIGNI[...] + cell.accessoryType = .disclosureIndicator + default: + break } + + return cell } - - // Placeholder methods for undefined functions in this context - func getApplication(row: Int, section: Int) -> Any? { return nil } - func getApplicationFilePath(with source: Any, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { return nil } - func startInstallProcess(meow: Any, filePath: String) {} - func shareFile(meow: Any, filePath: String) {} - func startSigning(meow: Any) {} - @objc func afetch() {} - func presentLoader() -> UIAlertController? { return nil } -} -extension LibraryViewController: UISearchResultsUpdating { - func updateSearchResults(for searchController: UISearchController) { - filterContentForSearchText(searchController.searchBar.text!) - } - - fileprivate func fetchSources() { - // Fetch Signed Apps - let signedAppsFetchRequest: NSFetchRequest = SignedApps.fetchRequest() - do { - signedApps = try CoreDataManager.shared.context.fetch(signedAppsFetchRequest) - } catch { - print("Failed to fetch signed apps: \(error)") - } - - // Fetch Downloaded Apps - let downloadedAppsFetchRequest: NSFetchRequest = DownloadedApps.fetchRequest() - do { - downloadedApps = try CoreDataManager.shared.context.fetch(downloadedAppsFetchRequest) - } catch { - print("Failed to fetch downloaded apps: \(error)") + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let itemTapped = tableData[indexPath.section][indexPath.row] + switch itemTapped { + case "AppIcon": + importAppIconFile() + case String.localized("APPS_INFORMATION_TITLE_NAME"): + + let l = SigningsInputViewController( + parentView: self, + initialValue: (mainOptions.mainOptions.name ?? bundle?.name)!, + valueToSaveTo: indexPath.row + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"): + + let l = SigningsInputViewController( + parentView: self, + initialValue: (mainOptions.mainOptions.bundleId ?? bundle?.bundleId)!, + valueToSaveTo: indexPath.row + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APPS_INFORMATION_TITLE_VERSION"): + + let l = SigningsInputViewController( + parentView: self, + initialValue: (mainOptions.mainOptions.version ?? bundle?.version)!, + valueToSaveTo: indexPath.row + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"): + + let l = SigningsTweakViewController( + signingDataWrapper: signingDataWrapper + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"): + + let l = SigningsDylibViewController( + mainOptions: mainOptions, + app: getFilesForDownloadedApps(app: application as! DownloadedApps, getuuidonly: false) + ) + + navigationController?.pushViewController(l, animated: true) + case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES"): + + let l = SigningsAdvancedViewController( + signingDataWrapper: signingDataWrapper, + mainOptions: mainOptions + ) + + navigationController?.pushViewController(l, animated: true) + + default: + break } - DispatchQueue.main.async { - self.tableView.reloadData() - self.loaderAlert?.dismiss(animated: true, completion: nil) - } - } - - fileprivate func isFiltering() -> Bool { - return searchController.isActive && !searchBarIsEmpty() + tableView.deselectRow(at: indexPath, animated: true) } +} + +// MARK: - this sucks + +extension SigningsViewController { - fileprivate func searchBarIsEmpty() -> Bool { - return searchController.searchBar.text?.isEmpty ?? true + public func getFilesForDownloadedApps(app: DownloadedApps, getuuidonly: Bool) -> URL { + return CoreDataManager.shared.getFilesForDownloadedApps(for: app, getuuidonly: getuuidonly) } - fileprivate func filterContentForSearchText(_ searchText: String, scope: String = "All") { - - filteredSignedApps = signedApps?.filter { app in - return app.name?.lowercased().contains(searchText.lowercased()) ?? false - } ?? [] - - filteredDownloadedApps = downloadedApps?.filter { app in - return app.name?.lowercased().contains(searchText.lowercased()) ?? false - } ?? [] + private func getIconURL(for app: DownloadedApps) -> URL? { + guard let iconURLString = app.value(forKey: "iconURL") as? String, + let iconURL = URL(string: iconURLString) else { + return nil + } - tableView.reloadData() + let filesURL = getFilesForDownloadedApps(app: app, getuuidonly: false) + return filesURL.appendingPathComponent(iconURL.lastPathComponent) } } \ No newline at end of file From 8399d8db748342f93112faa16f506adeb1f00cfc Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:51:06 -0400 Subject: [PATCH 332/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 570 +++++++-------------- 1 file changed, 198 insertions(+), 372 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 0783501b..a783f141 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -1,425 +1,251 @@ -import UIKit +import Foundation import CoreData +import UniformTypeIdentifiers -struct BundleOptions { - var name: String? - var bundleId: String? - var version: String? - var sourceURL: URL? -} - -class SigningsViewController: UIViewController { - - var tableData = [ - [ - "AppIcon", - String.localized("APPS_INFORMATION_TITLE_NAME"), - String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"), - String.localized("APPS_INFORMATION_TITLE_VERSION"), - ], - [ - "Signing", - ], - [ - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), - ], - [ String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES") ], - ] - - var sectionTitles = [ - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_CUSTOMIZATION"), - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_SIGNING"), - String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_TITLE_ADVANCED"), - "", - ] +class LibraryViewController: UITableViewController { + var signedApps: [SignedApps]? + var downloadedApps: [DownloadedApps]? - public var application: NSManagedObject? - private var appsViewController: LibraryViewController? - - var signingDataWrapper: SigningDataWrapper - var mainOptions = SigningMainDataWrapper(mainOptions: MainSigningOptions()) - - var bundle: BundleOptions? + var filteredSignedApps: [SignedApps] = [] + var filteredDownloadedApps: [DownloadedApps] = [] - var tableView: UITableView! - private var variableBlurView: UIVariableBlurView? - private var largeButton = ActivityIndicatorButton() - private var iconCell = IconImageViewCell() - var signingCompletionHandler: ((Bool) -> Void)? - - init(signingDataWrapper: SigningDataWrapper, application: NSManagedObject, appsViewController: LibraryViewController) { - self.signingDataWrapper = signingDataWrapper - self.application = application - self.appsViewController = appsViewController - super.init(nibName: nil, bundle: nil) - - if let name = application.value(forKey: "name") as? String, - let bundleId = application.value(forKey: "bundleidentifier") as? String, - let version = application.value(forKey: "version") as? String { - let sourceLocation = application.value(forKey: "oSU") as? String - let sourceURL = sourceLocation != nil ? URL(string: sourceLocation!) : nil - self.bundle = BundleOptions( - name: name, - bundleId: bundleId, - version: version, - sourceURL: sourceURL - ) - } - - if let hasGotCert = CoreDataManager.shared.getCurrentCertificate() { self.mainOptions.mainOptions.certificate = hasGotCert } - if let uuid = application.value(forKey: "uuid") as? String { self.mainOptions.mainOptions.uuid = uuid } - - if signingDataWrapper.signingOptions.ppqCheckProtection && - mainOptions.mainOptions.certificate?.certData?.pPQCheck == true { - - if !signingDataWrapper.signingOptions.dynamicProtection { - mainOptions.mainOptions.bundleId = (bundle?.bundleId)!+"."+Preferences.pPQCheckString - } - } - - if let currentBundleId = bundle?.bundleId, - let newBundleId = signingDataWrapper.signingOptions.bundleIdConfig[currentBundleId] { - mainOptions.mainOptions.bundleId = newBundleId - } - - if let currentName = bundle?.name, - let newName = signingDataWrapper.signingOptions.displayNameConfig[currentName] { - mainOptions.mainOptions.name = newName - } - - if signingDataWrapper.signingOptions.dynamicProtection { - Task { - await checkDynamicProtection() - } - } - } + var installer: Installer? - private func checkDynamicProtection() async { - guard signingDataWrapper.signingOptions.ppqCheckProtection, - mainOptions.mainOptions.certificate?.certData?.pPQCheck == true, - let bundleId = bundle?.bundleId else { - return - } - - let shouldModify = await BundleIdChecker.shouldModifyBundleId(originalBundleId: bundleId) - if shouldModify { - mainOptions.mainOptions.bundleId = bundleId+"."+Preferences.pPQCheckString - } - } + public var searchController: UISearchController! + var popupVC: PopupViewController! + var loaderAlert: UIAlertController? - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + init() { super.init(style: .grouped) } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() - setupNavigation() setupViews() - setupToolbar() - #if !targetEnvironment(simulator) - certAlert() - #endif - - let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) - swipeLeft.direction = .left - let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) - swipeRight.direction = .right - tableView.addGestureRecognizer(swipeLeft) - tableView.addGestureRecognizer(swipeRight) - NotificationCenter.default.addObserver(self, selector: #selector(fetch), name: Notification.Name("reloadSigningController"), object: nil) - } - - deinit { - NotificationCenter.default.removeObserver(self, name: Notification.Name("reloadSigningController"), object: nil) - } - - @objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) { - let location = gesture.location(in: tableView) - if let indexPath = tableView.indexPathForRow(at: location), - indexPath.section == 1 && indexPath.row == 0 { - let certificates = CoreDataManager.shared.getDatedCertificate() - guard certificates.count > 1 else { return } - - let currentIndex = certificates.firstIndex { $0 == mainOptions.mainOptions.certificate } ?? 0 - var newIndex = currentIndex - - switch gesture.direction { - case .left: - newIndex = (currentIndex + 1) % certificates.count - case .right: - newIndex = (currentIndex - 1 + certificates.count) % certificates.count - default: - break - } - - let feedbackGenerator = UISelectionFeedbackGenerator() - feedbackGenerator.prepare() - feedbackGenerator.selectionChanged() - - Preferences.selectedCert = newIndex - mainOptions.mainOptions.certificate = certificates[newIndex] - tableView.reloadRows(at: [indexPath], with: gesture.direction == .left ? .left : .right) - } + setupSearchController() + fetchSources() + loaderAlert = presentLoader() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - self.tableView.reloadData() - } - - fileprivate func setupNavigation() { - let logoImageView = UIImageView(image: UIImage(named: "feather_glyph")) - logoImageView.contentMode = .scaleAspectFit - navigationItem.titleView = logoImageView - self.navigationController?.navigationBar.prefersLargeTitles = false - - self.isModalInPresentation = true - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: String.localized("DISMISS"), style: .done, target: self, action: #selector(closeSheet)) + setupNavigation() } fileprivate func setupViews() { - self.tableView = UITableView(frame: .zero, style: .insetGrouped) - self.tableView.translatesAutoresizingMaskIntoConstraints = false self.tableView.dataSource = self self.tableView.delegate = self - self.tableView.showsHorizontalScrollIndicator = false - self.tableView.showsVerticalScrollIndicator = false - self.tableView.contentInset.bottom = 70 - - self.view.addSubview(tableView) - self.tableView.constraintCompletely(to: view) + tableView.register(AppsTableViewCell.self, forCellReuseIdentifier: "RoundedBackgroundCell") + NotificationCenter.default.addObserver(self, selector: #selector(afetch), name: Notification.Name("lfetch"), object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(handleInstallNotification(_:)), + name: Notification.Name("InstallDownloadedApp"), + object: nil + ) } - fileprivate func setupToolbar() { - largeButton.translatesAutoresizingMaskIntoConstraints = false - largeButton.addTarget(self, action: #selector(startSign), for: .touchUpInside) - - let gradientMask = VariableBlurViewConstants.defaultGradientMask - variableBlurView = UIVariableBlurView(frame: .zero) - variableBlurView?.gradientMask = gradientMask - variableBlurView?.transform = CGAffineTransform(rotationAngle: CGFloat.pi) - variableBlurView?.translatesAutoresizingMaskIntoConstraints = false - - view.addSubview(variableBlurView!) - view.addSubview(largeButton) - - var height = 80.0 - if UIDevice.current.userInterfaceIdiom == .pad { height = 65.0 } - - NSLayoutConstraint.activate([ - variableBlurView!.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), - variableBlurView!.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), - variableBlurView!.bottomAnchor.constraint(equalTo: view.bottomAnchor), - variableBlurView!.heightAnchor.constraint(equalToConstant: height), + @objc func handleInstallNotification(_ notification: Notification) { + if let userInfo = notification.userInfo, + let app = userInfo["app"] as? DownloadedApps { - largeButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), - largeButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), - largeButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -17), - largeButton.heightAnchor.constraint(equalToConstant: 50) - ]) - - variableBlurView?.layer.zPosition = 3 - largeButton.layer.zPosition = 4 - } - - internal func certAlert() { - if (mainOptions.mainOptions.certificate == nil) { DispatchQueue.main.async { - let alert = UIAlertController( - title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), - message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default) { _ in - self.dismiss(animated: true) - } - ) - self.present(alert, animated: true, completion: nil) + self.startInstallProcess(meow: app, filePath: app.filePath ?? "") } } } - @objc func closeSheet() { - dismiss(animated: true, completion: nil) + fileprivate func setupNavigation() { + navigationItem.title = "Library" + navigationController?.navigationBar.prefersLargeTitles = true } - @objc func fetch() { - self.tableView.reloadData() + fileprivate func setupSearchController() { + searchController = UISearchController(searchResultsController: nil) + searchController.searchResultsUpdater = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.searchBar.placeholder = "Search Apps..." + navigationItem.searchController = searchController + definesPresentationContext = true } - @objc func startSign() { - self.navigationItem.leftBarButtonItem = nil - largeButton.showLoadingIndicator() - signInitialApp( - bundle: bundle!, - mainOptions: mainOptions, - signingOptions: signingDataWrapper, - appPath:getFilesForDownloadedApps(app: application as! DownloadedApps, getuuidonly: false)) - { result in - switch result { - case .success(let (signedPath, signedApp)): - self.appsViewController?.fetchSources() - self.appsViewController?.tableView.reloadData() - Debug.shared.log(message: signedPath.path) - if self.signingDataWrapper.signingOptions.installAfterSigned { - self.appsViewController?.startInstallProcess(meow: signedApp, filePath: signedPath.path) - self.signingCompletionHandler?(true) - } - - case .failure(let error): - Debug.shared.log(message: "Signing failed: \(error.localizedDescription)", type: .error) - self.signingCompletionHandler?(false) - } - - self.dismiss(animated: true) + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 2 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if isFiltering() { + return section == 0 ? filteredSignedApps.count : filteredDownloadedApps.count } + return section == 0 ? signedApps?.count ?? 0 : downloadedApps?.count ?? 0 } -} - -extension SigningsViewController: UITableViewDataSource, UITableViewDelegate { - func numberOfSections(in tableView: UITableView) -> Int { return sectionTitles.count } - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableData[section].count } - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionTitles[section] } - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return sectionTitles[section].isEmpty ? 0 : 40 } - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let title = sectionTitles[section] - let headerView = InsetGroupedSectionHeader(title: title) - return headerView + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "RoundedBackgroundCell", for: indexPath) as! AppsTableViewCell + + let app = isFiltering() + ? (indexPath.section == 0 ? filteredSignedApps[indexPath.row] : filteredDownloadedApps[indexPath.row]) + : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) + + if let app = app { + cell.configure( + image: UIImage(systemName: "questionmark.app.dashed"), // Placeholder image + title: app.name ?? "Unknown App", + subtitle: app.bundleIdentifier ?? "Unknown Bundle ID" + ) + } + + return cell + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return section == 0 ? "Signed Apps" : "Downloaded Apps" } - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let reuseIdentifier = "Cell" - let cell = UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifier) - cell.accessoryType = .none - cell.selectionStyle = .gray + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) - let cellText = tableData[indexPath.section][indexPath.row] - cell.textLabel?.text = cellText + let app = isFiltering() + ? (indexPath.section == 0 ? filteredSignedApps[indexPath.row] : filteredDownloadedApps[indexPath.row]) + : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) - switch cellText { - case "AppIcon": - let cell = iconCell - - if (mainOptions.mainOptions.iconURL != nil) { - cell.configure(with: mainOptions.mainOptions.iconURL) - } else { - cell.configure(with: CoreDataManager.shared.loadImage(from: getIconURL(for: application as! DownloadedApps))) - } + if let app = app { + popupVC = PopupViewController() - cell.accessoryType = .disclosureIndicator - return cell - case String.localized("APPS_INFORMATION_TITLE_NAME"): - cell.textLabel?.text = String.localized("APPS_INFORMATION_TITLE_NAME") - cell.detailTextLabel?.text = mainOptions.mainOptions.name ?? bundle?.name - cell.accessoryType = .disclosureIndicator - case String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"): - cell.textLabel?.text = String.localized("APPS_INFORMATION_TITLE_IDENTIFIER") - cell.detailTextLabel?.text = mainOptions.mainOptions.bundleId ?? bundle?.bundleId - cell.accessoryType = .disclosureIndicator - case String.localized("APPS_INFORMATION_TITLE_VERSION"): - cell.detailTextLabel?.text = mainOptions.mainOptions.version ?? bundle?.version - cell.accessoryType = .disclosureIndicator - case "Signing": - if let hasGotCert = mainOptions.mainOptions.certificate { - let cell = CertificateViewTableViewCell() - cell.configure(with: hasGotCert, isSelected: false) - cell.selectionStyle = .none - return cell + if indexPath.section == 0 { + // Handle Signed Apps action + let button1 = PopupViewController.PopupButton( + title: "Sign", + style: .default + ) { + self.startSigning(meow: app) + } + + popupVC.configureButtons([button1]) + + + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + .medium() + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) + } else { - cell.textLabel?.text = String.localized("SETTINGS_VIEW_CONTROLLER_CELL_CURRENT_CERTIFICATE_NOSELECTED") - cell.textLabel?.textColor = .secondaryLabel - cell.selectionStyle = .none + // Handle Downloaded Apps actions + + let button1 = PopupViewController.PopupButton( + title: "Install", + style: .default + ) { + + if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { + + let alertController = UIAlertController( + title: "Install App", + message: "Are you sure you want to install this app?", + preferredStyle: .alert + ) + + let confirmAction = UIAlertAction( + title: "Install", + style: .default + ) { _ in + self.startInstallProcess(meow: app, filePath: filePath.path) + } + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true, completion: nil) + } + } + + let button2 = PopupViewController.PopupButton( + title: "Share", + style: .default + ) { + if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { + self.shareFile(meow: app, filePath: filePath.path) + } + } + + popupVC.configureButtons([button1, button2]) + + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + .medium(), + .large() + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) } - case "Change Certificate", String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), String.localized("APP_SIGNI[...] - cell.accessoryType = .disclosureIndicator - default: - break } - - return cell } + + // Placeholder methods for undefined functions in this context + func getApplication(row: Int, section: Int) -> Any? { return nil } + func getApplicationFilePath(with source: Any, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { return nil } + func startInstallProcess(meow: Any, filePath: String) {} + func shareFile(meow: Any, filePath: String) {} + func startSigning(meow: Any) {} + @objc func afetch() {} + func presentLoader() -> UIAlertController? { return nil } +} - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let itemTapped = tableData[indexPath.section][indexPath.row] - switch itemTapped { - case "AppIcon": - importAppIconFile() - case String.localized("APPS_INFORMATION_TITLE_NAME"): - - let l = SigningsInputViewController( - parentView: self, - initialValue: (mainOptions.mainOptions.name ?? bundle?.name)!, - valueToSaveTo: indexPath.row - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APPS_INFORMATION_TITLE_IDENTIFIER"): - - let l = SigningsInputViewController( - parentView: self, - initialValue: (mainOptions.mainOptions.bundleId ?? bundle?.bundleId)!, - valueToSaveTo: indexPath.row - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APPS_INFORMATION_TITLE_VERSION"): - - let l = SigningsInputViewController( - parentView: self, - initialValue: (mainOptions.mainOptions.version ?? bundle?.version)!, - valueToSaveTo: indexPath.row - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"): - - let l = SigningsTweakViewController( - signingDataWrapper: signingDataWrapper - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"): - - let l = SigningsDylibViewController( - mainOptions: mainOptions, - app: getFilesForDownloadedApps(app: application as! DownloadedApps, getuuidonly: false) - ) - - navigationController?.pushViewController(l, animated: true) - case String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES"): - - let l = SigningsAdvancedViewController( - signingDataWrapper: signingDataWrapper, - mainOptions: mainOptions - ) - - navigationController?.pushViewController(l, animated: true) - - default: - break +extension LibraryViewController: UISearchResultsUpdating { + func updateSearchResults(for searchController: UISearchController) { + filterContentForSearchText(searchController.searchBar.text!) + } + + fileprivate func fetchSources() { + // Fetch Signed Apps + let signedAppsFetchRequest: NSFetchRequest = SignedApps.fetchRequest() + do { + signedApps = try CoreDataManager.shared.context.fetch(signedAppsFetchRequest) + } catch { + print("Failed to fetch signed apps: \(error)") } - tableView.deselectRow(at: indexPath, animated: true) + // Fetch Downloaded Apps + let downloadedAppsFetchRequest: NSFetchRequest = DownloadedApps.fetchRequest() + do { + downloadedApps = try CoreDataManager.shared.context.fetch(downloadedAppsFetchRequest) + } catch { + print("Failed to fetch downloaded apps: \(error)") + } + + DispatchQueue.main.async { + self.tableView.reloadData() + self.loaderAlert?.dismiss(animated: true, completion: nil) + } } -} - -// MARK: - this sucks - -extension SigningsViewController { - public func getFilesForDownloadedApps(app: DownloadedApps, getuuidonly: Bool) -> URL { - return CoreDataManager.shared.getFilesForDownloadedApps(for: app, getuuidonly: getuuidonly) + fileprivate func isFiltering() -> Bool { + return searchController.isActive && !searchBarIsEmpty() } - private func getIconURL(for app: DownloadedApps) -> URL? { - guard let iconURLString = app.value(forKey: "iconURL") as? String, - let iconURL = URL(string: iconURLString) else { - return nil - } + fileprivate func searchBarIsEmpty() -> Bool { + return searchController.searchBar.text?.isEmpty ?? true + } + + fileprivate func filterContentForSearchText(_ searchText: String, scope: String = "All") { + + filteredSignedApps = signedApps?.filter { app in + return app.name?.lowercased().contains(searchText.lowercased()) ?? false + } ?? [] + + filteredDownloadedApps = downloadedApps?.filter { app in + return app.name?.lowercased().contains(searchText.lowercased()) ?? false + } ?? [] - let filesURL = getFilesForDownloadedApps(app: app, getuuidonly: false) - return filesURL.appendingPathComponent(iconURL.lastPathComponent) + tableView.reloadData() } } \ No newline at end of file From c0685183698ad20c08ee02a9d22f91e7dce09f1b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:52:02 -0400 Subject: [PATCH 333/391] Update SigningsViewController.swift --- .../Signing/SigningViewController/SigningsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift index fde3d0b2..0783501b 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift @@ -332,7 +332,7 @@ extension SigningsViewController: UITableViewDataSource, UITableViewDelegate { cell.textLabel?.textColor = .secondaryLabel cell.selectionStyle = .none } - case "Change Certificate", String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES"): + case "Change Certificate", String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), String.localized("APP_SIGNI[...] cell.accessoryType = .disclosureIndicator default: break From 21d0feb6cd995a34902570aecb7046f34aea083f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:56:11 -0400 Subject: [PATCH 334/391] Update SigningsInputViewController.swift --- .../SigningsInputViewController.swift | 172 +++++++++--------- 1 file changed, 87 insertions(+), 85 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsInputViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsInputViewController.swift index 0fe52d2d..07e2b2a2 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsInputViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsInputViewController.swift @@ -1,90 +1,92 @@ -// -// SigningsInputViewController.swift -// feather -// -// Created by samara on 8/15/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import Foundation import UIKit class SigningsInputViewController: UITableViewController { - var parentView: SigningsViewController - var initialValue: String - var valueToSaveTo: Int - private var changedValue: String? - - private lazy var textField: UITextField = { - let textField = UITextField(frame: .zero) - textField.translatesAutoresizingMaskIntoConstraints = false - textField.addTarget(self, action: #selector(textDidChange), for: .editingChanged) - return textField - }() + var parentView: SigningsViewController + var initialValue: String + var valueToSaveTo: Int + private var changedValue: String? + + private lazy var textField: UITextField = { + let textField = UITextField(frame: .zero) + textField.translatesAutoresizingMaskIntoConstraints = false + textField.addTarget(self, action: #selector(textDidChange), for: .editingChanged) + return textField + }() - init(parentView: SigningsViewController, initialValue: String, valueToSaveTo: Int) { - self.parentView = parentView - self.initialValue = initialValue - self.valueToSaveTo = valueToSaveTo - super.init(style: .insetGrouped) - } - - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - override func viewDidLoad() { - super.viewDidLoad() - navigationItem.largeTitleDisplayMode = .never - self.title = initialValue.capitalized - - let saveButton = UIBarButtonItem(title: String.localized("SAVE"), style: .done, target: self, action: #selector(saveButton)) - saveButton.isEnabled = false - navigationItem.rightBarButtonItem = saveButton - - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "InputCell") - } - - @objc func saveButton() { - switch valueToSaveTo { - case 1: - parentView.mainOptions.mainOptions.name = changedValue - case 2: - parentView.mainOptions.mainOptions.bundleId = changedValue - case 3: - parentView.mainOptions.mainOptions.version = changedValue - default: - break - } - - self.navigationController?.popViewController(animated: true) - } - - @objc private func textDidChange() { - navigationItem.rightBarButtonItem?.isEnabled = !(textField.text?.isEmpty ?? true) - changedValue = textField.text - } - - override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "InputCell", for: indexPath) - switch indexPath.section { - case 0: - textField.text = initialValue - textField.placeholder = initialValue - - if textField.superview == nil { - cell.contentView.addSubview(textField) - NSLayoutConstraint.activate([ - textField.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor), - textField.leadingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.leadingAnchor), - textField.trailingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.trailingAnchor) - ]) - } - - cell.selectionStyle = .none - default: break - } - return cell - } -} + init(parentView: SigningsViewController, initialValue: String, valueToSaveTo: Int) { + self.parentView = parentView + self.initialValue = initialValue + self.valueToSaveTo = valueToSaveTo + super.init(style: .insetGrouped) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.largeTitleDisplayMode = .never + self.title = initialValue.capitalized + + let saveButton = UIBarButtonItem(title: String.localized("SAVE"), style: .done, target: self, action: #selector(saveButton)) + saveButton.isEnabled = false + navigationItem.rightBarButtonItem = saveButton + + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "InputCell") + } + + @objc func saveButton() { + guard let changedValue = changedValue else { return } + + switch valueToSaveTo { + case 1: + parentView.mainOptions.mainOptions.name = changedValue + case 2: + parentView.mainOptions.mainOptions.bundleId = changedValue + case 3: + parentView.mainOptions.mainOptions.version = changedValue + default: + break + } + + self.navigationController?.popViewController(animated: true) + } + + @objc private func textDidChange() { + navigationItem.rightBarButtonItem?.isEnabled = !(textField.text?.isEmpty ?? true) + changedValue = textField.text + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "InputCell", for: indexPath) + switch indexPath.section { + case 0: + textField.text = initialValue + textField.placeholder = initialValue + + if textField.superview == nil { + cell.contentView.addSubview(textField) + NSLayoutConstraint.activate([ + textField.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor), + textField.leadingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.leadingAnchor), + textField.trailingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.trailingAnchor) + ]) + } + + cell.selectionStyle = .none + default: + break + } + return cell + } +} \ No newline at end of file From a29ab7e2e930363e634451af3ed8fdbb731deed9 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 17:56:41 -0400 Subject: [PATCH 335/391] Update SigningsAdvancedViewController.swift --- .../SigningsAdvancedViewController.swift | 231 +++++++++--------- 1 file changed, 111 insertions(+), 120 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsAdvancedViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsAdvancedViewController.swift index 3674bc7d..67a5789c 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsAdvancedViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsAdvancedViewController.swift @@ -1,130 +1,121 @@ -// -// SigningsAdvancedViewController.swift -// feather -// -// Created by samara on 27.10.2024. -// - import UIKit -class SigningsAdvancedViewController: FRSITableViewCOntroller { - private var toggleOptions: [TogglesOption] - - override init(signingDataWrapper: SigningDataWrapper, mainOptions: SigningMainDataWrapper) { - self.toggleOptions = feather.toggleOptions(signingDataWrapper: signingDataWrapper) - super.init(signingDataWrapper: signingDataWrapper, mainOptions: mainOptions) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - tableData = [ - [ String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_APPEARENCE") ], - [ String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_MINIMUM_APP_VERSION") ], - [], - ] - - sectionTitles = [ - String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_APPEARENCE"), - String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_MINIMUM_APP_VERSION"), - String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_PROPERTIES"), - ] - +class SigningsAdvancedViewController: FRSITableViewController { + private var toggleOptions: [TogglesOption] + + override init(signingDataWrapper: SigningDataWrapper, mainOptions: SigningMainDataWrapper) { + self.toggleOptions = feather.toggleOptions(signingDataWrapper: signingDataWrapper) + super.init(signingDataWrapper: signingDataWrapper, mainOptions: mainOptions) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + tableData = [ + [String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_APPEARENCE")], + [String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_MINIMUM_APP_VERSION")], + [], + ] + + sectionTitles = [ + String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_APPEARENCE"), + String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_MINIMUM_APP_VERSION"), + String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_PROPERTIES"), + ] + self.title = String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_PROPERTIES") - self.tableData[2] = toggleOptions.map { $0.title } - } + self.tableData[2] = toggleOptions.map { $0.title } + } } extension SigningsAdvancedViewController { - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let reuseIdentifier = "Cell" + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let reuseIdentifier = "Cell" let cell = UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifier) - cell.accessoryType = .none - cell.selectionStyle = .gray - - let cellText = tableData[indexPath.section][indexPath.row] - cell.textLabel?.text = cellText - - - switch cellText { - case String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_APPEARENCE"): - let forceLightDarkAppearence = TweakLibraryViewCell() - forceLightDarkAppearence.selectionStyle = .none - forceLightDarkAppearence.configureSegmentedControl( - with: mainOptions.mainOptions.forceLightDarkAppearenceString, - selectedIndex: 0 - ) - forceLightDarkAppearence.segmentedControl.addTarget(self, action: #selector(forceLightDarkAppearenceDidChange(_:)), for: .valueChanged) - - return forceLightDarkAppearence - case String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_MINIMUM_APP_VERSION"): - let forceMinimumVersion = TweakLibraryViewCell() - forceMinimumVersion.selectionStyle = .none - forceMinimumVersion.configureSegmentedControl( - with: mainOptions.mainOptions.forceMinimumVersionString, - selectedIndex: 0 - ) - forceMinimumVersion.segmentedControl.addTarget(self, action: #selector(forceMinimumVersionDidChange(_:)), for: .valueChanged) - - return forceMinimumVersion - default: - break - } - - if indexPath.section == 2 { - let toggleOption = toggleOptions[indexPath.row] - cell.textLabel?.text = toggleOption.title - let toggleSwitch = UISwitch() - toggleSwitch.isOn = toggleOption.binding - toggleSwitch.tag = indexPath.row - toggleSwitch.addTarget(self, action: #selector(toggleOptionsSwitches(_:)), for: .valueChanged) - cell.accessoryView = toggleSwitch - } - - return cell - - } + cell.accessoryType = .none + cell.selectionStyle = .gray + + let cellText = tableData[indexPath.section][indexPath.row] + cell.textLabel?.text = cellText + + switch cellText { + case String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_APPEARENCE"): + let forceLightDarkAppearance = TweakLibraryViewCell() + forceLightDarkAppearance.selectionStyle = .none + forceLightDarkAppearance.configureSegmentedControl( + with: mainOptions.mainOptions.forceLightDarkAppearenceString, + selectedIndex: 0 + ) + forceLightDarkAppearance.segmentedControl.addTarget(self, action: #selector(forceLightDarkAppearanceDidChange(_:)), for: .valueChanged) + + return forceLightDarkAppearance + case String.localized("APP_SIGNING_INPUT_VIEW_CONTROLLER_SECTION_TITLE_MINIMUM_APP_VERSION"): + let forceMinimumVersion = TweakLibraryViewCell() + forceMinimumVersion.selectionStyle = .none + forceMinimumVersion.configureSegmentedControl( + with: mainOptions.mainOptions.forceMinimumVersionString, + selectedIndex: 0 + ) + forceMinimumVersion.segmentedControl.addTarget(self, action: #selector(forceMinimumVersionDidChange(_:)), for: .valueChanged) + + return forceMinimumVersion + default: + break + } + + if indexPath.section == 2 { + let toggleOption = toggleOptions[indexPath.row] + cell.textLabel?.text = toggleOption.title + let toggleSwitch = UISwitch() + toggleSwitch.isOn = toggleOption.binding + toggleSwitch.tag = indexPath.row + toggleSwitch.addTarget(self, action: #selector(toggleOptionsSwitches(_:)), for: .valueChanged) + cell.accessoryView = toggleSwitch + } + + return cell + } } extension SigningsAdvancedViewController { - @objc private func forceLightDarkAppearenceDidChange(_ sender: UISegmentedControl) { - signingDataWrapper.signingOptions.forceLightDarkAppearence = - mainOptions.mainOptions.forceLightDarkAppearenceString[sender.selectedSegmentIndex] - } - - @objc private func forceMinimumVersionDidChange(_ sender: UISegmentedControl) { - signingDataWrapper.signingOptions.forceMinimumVersion = - mainOptions.mainOptions.forceMinimumVersionString[sender.selectedSegmentIndex] - } - - @objc func toggleOptionsSwitches(_ sender: UISwitch) { - switch sender.tag { - case 0: - signingDataWrapper.signingOptions.removePlugins = sender.isOn - case 1: - signingDataWrapper.signingOptions.forceFileSharing = sender.isOn - case 2: - signingDataWrapper.signingOptions.removeSupportedDevices = sender.isOn - case 3: - signingDataWrapper.signingOptions.removeURLScheme = sender.isOn - case 4: - signingDataWrapper.signingOptions.forceProMotion = sender.isOn - case 5: - signingDataWrapper.signingOptions.forceForceFullScreen = sender.isOn - case 6: - signingDataWrapper.signingOptions.forceiTunesFileSharing = sender.isOn - case 7: - signingDataWrapper.signingOptions.forceTryToLocalize = sender.isOn - case 8: - signingDataWrapper.signingOptions.removeProvisioningFile = sender.isOn - case 9: - signingDataWrapper.signingOptions.removeWatchPlaceHolder = sender.isOn - default: - break - } - } -} + @objc private func forceLightDarkAppearanceDidChange(_ sender: UISegmentedControl) { + signingDataWrapper.signingOptions.forceLightDarkAppearence = + mainOptions.mainOptions.forceLightDarkAppearenceString[sender.selectedSegmentIndex] + } + + @objc private func forceMinimumVersionDidChange(_ sender: UISegmentedControl) { + signingDataWrapper.signingOptions.forceMinimumVersion = + mainOptions.mainOptions.forceMinimumVersionString[sender.selectedSegmentIndex] + } + + @objc func toggleOptionsSwitches(_ sender: UISwitch) { + switch sender.tag { + case 0: + signingDataWrapper.signingOptions.removePlugins = sender.isOn + case 1: + signingDataWrapper.signingOptions.forceFileSharing = sender.isOn + case 2: + signingDataWrapper.signingOptions.removeSupportedDevices = sender.isOn + case 3: + signingDataWrapper.signingOptions.removeURLScheme = sender.isOn + case 4: + signingDataWrapper.signingOptions.forceProMotion = sender.isOn + case 5: + signingDataWrapper.signingOptions.forceForceFullScreen = sender.isOn + case 6: + signingDataWrapper.signingOptions.forceiTunesFileSharing = sender.isOn + case 7: + signingDataWrapper.signingOptions.forceTryToLocalize = sender.isOn + case 8: + signingDataWrapper.signingOptions.removeProvisioningFile = sender.isOn + case 9: + signingDataWrapper.signingOptions.removeWatchPlaceHolder = sender.isOn + default: + break + } + } +} \ No newline at end of file From eed1b7ea0a3782af5e23913875cefd706cdf12aa Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 18:10:05 -0400 Subject: [PATCH 336/391] Update SigningsAdvancedViewController.swift --- .../SigningsAdvancedViewController.swift | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsAdvancedViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsAdvancedViewController.swift index 67a5789c..78eaa42a 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsAdvancedViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsAdvancedViewController.swift @@ -1,11 +1,17 @@ import UIKit -class SigningsAdvancedViewController: FRSITableViewController { +class SigningsAdvancedViewController: UITableViewController { private var toggleOptions: [TogglesOption] + private var signingDataWrapper: SigningDataWrapper + private var mainOptions: SigningMainDataWrapper + private var tableData: [[String]] = [] + private var sectionTitles: [String] = [] - override init(signingDataWrapper: SigningDataWrapper, mainOptions: SigningMainDataWrapper) { + init(signingDataWrapper: SigningDataWrapper, mainOptions: SigningMainDataWrapper) { + self.signingDataWrapper = signingDataWrapper + self.mainOptions = mainOptions self.toggleOptions = feather.toggleOptions(signingDataWrapper: signingDataWrapper) - super.init(signingDataWrapper: signingDataWrapper, mainOptions: mainOptions) + super.init(style: .insetGrouped) } required init?(coder: NSCoder) { @@ -33,6 +39,14 @@ class SigningsAdvancedViewController: FRSITableViewController { } extension SigningsAdvancedViewController { + override func numberOfSections(in tableView: UITableView) -> Int { + return tableData.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return tableData[section].count + } + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let reuseIdentifier = "Cell" let cell = UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifier) From 54e98911b4ebecede3afaa07d8a47b968929c84c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 18:34:05 -0400 Subject: [PATCH 337/391] Update SigningsViewController.swift --- .../Signing/SigningViewController/SigningsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift index 0783501b..fde3d0b2 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift @@ -332,7 +332,7 @@ extension SigningsViewController: UITableViewDataSource, UITableViewDelegate { cell.textLabel?.textColor = .secondaryLabel cell.selectionStyle = .none } - case "Change Certificate", String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), String.localized("APP_SIGNI[...] + case "Change Certificate", String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_ADD_TWEAKS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_MODIFY_DYLIBS"), String.localized("APP_SIGNING_VIEW_CONTROLLER_CELL_PROPERTIES"): cell.accessoryType = .disclosureIndicator default: break From a3a2357cd7fc52d762745f15ab30d513a90d5837 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 18:38:05 -0400 Subject: [PATCH 338/391] Update SigningsViewController.swift --- .../Signing/SigningViewController/SigningsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift index fde3d0b2..81923195 100644 --- a/iOS/Views/Signing/SigningViewController/SigningsViewController.swift +++ b/iOS/Views/Signing/SigningViewController/SigningsViewController.swift @@ -259,7 +259,7 @@ class SigningsViewController: UIViewController { { result in switch result { case .success(let (signedPath, signedApp)): - self.appsViewController?.fetchSources() + self.appsViewController?.fetchSources() // Changed to 'public' in LibraryViewController self.appsViewController?.tableView.reloadData() Debug.shared.log(message: signedPath.path) if self.signingDataWrapper.signingOptions.installAfterSigned { From 5f28688f56a704993804f20fcbe723507e4019bf Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 18:41:59 -0400 Subject: [PATCH 339/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index a783f141..f7e8108f 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -205,7 +205,7 @@ extension LibraryViewController: UISearchResultsUpdating { filterContentForSearchText(searchController.searchBar.text!) } - fileprivate func fetchSources() { + public func fetchSources() { // Fetch Signed Apps let signedAppsFetchRequest: NSFetchRequest = SignedApps.fetchRequest() do { From c115368e28684f13361fb62fd6f98e3d7013e8b4 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 18:43:48 -0400 Subject: [PATCH 340/391] Update AppsTableViewCell.swift --- iOS/Views/Apps/AppsTableViewCell.swift | 349 ++++++++++++------------- 1 file changed, 169 insertions(+), 180 deletions(-) diff --git a/iOS/Views/Apps/AppsTableViewCell.swift b/iOS/Views/Apps/AppsTableViewCell.swift index af590368..c4112e9f 100644 --- a/iOS/Views/Apps/AppsTableViewCell.swift +++ b/iOS/Views/Apps/AppsTableViewCell.swift @@ -1,200 +1,189 @@ -// -// AppsTableViewCell.swift -// feather -// -// Created by samara on 7/1/24. -// Copyright (c) 2024 Samara M (khcrysalis) -// - import Foundation import UIKit import CoreData class AppsTableViewCell: UITableViewCell { - let nameLabel: UILabel = { - let label = UILabel() - label.font = UIFont.boldSystemFont(ofSize: 17) - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() - - let versionLabel: UILabel = { - let label = UILabel() - label.font = UIFont.systemFont(ofSize: 13) - label.textColor = .secondaryLabel - label.numberOfLines = 1 - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() - - let detailLabel: UILabel = { - let label = UILabel() - label.font = UIFont.systemFont(ofSize: 11) - label.textColor = .secondaryLabel - label.numberOfLines = 1 - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() - - private let pillsStackView: UIStackView = { - let stackView = UIStackView() - stackView.axis = .horizontal - stackView.spacing = 10 - stackView.distribution = .fillEqually - stackView.alignment = .leading - stackView.translatesAutoresizingMaskIntoConstraints = false - return stackView - }() - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupViews() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupViews() { - contentView.addSubview(nameLabel) - contentView.addSubview(versionLabel) - contentView.addSubview(pillsStackView) - imageView?.translatesAutoresizingMaskIntoConstraints = true + let nameLabel: UILabel = { + let label = UILabel() + label.font = UIFont.boldSystemFont(ofSize: 17) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + let versionLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 13) + label.textColor = .secondaryLabel + label.numberOfLines = 1 + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + let detailLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 11) + label.textColor = .secondaryLabel + label.numberOfLines = 1 + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let pillsStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 10 + stackView.distribution = .fillEqually + stackView.alignment = .leading + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() - NSLayoutConstraint.activate([ - - - nameLabel.leadingAnchor.constraint(equalTo: imageView!.trailingAnchor, constant: 15), - nameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), - - versionLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor), - versionLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 4), - versionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20), - versionLabel.bottomAnchor.constraint(equalTo: pillsStackView.topAnchor, constant: -10), - - pillsStackView.leadingAnchor.constraint(equalTo: imageView!.trailingAnchor, constant: 15), - pillsStackView.topAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: 10), - pillsStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15), - pillsStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10), - ]) - } + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { + contentView.addSubview(nameLabel) + contentView.addSubview(versionLabel) + contentView.addSubview(pillsStackView) + imageView?.translatesAutoresizingMaskIntoConstraints = true + NSLayoutConstraint.activate([ + nameLabel.leadingAnchor.constraint(equalTo: imageView!.trailingAnchor, constant: 15), + nameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), + + versionLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor), + versionLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 4), + versionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20), + versionLabel.bottomAnchor.constraint(equalTo: pillsStackView.topAnchor, constant: -10), + + pillsStackView.leadingAnchor.constraint(equalTo: imageView!.trailingAnchor, constant: 15), + pillsStackView.topAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: 10), + pillsStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15), + pillsStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10), + ]) + } - override func layoutSubviews() { - super.layoutSubviews() - } - - func configure(with app: NSManagedObject, filePath: URL) { - var appname = "" - if let name = app.value(forKey: "name") as? String { - appname += name - } - - var desc = "" - if let version = app.value(forKey: "version") as? String { - desc += version - } - desc += " • " - if let bundleIdentifier = app.value(forKey: "bundleidentifier") as? String { - desc += bundleIdentifier - - if bundleIdentifier.hasSuffix("Beta") { - appname += " (Beta)" - } - } - - pillsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - - if FileManager.default.fileExists(atPath: filePath.path) { - if let timeToLive: Date = getValue(forKey: "timeToLive", from: app) { - let currentDate = Date() - let calendar = Calendar.current - let components = calendar.dateComponents([.day], from: currentDate, to: timeToLive) - - let daysLeft = components.day ?? 0 - let expirationText = daysLeft < 0 ? "Expired" : "\(daysLeft) days left" - - let p1 = PillView(text: expirationText, backgroundColor: daysLeft < 0 ? .systemRed : .systemGreen, iconName: daysLeft < 0 ? "xmark" : "timer") - pillsStackView.addArrangedSubview(p1) - } - - if app.entity.name == "SignedApps", - let hasUpdate = app.value(forKey: "hasUpdate") as? Bool, - hasUpdate, - let currentVersion = app.value(forKey: "version") as? String, - let updateVersion = app.value(forKey: "updateVersion") as? String { - let updateText = "\(currentVersion) → \(updateVersion)" - let updatePill = PillView(text: updateText, backgroundColor: .systemPurple, iconName: "arrow.up.circle") - pillsStackView.addArrangedSubview(updatePill) - } else if let name: String = getValue(forKey: "teamName", from: app) { - let p = PillView(text: name, backgroundColor: .systemGray, iconName: "person") - pillsStackView.addArrangedSubview(p) - } - } else { - let p = PillView(text: "File Has Been Deleted", backgroundColor: .systemRed, iconName: "trash") - pillsStackView.addArrangedSubview(p) - } - - if let osu: String = getValue(forKey: "oSU", from: app) { - let p = PillView(text: osu, backgroundColor: .systemGray, iconName: "questionmark.app.dashed") - pillsStackView.addArrangedSubview(p) - } - - nameLabel.text = appname - versionLabel.text = desc - } + override func layoutSubviews() { + super.layoutSubviews() + } + + func configure(with app: NSManagedObject, filePath: URL) { + var appname = "" + if let name = app.value(forKey: "name") as? String { + appname += name + } + + var desc = "" + if let version = app.value(forKey: "version") as? String { + desc += version + } + desc += " • " + if let bundleIdentifier = app.value(forKey: "bundleidentifier") as? String { + desc += bundleIdentifier + + if bundleIdentifier.hasSuffix("Beta") { + appname += " (Beta)" + } + } + + pillsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } + + if FileManager.default.fileExists(atPath: filePath.path) { + if let timeToLive: Date = getValue(forKey: "timeToLive", from: app) { + let currentDate = Date() + let calendar = Calendar.current + let components = calendar.dateComponents([.day], from: currentDate, to: timeToLive) + + let daysLeft = components.day ?? 0 + let expirationText = daysLeft < 0 ? "Expired" : "\(daysLeft) days left" + + let p1 = PillView(text: expirationText, backgroundColor: daysLeft < 0 ? .systemRed : .systemGreen, iconName: daysLeft < 0 ? "xmark" : "timer") + pillsStackView.addArrangedSubview(p1) + } + + if app.entity.name == "SignedApps", + let hasUpdate = app.value(forKey: "hasUpdate") as? Bool, + hasUpdate, + let currentVersion = app.value(forKey: "version") as? String, + let updateVersion = app.value(forKey: "updateVersion") as? String { + let updateText = "\(currentVersion) → \(updateVersion)" + let updatePill = PillView(text: updateText, backgroundColor: .systemPurple, iconName: "arrow.up.circle") + pillsStackView.addArrangedSubview(updatePill) + } else if let name: String = getValue(forKey: "teamName", from: app) { + let p = PillView(text: name, backgroundColor: .systemGray, iconName: "person") + pillsStackView.addArrangedSubview(p) + } + } else { + let p = PillView(text: "File Has Been Deleted", backgroundColor: .systemRed, iconName: "trash") + pillsStackView.addArrangedSubview(p) + } + + if let osu: String = getValue(forKey: "oSU", from: app) { + let p = PillView(text: osu, backgroundColor: .systemGray, iconName: "questionmark.app.dashed") + pillsStackView.addArrangedSubview(p) + } + + nameLabel.text = appname + versionLabel.text = desc + } } func getValue(forKey key: String, from app: NSManagedObject) -> T? { - guard let attributeType = app.entity.attributesByName[key]?.attributeType else { - return nil - } - - switch attributeType { - case .stringAttributeType: - return app.value(forKey: key) as? T - case .dateAttributeType: - return app.value(forKey: key) as? T - default: - return nil - } + guard let attributeType = app.entity.attributesByName[key]?.attributeType else { + return nil + } + + switch attributeType { + case .stringAttributeType: + return app.value(forKey: key) as? T + case .dateAttributeType: + return app.value(forKey: key) as? T + default: + return nil + } } class BadgeView: UIView { - private let badgeLabel = UILabel() + private let badgeLabel = UILabel() - override init(frame: CGRect) { - super.init(frame: frame) - setupView() - } + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } - required init?(coder: NSCoder) { - super.init(coder: coder) - setupView() - } + required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + } - private func setupView() { - badgeLabel.text = "BETA" - badgeLabel.textColor = .label - badgeLabel.textAlignment = .center - badgeLabel.backgroundColor = .systemYellow.withAlphaComponent(0.2) - badgeLabel.font = .boldSystemFont(ofSize: 12) + private func setupView() { + badgeLabel.text = "BETA" + badgeLabel.textColor = .label + badgeLabel.textAlignment = .center + badgeLabel.backgroundColor = .systemYellow.withAlphaComponent(0.2) + badgeLabel.font = .boldSystemFont(ofSize: 12) - badgeLabel.translatesAutoresizingMaskIntoConstraints = false - addSubview(badgeLabel) + badgeLabel.translatesAutoresizingMaskIntoConstraints = false + addSubview(badgeLabel) - NSLayoutConstraint.activate([ - badgeLabel.centerYAnchor.constraint(equalTo: centerYAnchor), - badgeLabel.centerXAnchor.constraint(equalTo: centerXAnchor), - badgeLabel.widthAnchor.constraint(equalToConstant: 50), - badgeLabel.heightAnchor.constraint(equalToConstant: 20) - ]) - - badgeLabel.layer.cornerRadius = 10 - badgeLabel.layer.cornerCurve = .continuous - badgeLabel.clipsToBounds = true - badgeLabel.layer.borderColor = UIColor.systemYellow.withAlphaComponent(0.3).cgColor - badgeLabel.layer.borderWidth = 1.0 - } -} + NSLayoutConstraint.activate([ + badgeLabel.centerYAnchor.constraint(equalTo: centerYAnchor), + badgeLabel.centerXAnchor.constraint(equalTo: centerXAnchor), + badgeLabel.widthAnchor.constraint(equalToConstant: 50), + badgeLabel.heightAnchor.constraint(equalToConstant: 20) + ]) + + badgeLabel.layer.cornerRadius = 10 + badgeLabel.layer.cornerCurve = .continuous + badgeLabel.clipsToBounds = true + badgeLabel.layer.borderColor = UIColor.systemYellow.withAlphaComponent(0.3).cgColor + badgeLabel.layer.borderWidth = 1.0 + } +} \ No newline at end of file From 9df429c1e2d017196e3de37defa751d83b527741 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 18:55:27 -0400 Subject: [PATCH 341/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 47 +++-------------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 20809947..63e277c4 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -56,7 +56,7 @@ class HomeViewFileHandlers { func renameFile(viewController: FileHandlingDelegate, fileURL: URL, newName: String, completion: @escaping (Result) -> Void) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { + DispatchQueue.global().async(execute: { do { try self.fileManager.moveItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { @@ -71,12 +71,12 @@ class HomeViewFileHandlers { completion(.failure(error)) } } - } + }) } func deleteFile(viewController: FileHandlingDelegate, fileURL: URL, completion: @escaping (Result) -> Void) { viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { + DispatchQueue.global().async(execute: { do { try self.fileManager.removeItem(at: fileURL) DispatchQueue.main.async { @@ -91,13 +91,13 @@ class HomeViewFileHandlers { completion(.failure(error)) } } - } + }) } func unzipFile(viewController: FileHandlingDelegate, fileURL: URL, destinationName: String, progressHandler: ((Double) -> Void)? = nil, completion: @escaping (Result) -> Void) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(destinationName) viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { + DispatchQueue.global().async(execute: { do { try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: { progress in if let progressHandler = progressHandler { @@ -116,46 +116,11 @@ class HomeViewFileHandlers { completion(.failure(error)) } } - } + }) } func shareFile(viewController: UIViewController, fileURL: URL) { let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) viewController.present(activityController, animated: true, completion: nil) } - - // Remove Process Execution Helper - // private func executeProcess(executableURL: URL, arguments: [String]) throws -> String { - // let command = "\(executableURL.path) \(arguments.joined(separator: " "))" - // if let output = ProcessUtility.shared.executeShellCommand(command) { - // return output - // } else { - // throw NSError(domain: "Process", code: -1, userInfo: [NSLocalizedDescriptionKey: "Process failed"]) - // } - // } - - // Remove listDylibs method - // func listDylibs(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { - // let otoolURL = URL(fileURLWithPath: "/usr/bin/otool") - // let arguments = ["-L", filePath] - // DispatchQueue.global().async { - // do { - // let output = try self.executeProcess(executableURL: otoolURL, arguments: arguments) - // let lines = output.components(separatedBy: .newlines) - // let dylibs = lines.filter { $0.hasPrefix("\t") }.compactMap { line in - // line.components(separatedBy: "(").first?.trimmingCharacters(in: .whitespaces) - // } - // DispatchQueue.main.async { completion(.success(dylibs)) } - // } catch { - // DispatchQueue.main.async { completion(.failure(error)) } - // } - // } - // } - - // Remove listDylibsFromApp method - // func listDylibsFromApp(filePath: String, completion: @escaping (Result<[String], Error>) -> Void) { - // listDylibs(filePath: filePath, completion: completion) - // } - - // Add other functions here, using DispatchQueue.global().async for stability. } \ No newline at end of file From 1f376187cbba93b6c4133fb70e50ce52cd64274b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 19:23:53 -0400 Subject: [PATCH 342/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 85 +++++++++++----------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index f7e8108f..9f1d4642 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -47,9 +47,10 @@ class LibraryViewController: UITableViewController { @objc func handleInstallNotification(_ notification: Notification) { if let userInfo = notification.userInfo, let app = userInfo["app"] as? DownloadedApps { - - DispatchQueue.main.async { - self.startInstallProcess(meow: app, filePath: app.filePath ?? "") + if let filePath = app.value(forKey: "filePath") as? String { + DispatchQueue.main.async { + self.startInstallProcess(meow: app, filePath: filePath) + } } } } @@ -89,11 +90,8 @@ class LibraryViewController: UITableViewController { : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) if let app = app { - cell.configure( - image: UIImage(systemName: "questionmark.app.dashed"), // Placeholder image - title: app.name ?? "Unknown App", - subtitle: app.bundleIdentifier ?? "Unknown Bundle ID" - ) + let filePath = app.value(forKey: "filePath") as? String ?? "" + cell.configure(with: app, filePath: URL(fileURLWithPath: filePath)) } return cell @@ -117,14 +115,14 @@ class LibraryViewController: UITableViewController { // Handle Signed Apps action let button1 = PopupViewController.PopupButton( title: "Sign", - style: .default - ) { - self.startSigning(meow: app) - } + style: .default, + handler: { + self.startSigning(meow: app) + } + ) popupVC.configureButtons([button1]) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { presentationController.detents = [ .medium() @@ -136,44 +134,44 @@ class LibraryViewController: UITableViewController { } else { // Handle Downloaded Apps actions - let button1 = PopupViewController.PopupButton( title: "Install", - style: .default - ) { - - if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { - - let alertController = UIAlertController( - title: "Install App", - message: "Are you sure you want to install this app?", - preferredStyle: .alert - ) - - let confirmAction = UIAlertAction( - title: "Install", - style: .default - ) { _ in - self.startInstallProcess(meow: app, filePath: filePath.path) + style: .default, + handler: { + if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { + let alertController = UIAlertController( + title: "Install App", + message: "Are you sure you want to install this app?", + preferredStyle: .alert + ) + + let confirmAction = UIAlertAction( + title: "Install", + style: .default, + handler: { _ in + self.startInstallProcess(meow: app, filePath: filePath.path) + } + ) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true, completion: nil) } - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - self.present(alertController, animated: true, completion: nil) } - } + ) let button2 = PopupViewController.PopupButton( title: "Share", - style: .default - ) { - if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { - self.shareFile(meow: app, filePath: filePath.path) + style: .default, + handler: { + if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { + self.shareFile(meow: app, filePath: filePath.path) + } } - } + ) popupVC.configureButtons([button1, button2]) @@ -237,7 +235,6 @@ extension LibraryViewController: UISearchResultsUpdating { } fileprivate func filterContentForSearchText(_ searchText: String, scope: String = "All") { - filteredSignedApps = signedApps?.filter { app in return app.name?.lowercased().contains(searchText.lowercased()) ?? false } ?? [] From 160af035493eebfaef9dc67bd8158d267130c225 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 19:24:24 -0400 Subject: [PATCH 343/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 63e277c4..dc561f6c 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -56,7 +56,7 @@ class HomeViewFileHandlers { func renameFile(viewController: FileHandlingDelegate, fileURL: URL, newName: String, completion: @escaping (Result) -> Void) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) viewController.activityIndicator.startAnimating() - DispatchQueue.global().async(execute: { + DispatchQueue.global().async { do { try self.fileManager.moveItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { @@ -71,12 +71,12 @@ class HomeViewFileHandlers { completion(.failure(error)) } } - }) + } } func deleteFile(viewController: FileHandlingDelegate, fileURL: URL, completion: @escaping (Result) -> Void) { viewController.activityIndicator.startAnimating() - DispatchQueue.global().async(execute: { + DispatchQueue.global().async { do { try self.fileManager.removeItem(at: fileURL) DispatchQueue.main.async { @@ -91,13 +91,13 @@ class HomeViewFileHandlers { completion(.failure(error)) } } - }) + } } func unzipFile(viewController: FileHandlingDelegate, fileURL: URL, destinationName: String, progressHandler: ((Double) -> Void)? = nil, completion: @escaping (Result) -> Void) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(destinationName) viewController.activityIndicator.startAnimating() - DispatchQueue.global().async(execute: { + DispatchQueue.global().async { do { try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: { progress in if let progressHandler = progressHandler { @@ -116,7 +116,7 @@ class HomeViewFileHandlers { completion(.failure(error)) } } - }) + } } func shareFile(viewController: UIViewController, fileURL: URL) { From c459d8d9e4a33011e7a2657beb84cc0e35707105 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 19:49:37 -0400 Subject: [PATCH 344/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 9f1d4642..54dee1a4 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -1,6 +1,4 @@ -import Foundation -import CoreData -import UniformTypeIdentifiers +import UIKit class LibraryViewController: UITableViewController { var signedApps: [SignedApps]? @@ -42,6 +40,9 @@ class LibraryViewController: UITableViewController { name: Notification.Name("InstallDownloadedApp"), object: nil ) + + // Setting background color using hex initializer + self.view.backgroundColor = UIColor(hex: "#F0F0F0") // Example hex color } @objc func handleInstallNotification(_ notification: Notification) { @@ -158,6 +159,9 @@ class LibraryViewController: UITableViewController { alertController.addAction(confirmAction) alertController.addAction(cancelAction) + // Example of updating the alert view colors + alertController.view.tintColor = UIColor(hex: "#FF5733") // Example hex color + self.present(alertController, animated: true, completion: nil) } } From 228a737dd0d063b793b2c5447a5ae79b5a84ba1f Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 20:01:45 -0400 Subject: [PATCH 345/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index dc561f6c..ce149212 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -101,7 +101,9 @@ class HomeViewFileHandlers { do { try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: { progress in if let progressHandler = progressHandler { - progressHandler(Double(progress.completedUnitCount) / Double(progress.totalUnitCount)) + let progressValue = Double(progress.completedUnitCount) / Double(progress.totalUnitCount) + print("Progress: \(progressValue)") + progressHandler(progressValue) } }) DispatchQueue.main.async { From 5e888c60cd95e7238f6d7434d63881105f290228 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 20:22:36 -0400 Subject: [PATCH 346/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 54dee1a4..62231e3d 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -1,4 +1,5 @@ import UIKit +import CoreData class LibraryViewController: UITableViewController { var signedApps: [SignedApps]? From fa6f345a70f84ce91df27414e0070c4d40e32403 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 20:41:56 -0400 Subject: [PATCH 347/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 692 +++++++++++++++++---- 1 file changed, 556 insertions(+), 136 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 62231e3d..270d99a3 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -1,5 +1,6 @@ import UIKit import CoreData +import UniformTypeIdentifiers class LibraryViewController: UITableViewController { var signedApps: [SignedApps]? @@ -41,213 +42,632 @@ class LibraryViewController: UITableViewController { name: Notification.Name("InstallDownloadedApp"), object: nil ) - - // Setting background color using hex initializer - self.view.backgroundColor = UIColor(hex: "#F0F0F0") // Example hex color } - @objc func handleInstallNotification(_ notification: Notification) { - if let userInfo = notification.userInfo, - let app = userInfo["app"] as? DownloadedApps { - if let filePath = app.value(forKey: "filePath") as? String { - DispatchQueue.main.async { - self.startInstallProcess(meow: app, filePath: filePath) - } + @objc private func handleInstallNotification(_ notification: Notification) { + guard let downloadedApp = notification.userInfo?["downloadedApp"] as? DownloadedApps else { return } + + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + signingDataWrapper.signingOptions.installAfterSigned = true + + let ap = SigningsViewController( + signingDataWrapper: signingDataWrapper, + application: downloadedApp, + appsViewController: self + ) + + ap.signingCompletionHandler = { success in + if success { + Debug.shared.log(message: "Signing completed successfully", type: .success) } } + + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + + present(navigationController, animated: true) } - fileprivate func setupNavigation() { - navigationItem.title = "Library" - navigationController?.navigationBar.prefersLargeTitles = true + deinit { + NotificationCenter.default.removeObserver(self, name: Notification.Name("lfetch"), object: nil) + NotificationCenter.default.removeObserver(self, name: Notification.Name("InstallDownloadedApp"), object: nil) } - fileprivate func setupSearchController() { - searchController = UISearchController(searchResultsController: nil) - searchController.searchResultsUpdater = self - searchController.obscuresBackgroundDuringPresentation = false - searchController.searchBar.placeholder = "Search Apps..." - navigationItem.searchController = searchController - definesPresentationContext = true + fileprivate func setupNavigation() { + self.navigationController?.navigationBar.prefersLargeTitles = true + self.title = String.localized("TAB_LIBRARY") } - // MARK: - Table view data source + private func handleAppUpdate(for signedApp: SignedApps) { + guard let sourceURL = signedApp.originalSourceURL else { + Debug.shared.log(message: "Missing update version or source URL", type: .error) + return + } + + Debug.shared.log(message: "Fetching update from source: \(sourceURL.absoluteString)", type: .info) + + present(loaderAlert!, animated: true) + + // Create mock source if in debug mode + if isDebugMode { + let mockSource = SourceRefreshOperation() + mockSource.createMockSource { mockSourceData in + if let sourceData = mockSourceData { + self.handleSourceData(sourceData, for: signedApp) + } else { + Debug.shared.log(message: "Failed to create mock source", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + } + } + } else { + // Normal source fetch + SourceGET().downloadURL(from: sourceURL) { [weak self] result in + guard let self = self else { return } + + switch result { + case .success((let data, _)): + if case .success(let sourceData) = SourceGET().parse(data: data) { + self.handleSourceData(sourceData, for: signedApp) + } else { + Debug.shared.log(message: "Failed to parse source data", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + } + case .failure(let error): + Debug.shared.log(message: "Failed to fetch source: \(error)", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + } + } + } + } - override func numberOfSections(in tableView: UITableView) -> Int { - return 2 + private func handleSourceData(_ sourceData: SourcesData, for signedApp: SignedApps) { + guard let bundleId = signedApp.bundleidentifier, + let updateVersion = signedApp.updateVersion, + let app = sourceData.apps.first(where: { $0.bundleIdentifier == bundleId }), + let versions = app.versions else { + Debug.shared.log(message: "Failed to find app in source", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + return + } + + // Look for the version that matches our update version + for version in versions { + if version.version == updateVersion { + // Found the matching version + Debug.shared.log(message: "Found matching version: \(version.version)", type: .info) + + let uuid = UUID().uuidString + + DispatchQueue.global(qos: .background).async { + do { + let tempDirectory = FileManager.default.temporaryDirectory + let destinationURL = tempDirectory.appendingPathComponent("\(uuid).ipa") + + // Download the file + if let data = try? Data(contentsOf: version.downloadURL) { + try data.write(to: destinationURL) + + let dl = AppDownload() + try handleIPAFile(destinationURL: destinationURL, uuid: uuid, dl: dl) + + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) { + // Force Sign & Install + let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + signingDataWrapper.signingOptions.installAfterSigned = true + + // Store the original signed app for deletion after update + let originalSignedApp = signedApp + + let ap = SigningsViewController( + signingDataWrapper: signingDataWrapper, + application: downloadedApp, + appsViewController: self + ) + + // Add completion handler to delete the original app after successful signing + ap.signingCompletionHandler = { [weak self] success in + if success { + CoreDataManager.shared.deleteAllSignedAppContent(for: originalSignedApp) + self?.fetchSources() + self?.tableView.reloadData() + } + } + + let navigationController = UINavigationController(rootViewController: ap) + + navigationController.shouldPresentFullScreen() + + self.present(navigationController, animated: true) + } + } + } + } + } catch { + Debug.shared.log(message: "Failed to handle update: \(error)", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } + } + } + return + } + } + + Debug.shared.log(message: "Could not find version \(updateVersion) in source", type: .error) + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + } } + private var isDebugMode: Bool { + var isDebug = false + assert({ + isDebug = true + return true + }()) + return isDebug + } +} + +extension LibraryViewController { + override func numberOfSections(in tableView: UITableView) -> Int { return 2 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if isFiltering() { - return section == 0 ? filteredSignedApps.count : filteredDownloadedApps.count + switch section { + case 0: + return isFiltering ? filteredSignedApps.count : signedApps?.count ?? 0 + case 1: + return isFiltering ? filteredDownloadedApps.count : downloadedApps?.count ?? 0 + default: + return 0 + } + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + switch section { + case 0: + let headerWithButton = GroupedSectionHeader( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS"), + subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL", arguments: String(signedApps?.count ?? 0)), + buttonTitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_BUTTON_IMPORT"), + buttonAction: { + self.startImporting() + }) + return headerWithButton + case 1: + + let headerWithButton = GroupedSectionHeader( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS"), + subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL", arguments: String(downloadedApps?.count ?? 0)) + ) + + return headerWithButton + default: + return nil } - return section == 0 ? signedApps?.count ?? 0 : downloadedApps?.count ?? 0 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "RoundedBackgroundCell", for: indexPath) as! AppsTableViewCell + let cell = AppsTableViewCell(style: .subtitle, reuseIdentifier: "RoundedBackgroundCell") + cell.selectionStyle = .default + cell.accessoryType = .disclosureIndicator + cell.backgroundColor = .clear + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - let app = isFiltering() - ? (indexPath.section == 0 ? filteredSignedApps[indexPath.row] : filteredDownloadedApps[indexPath.row]) - : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) - if let app = app { - let filePath = app.value(forKey: "filePath") as? String ?? "" - cell.configure(with: app, filePath: URL(fileURLWithPath: filePath)) + if let iconURL = source!.value(forKey: "iconURL") as? String { + let imagePath = filePath!.appendingPathComponent(iconURL) + + if let image = CoreDataManager.shared.loadImage(from: imagePath) { + SectionIcons.sectionImage(to: cell, with: image) + } else { + SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) + } + } else { + SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) } + cell.configure(with: source!, filePath: filePath!) return cell } - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return section == 0 ? "Signed Apps" : "Downloaded Apps" - } - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - let app = isFiltering() - ? (indexPath.section == 0 ? filteredSignedApps[indexPath.row] : filteredDownloadedApps[indexPath.row]) - : (indexPath.section == 0 ? signedApps?[indexPath.row] : downloadedApps?[indexPath.row]) - - if let app = app { - popupVC = PopupViewController() - - if indexPath.section == 0 { - // Handle Signed Apps action - let button1 = PopupViewController.PopupButton( - title: "Sign", - style: .default, - handler: { - self.startSigning(meow: app) - } - ) + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: true) + let filePath2 = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: false) + let appName = "\((source!.value(forKey: "name") as? String ?? ""))" + switch indexPath.section { + case 0: + if FileManager.default.fileExists(atPath: filePath2!.path) { + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet - popupVC.configureButtons([button1]) + let hasUpdate = (source as? SignedApps)?.value(forKey: "hasUpdate") as? Bool ?? false + if let signedApp = source as? SignedApps, hasUpdate { + // Update available menu + let updateButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), + color: .tintColor.withAlphaComponent(0.9), + titleColor: .white + ) + updateButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + self.handleAppUpdate(for: signedApp) + } + } + + let clearButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE"), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + clearButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + CoreDataManager.shared.clearUpdateState(for: signedApp) + self.tableView.reloadRows(at: [indexPath], with: .none) + } + + popupVC.configureButtons([updateButton, clearButton]) + } else { + // Regular menu + let button1 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), + color: .tintColor.withAlphaComponent(0.9) + ) + button1.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + } + + let button4 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + button4.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + if let workspace = LSApplicationWorkspace.default() { + let success = workspace.openApplication(withBundleID: "\((source!.value(forKey: "bundleidentifier") as? String ?? ""))") + if !success { + Debug.shared.log(message: "Unable to open, do you have the app installed?", type: .warning) + } + } + } + + let button3 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + button3.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + if let cert = CoreDataManager.shared.getCurrentCertificate() { + self.present(self.loaderAlert!, animated: true) + + resignApp(certificate: cert, appPath: filePath2!) { success in + if success { + CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + Debug.shared.log(message: "Done action??") + self.tableView.reloadRows(at: [indexPath], with: .left) + } + } + } + } + } else { + let alert = UIAlertController( + title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), + message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) + self.present(alert, animated: true) + } + } + } + + let button2 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), + color: .quaternarySystemFill, + titleColor: .tintColor + ) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.shareFile(meow: source!, filePath: filePath?.path ?? "") + } + + popupVC.configureButtons([button1, button4, button3, button2]) + } + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: hasUpdate ? 150.0 : 270.0) if let presentationController = popupVC.presentationController as? UISheetPresentationController { presentationController.detents = [ + detent2, .medium() ] presentationController.prefersGrabberVisible = true } self.present(popupVC, animated: true) - } else { - // Handle Downloaded Apps actions - let button1 = PopupViewController.PopupButton( - title: "Install", - style: .default, - handler: { - if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { - let alertController = UIAlertController( - title: "Install App", - message: "Are you sure you want to install this app?", - preferredStyle: .alert - ) - - let confirmAction = UIAlertAction( - title: "Install", - style: .default, - handler: { _ in - self.startInstallProcess(meow: app, filePath: filePath.path) - } - ) - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - // Example of updating the alert view colors - alertController.view.tintColor = UIColor(hex: "#FF5733") // Example hex color - - self.present(alertController, animated: true, completion: nil) - } - } - ) + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } + case 1: + if FileManager.default.fileExists(atPath: filePath2!.path) { + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet + + let singingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let button1 = PopupViewControllerButton( + title: singingData.signingOptions.installAfterSigned + ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) + : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), + color: .tintColor.withAlphaComponent(0.9)) + button1.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startSigning(meow: source!) + } - let button2 = PopupViewController.PopupButton( - title: "Share", - style: .default, - handler: { - if let filePath = self.getApplicationFilePath(with: app, row: indexPath.row, section: indexPath.section) { - self.shareFile(meow: app, filePath: filePath.path) + let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: .quaternarySystemFill, titleColor: .tintColor) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + let alertController = UIAlertController( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), + message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), + preferredStyle: .alert + ) + + let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in + self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + } + + let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) + + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true, completion: nil) } - ) + } popupVC.configureButtons([button1, button2]) + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) if let presentationController = popupVC.presentationController as? UISheetPresentationController { presentationController.detents = [ + detent2, .medium(), - .large() + ] presentationController.prefersGrabberVisible = true } self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) } + default: + break } + + tableView.deselectRow(at: indexPath, animated: true) } - // Placeholder methods for undefined functions in this context - func getApplication(row: Int, section: Int) -> Any? { return nil } - func getApplicationFilePath(with source: Any, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { return nil } - func startInstallProcess(meow: Any, filePath: String) {} - func shareFile(meow: Any, filePath: String) {} - func startSigning(meow: Any) {} - @objc func afetch() {} - func presentLoader() -> UIAlertController? { return nil } -} - -extension LibraryViewController: UISearchResultsUpdating { - func updateSearchResults(for searchController: UISearchController) { - filterContentForSearchText(searchController.searchBar.text!) + @objc func startSigning(meow: NSManagedObject) { + if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) + } + } } - public func fetchSources() { - // Fetch Signed Apps - let signedAppsFetchRequest: NSFetchRequest = SignedApps.fetchRequest() - do { - signedApps = try CoreDataManager.shared.context.fetch(signedAppsFetchRequest) - } catch { - print("Failed to fetch signed apps: \(error)") - } + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) - // Fetch Downloaded Apps - let downloadedAppsFetchRequest: NSFetchRequest = DownloadedApps.fetchRequest() - do { - downloadedApps = try CoreDataManager.shared.context.fetch(downloadedAppsFetchRequest) - } catch { - print("Failed to fetch downloaded apps: \(error)") + let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break + } + completionHandler(true) } + deleteAction.backgroundColor = UIColor.red + let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) + configuration.performsFirstActionWithFullSwipe = true + + return configuration + } + + override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + + let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] + } + } + + self.present(navigationController, animated: true) + }), + + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") + } + } + }) + ]) + }) + return configuration + } +} + +extension LibraryViewController { + @objc func afetch() { self.fetchSources() } + + func fetchSources() { + signedApps = CoreDataManager.shared.getDatedSignedApps() + downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + DispatchQueue.main.async { - self.tableView.reloadData() - self.loaderAlert?.dismiss(animated: true, completion: nil) + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() + } } } - fileprivate func isFiltering() -> Bool { - return searchController.isActive && !searchBarIsEmpty() + func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { + if section == 0 { + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) + } + + if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) + } + return nil } - fileprivate func searchBarIsEmpty() -> Bool { - return searchController.searchBar.text?.isEmpty ?? true + func getApplication(row: Int, section: Int) -> NSManagedObject? { + if isFiltering { + if section == 0 { + if row < filteredSignedApps.count { + return filteredSignedApps[row] + } + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] + } + } + } else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] + } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] + } + } + } + return nil + } +} + +extension LibraryViewController: UISearchResultsUpdating { + func updateSearchResults(for searchController: UISearchController) { + let searchText = searchController.searchBar.text ?? "" + filterContentForSearchText(searchText) + tableView.reloadData() } - fileprivate func filterContentForSearchText(_ searchText: String, scope: String = "All") { + private func filterContentForSearchText(_ searchText: String) { + let lowercasedSearchText = searchText.lowercased() + filteredSignedApps = signedApps?.filter { app in - return app.name?.lowercased().contains(searchText.lowercased()) ?? false + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) } ?? [] - + filteredDownloadedApps = downloadedApps?.filter { app in - return app.name?.lowercased().contains(searchText.lowercased()) ?? false + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) } ?? [] - - tableView.reloadData() } +} + +extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { + func setupSearchController() { + searchController = UISearchController(searchResultsController: nil) + searchController.obscuresBackgroundDuringPresentation = false + searchController.hidesNavigationBarDuringPresentation = true + searchController.searchResultsUpdater = self + searchController.delegate = self + searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") + navigationItem.searchController = searchController + definesPresentationContext = true + navigationItem.hidesSearchBarWhenScrolling = false + } + + var isFiltering: Bool { + return searchController.isActive && !searchBarIsEmpty + } + + var searchBarIsEmpty: Bool { + return searchController.searchBar.text?.isEmpty ?? true + } +} + +/// https://stackoverflow.com/a/75310581 +func presentLoader() -> UIAlertController { + let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.isUserInteractionEnabled = false + activityIndicator.startAnimating() + + alert.view.addSubview(activityIndicator) + + NSLayoutConstraint.activate([ + alert.view.heightAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant: 95), + activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) + ]) + + return alert } \ No newline at end of file From fb11a43ce5e2d16fe58317c6a76e0df7e378721c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 21:34:50 -0400 Subject: [PATCH 348/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 92 +++++++++++++++++----- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 270d99a3..ef4378e4 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -1,6 +1,5 @@ import UIKit import CoreData -import UniformTypeIdentifiers class LibraryViewController: UITableViewController { var signedApps: [SignedApps]? @@ -299,8 +298,8 @@ extension LibraryViewController { // Update available menu let updateButton = PopupViewControllerButton( title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), - color: .tintColor.withAlphaComponent(0.9), - titleColor: .white + color: UIColor.tintColor.withAlphaComponent(0.9), + titleColor: UIColor.white ) updateButton.onTap = { [weak self] in guard let self = self else { return } @@ -311,8 +310,8 @@ extension LibraryViewController { let clearButton = PopupViewControllerButton( title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE"), - color: .quaternarySystemFill, - titleColor: .tintColor + color: UIColor.quaternarySystemFill, + titleColor: UIColor.tintColor ) clearButton.onTap = { [weak self] in guard let self = self else { return } @@ -326,7 +325,7 @@ extension LibraryViewController { // Regular menu let button1 = PopupViewControllerButton( title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), - color: .tintColor.withAlphaComponent(0.9) + color: UIColor.tintColor.withAlphaComponent(0.9) ) button1.onTap = { [weak self] in guard let self = self else { return } @@ -336,8 +335,8 @@ extension LibraryViewController { let button4 = PopupViewControllerButton( title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor + color: UIColor.quaternarySystemFill, + titleColor: UIColor.tintColor ) button4.onTap = { [weak self] in guard let self = self else { return } @@ -352,8 +351,8 @@ extension LibraryViewController { let button3 = PopupViewControllerButton( title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor + color: UIColor.quaternarySystemFill, + titleColor: UIColor.tintColor ) button3.onTap = { [weak self] in guard let self = self else { return } @@ -386,8 +385,8 @@ extension LibraryViewController { let button2 = PopupViewControllerButton( title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), - color: .quaternarySystemFill, - titleColor: .tintColor + color: UIColor.quaternarySystemFill, + titleColor: UIColor.tintColor ) button2.onTap = { [weak self] in guard let self = self else { return } @@ -420,14 +419,14 @@ extension LibraryViewController { title: singingData.signingOptions.installAfterSigned ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: .tintColor.withAlphaComponent(0.9)) + color: UIColor.tintColor.withAlphaComponent(0.9)) button1.onTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) self.startSigning(meow: source!) } - let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: .quaternarySystemFill, titleColor: .tintColor) + let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor) button2.onTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) { @@ -463,6 +462,61 @@ extension LibraryViewController { presentationController.prefersGrabberVisible = true } + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } + case 1: + if FileManager.default.fileExists(atPath: filePath2!.path) { + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet + + let signingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let button1 = PopupViewControllerButton( + title: signingData.signingOptions.installAfterSigned + ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) + : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), + color: UIColor.tintColor.withAlphaComponent(0.9)) + button1.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startSigning(meow: source!) + } + + let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + let alertController = UIAlertController( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), + message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), + preferredStyle: .alert + ) + + let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in + self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + } + + let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) + + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true, completion: nil) + } + } + + popupVC.configureButtons([button1, button2]) + + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium() + ] + presentationController.prefersGrabberVisible = true + } + self.present(popupVC, animated: true) } else { Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) @@ -471,7 +525,7 @@ extension LibraryViewController { break } - tableView.deselectRow(at: indexPath, animated: true) + tableView.deselectRow(at: indexPath, animated: true) } @objc func startSigning(meow: NSManagedObject) { @@ -518,7 +572,7 @@ extension LibraryViewController { let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DATEILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in let viewController = AppsInformationViewController() viewController.source = source viewController.filePath = filePath @@ -533,7 +587,7 @@ extension LibraryViewController { self.present(navigationController, animated: true) }), - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_LN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in let path = filePath?.deletingLastPathComponent() let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") @@ -664,10 +718,10 @@ func presentLoader() -> UIAlertController { NSLayoutConstraint.activate([ alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant(95), activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) ]) return alert -} \ No newline at end of file +} \ No newline at end of file From 9f2fad6925f95cda833983c2aff2f420f0578521 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 21:41:00 -0400 Subject: [PATCH 349/391] Update TweakHandler.swift --- Shared/Magic/TweakHandler.swift | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Shared/Magic/TweakHandler.swift b/Shared/Magic/TweakHandler.swift index 3e699dc5..48b6ab66 100644 --- a/Shared/Magic/TweakHandler.swift +++ b/Shared/Magic/TweakHandler.swift @@ -98,12 +98,14 @@ class TweakHandler { // Perform path adjustments if needed // This is a placeholder for the removed Process command - // Adjust the paths using native APIs or other logic // inject if there's a valid app main executable if let exe = try TweakHandler.findExecutable(at: app) { - // Adjust the paths using native APIs or other logic - // This is a placeholder for the removed Process command + _ = injectDylib( + filePath: exe.path, + dylibPath: "@executable_path/Frameworks/\(url.lastPathComponent)", + weakInject: true + ) } } catch { throw error @@ -120,17 +122,19 @@ class TweakHandler { // inject if there's a valid app main executable if let appexe = try TweakHandler.findExecutable(at: app) { - // Adjust the paths using native APIs or other logic - // This is a placeholder for the removed Process command + _ = injectDylib( + filePath: appexe.path, + dylibPath: "@executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent)", + weakInject: true + ) } } - } catch { throw error } } - // Extracy imported deb file + // Extract imported deb file private func handleDeb(at url: URL, baseTmpDir: URL) throws { let uniqueSubDir = baseTmpDir.appendingPathComponent(UUID().uuidString) try TweakHandler.createDirectoryIfNeeded(at: uniqueSubDir) @@ -158,7 +162,7 @@ class TweakHandler { } } - // Read extracted deb file, locate all neccessary contents to copy over to the .app + // Read extracted deb file, locate all necessary contents to copy over to the .app private func handleDirectories(at urls: [URL]) throws { let directoriesToCheck = [ "Library/Frameworks/", "var/jb/Library/Frameworks/", From d5c96a9ab26ecc2b105b7ea9bd9e9d699ca0cf76 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 21:42:23 -0400 Subject: [PATCH 350/391] Update TweakHandler.swift --- Shared/Magic/TweakHandler.swift | 371 +++++++++----------------------- 1 file changed, 104 insertions(+), 267 deletions(-) diff --git a/Shared/Magic/TweakHandler.swift b/Shared/Magic/TweakHandler.swift index 48b6ab66..2ca5ccd7 100644 --- a/Shared/Magic/TweakHandler.swift +++ b/Shared/Magic/TweakHandler.swift @@ -1,294 +1,131 @@ +import UIKit +import ZIPFoundation +import os.log import Foundation -import SWCompression -enum FileProcessingError: Error { - case unsupportedFileExtension(String) - case decompressionFailed(String) - case missingFile(String) +protocol FileHandlingDelegate: AnyObject { + var documentsDirectory: URL { get } + var activityIndicator: UIActivityIndicatorView { get } + func loadFiles() + func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) } -class TweakHandler { - - let fileManager = FileManager.default - - private var urls: [String] - private let app: URL - private var urlsToInject: [URL] = [] - private var directoriesToCheck: [URL] = [] - - init(urls: [String], app: URL) { - self.urls = urls - self.app = app - } - - public func getInputFiles() throws { - guard !urls.isEmpty else { - Debug.shared.log(message: "No dylibs to inject, skipping!") - return - } - - let frameworksPath = app.appendingPathComponent("Frameworks").appendingPathComponent("CydiaSubstrate.framework") - if (!fileManager.fileExists(atPath: frameworksPath.path)) { - if let ellekitURL = Bundle.main.url(forResource: "ellekit", withExtension: "deb") { - self.urls.insert(ellekitURL.absoluteString, at: 0) - } else { - Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle \u{2049}\u{fe0f}", type: .error) - return - } - } - - let baseTmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) - - do { - try TweakHandler.createDirectoryIfNeeded(at: app.appendingPathComponent("Frameworks")) - try TweakHandler.createDirectoryIfNeeded(at: baseTmpDir) - - // check for appropriate files, if theres debs - // it will extract then add a url, if theres no url, i.e. - // you haven't added a deb, it will skip - for url in urls { - let urlf = URL(string: url) - switch urlf!.pathExtension.lowercased() { - case "dylib": - try handleDylib(at: urlf!) - case "deb": - try handleDeb(at: urlf!, baseTmpDir: baseTmpDir) - default: - Debug.shared.log(message: "Unsupported file type: \(urlf!.lastPathComponent), skipping.") - } - } - - // check contents of data.tar's extracted from debs - if !directoriesToCheck.isEmpty { - try handleDirectories(at: directoriesToCheck) - if !urlsToInject.isEmpty { - try handleExtractedDirectoryContents(at: urlsToInject) - } - } - - } catch { - throw error - } +class HomeViewFileHandlers { + private let fileManager = FileManager.default + private let utilities = HomeViewUtilities() + + func uploadFile(viewController: FileHandlingDelegate) { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = viewController as? UIDocumentPickerDelegate + documentPicker.modalPresentationStyle = .formSheet + viewController.present(documentPicker, animated: true, completion: nil) } - - // finally, handle extracted contents - private func handleExtractedDirectoryContents(at urls: [URL]) throws { - for url in urls { - switch url.pathExtension.lowercased() { - case "dylib": - try handleDylib(at: url) - case "framework": - let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - try handleDylib(framework: destinationURL) - case "bundle": - let destinationURL = app.appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - default: - Debug.shared.log(message: "Unsupported file type: \(url.lastPathComponent), skipping.") - } - } + + func importFile(viewController: FileHandlingDelegate) { + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) + documentPicker.delegate = viewController as? UIDocumentPickerDelegate + documentPicker.modalPresentationStyle = .formSheet + viewController.present(documentPicker, animated: true, completion: nil) } - - // Inject imported dylib file - private func handleDylib(at url: URL) throws { + + func createNewFolder(viewController: FileHandlingDelegate, folderName: String, completion: @escaping (Result) -> Void) { + let folderURL = viewController.documentsDirectory.appendingPathComponent(folderName) do { - let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) - try TweakHandler.moveFile(from: url, to: destinationURL) - - // Perform path adjustments if needed - // This is a placeholder for the removed Process command - - // inject if there's a valid app main executable - if let exe = try TweakHandler.findExecutable(at: app) { - _ = injectDylib( - filePath: exe.path, - dylibPath: "@executable_path/Frameworks/\(url.lastPathComponent)", - weakInject: true - ) - } + try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) + viewController.loadFiles() + completion(.success(folderURL)) } catch { - throw error + utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Creating Folder") + completion(.failure(error)) } } - - // Inject imported framework dir - private func handleDylib(framework: URL) throws { - do { - if let fexe = try TweakHandler.findExecutable(at: framework) { - - // Perform path adjustments if needed - // This is a placeholder for the removed Process command - - // inject if there's a valid app main executable - if let appexe = try TweakHandler.findExecutable(at: app) { - _ = injectDylib( - filePath: appexe.path, - dylibPath: "@executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent)", - weakInject: true - ) - } - } - } catch { - throw error + + func createNewFile(viewController: FileHandlingDelegate, fileName: String, completion: @escaping (Result) -> Void) { + let fileURL = viewController.documentsDirectory.appendingPathComponent(fileName) + if !fileManager.fileExists(atPath: fileURL.path) { + fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) + viewController.loadFiles() + completion(.success(fileURL)) + } else { + let error = NSError(domain: "FileExists", code: 1, userInfo: [NSLocalizedDescriptionKey: "File already exists"]) + utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Creating File") + completion(.failure(error)) } } - - // Extract imported deb file - private func handleDeb(at url: URL, baseTmpDir: URL) throws { - let uniqueSubDir = baseTmpDir.appendingPathComponent(UUID().uuidString) - try TweakHandler.createDirectoryIfNeeded(at: uniqueSubDir) - - // I don't particularly like this code - // but it somehow works well enough, - // do note large lzma's are slow as hell - do { - let arFiles = try extractAR(try Data(contentsOf: url)) - - for arFile in arFiles { - let outputPath = uniqueSubDir.appendingPathComponent(arFile.name) - try arFile.content.write(to: outputPath) - - if ["data.tar.lzma", "data.tar.gz", "data.tar.xz", "data.tar.bz2"].contains(arFile.name) { - var fileToProcess = outputPath - try processFile(at: &fileToProcess) - try processFile(at: &fileToProcess) - directoriesToCheck.append(fileToProcess) + + func renameFile(viewController: FileHandlingDelegate, fileURL: URL, newName: String, completion: @escaping (Result) -> Void) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) + viewController.activityIndicator.startAnimating() + let workItem = DispatchWorkItem { + do { + try self.fileManager.moveItem(at: fileURL, to: destinationURL) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + completion(.success(destinationURL)) + } + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Renaming File") + completion(.failure(error)) } } - } catch { - Debug.shared.log(message: "Error handling file \(url): \(error)") - throw error } + DispatchQueue.global().async(execute: workItem) } - - // Read extracted deb file, locate all necessary contents to copy over to the .app - private func handleDirectories(at urls: [URL]) throws { - let directoriesToCheck = [ - "Library/Frameworks/", "var/jb/Library/Frameworks/", - "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/", - "Library/Application Support/", "var/jb/Library/Application Support/" - ] - - let fileManager = FileManager.default - - for baseURL in urls { - for directory in directoriesToCheck { - let directoryURL = baseURL.appendingPathComponent(directory) - - guard fileManager.fileExists(atPath: directoryURL.path) else { - Debug.shared.log(message: "Directory does not exist: \(directoryURL.path). Skipping.") - continue + + func deleteFile(viewController: FileHandlingDelegate, fileURL: URL, completion: @escaping (Result) -> Void) { + viewController.activityIndicator.startAnimating() + let workItem = DispatchWorkItem { + do { + try self.fileManager.removeItem(at: fileURL) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + completion(.success(())) } - - switch directory { - case "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/": - let dylibFiles = try locateDylibFiles(in: directoryURL) - for fileURL in dylibFiles { - urlsToInject.append(fileURL) - } - - case "Library/Frameworks/", "var/jb/Library/Frameworks/": - let frameworkDirectories = try locateFrameworkDirectories(in: directoryURL) - for frameworkURL in frameworkDirectories { - urlsToInject.append(frameworkURL) - } - - case "Library/Application Support/", "var/jb/Library/Application Support/": - try searchForBundles(in: directoryURL) - - default: - Debug.shared.log(message: "Unexpected directory path: \(directoryURL.path)") + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Deleting File") + completion(.failure(error)) } } } - } -} - -// MARK: - Find correct files in debs -extension TweakHandler { - private func searchForBundles(in directory: URL) throws { - let fileManager = FileManager.default - let allFiles = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) - - let bundleDirectories = allFiles.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "bundle" && url.hasDirectoryPath && !isSymlink - } - - for bundleURL in bundleDirectories { - urlsToInject.append(bundleURL) - } - - let directoriesToSearch = allFiles.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.hasDirectoryPath && !bundleDirectories.contains(url) && !isSymlink - } - - for dirURL in directoriesToSearch { - try searchForBundles(in: dirURL) - } - } - - private func locateDylibFiles(in directory: URL) throws -> [URL] { - let fileManager = FileManager.default - let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) - - let dylibFiles = files.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "dylib" && !isSymlink - } - - return dylibFiles + DispatchQueue.global().async(execute: workItem) } - private func locateFrameworkDirectories(in directory: URL) throws -> [URL] { - let fileManager = FileManager.default - let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) - - let frameworkDirectories = files.filter { url in - let attributes = try? fileManager.attributesOfItem(atPath: url.path) - let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink - return url.pathExtension.lowercased() == "framework" && url.hasDirectoryPath && !isSymlink - } - - return frameworkDirectories - } -} - -// MARK: - File management -extension TweakHandler { - private static func createDirectoryIfNeeded(at url: URL) throws { - let fileManager = FileManager.default - if !fileManager.fileExists(atPath: url.path) { - try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) - } - } - - public static func findExecutable(at frameworkURL: URL) throws -> URL? { - - let infoPlistURL = frameworkURL.appendingPathComponent("Info.plist") - - let plistData = try Data(contentsOf: infoPlistURL) - if let plist = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any], - let executableName = plist["CFBundleExecutable"] as? String { - let executableURL = frameworkURL.appendingPathComponent(executableName) - return executableURL - } else { - Debug.shared.log(message: "CFBundleExecutable not found in Info.plist") - return nil + func unzipFile(viewController: FileHandlingDelegate, fileURL: URL, destinationName: String, progressHandler: ((Double) -> Void)? = nil, completion: @escaping (Result) -> Void) { + let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(destinationName) + viewController.activityIndicator.startAnimating() + let workItem = DispatchWorkItem { + do { + try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: { progress in + if let progressHandler = progressHandler { + let progressValue = Double(progress.completedUnitCount) / Double(progress.totalUnitCount) + print("Progress: \(progressValue)") + progressHandler(progressValue) + } + }) + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + viewController.loadFiles() + completion(.success(destinationURL)) + } + } catch { + DispatchQueue.main.async { + viewController.activityIndicator.stopAnimating() + self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Unzipping File") + completion(.failure(error)) + } + } } + DispatchQueue.global().async(execute: workItem) } - private static func moveFile(from sourceURL: URL, to destinationURL: URL) throws { - let fileManager = FileManager.default - if fileManager.fileExists(atPath: destinationURL.path) { - Debug.shared.log(message: "File already exists at destination: \(destinationURL)") - } else { - try fileManager.moveItem(at: sourceURL, to: destinationURL) - } + func shareFile(viewController: UIViewController, fileURL: URL) { + let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) + viewController.present(activityController, animated: true, completion: nil) } } \ No newline at end of file From 8e72ea7397b3b0fa01dc2e00a3d69d5b6e2c6cce Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 23:06:58 -0400 Subject: [PATCH 351/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index ce149212..2ca5ccd7 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -56,7 +56,7 @@ class HomeViewFileHandlers { func renameFile(viewController: FileHandlingDelegate, fileURL: URL, newName: String, completion: @escaping (Result) -> Void) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { + let workItem = DispatchWorkItem { do { try self.fileManager.moveItem(at: fileURL, to: destinationURL) DispatchQueue.main.async { @@ -72,11 +72,12 @@ class HomeViewFileHandlers { } } } + DispatchQueue.global().async(execute: workItem) } func deleteFile(viewController: FileHandlingDelegate, fileURL: URL, completion: @escaping (Result) -> Void) { viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { + let workItem = DispatchWorkItem { do { try self.fileManager.removeItem(at: fileURL) DispatchQueue.main.async { @@ -92,12 +93,13 @@ class HomeViewFileHandlers { } } } + DispatchQueue.global().async(execute: workItem) } func unzipFile(viewController: FileHandlingDelegate, fileURL: URL, destinationName: String, progressHandler: ((Double) -> Void)? = nil, completion: @escaping (Result) -> Void) { let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(destinationName) viewController.activityIndicator.startAnimating() - DispatchQueue.global().async { + let workItem = DispatchWorkItem { do { try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: { progress in if let progressHandler = progressHandler { @@ -119,6 +121,7 @@ class HomeViewFileHandlers { } } } + DispatchQueue.global().async(execute: workItem) } func shareFile(viewController: UIViewController, fileURL: URL) { From 2016c94750ab9adec4614537aef81ed2e2a27d54 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Mon, 17 Mar 2025 23:11:03 -0400 Subject: [PATCH 352/391] Update TweakHandler.swift --- Shared/Magic/TweakHandler.swift | 371 +++++++++++++++++++++++--------- 1 file changed, 267 insertions(+), 104 deletions(-) diff --git a/Shared/Magic/TweakHandler.swift b/Shared/Magic/TweakHandler.swift index 2ca5ccd7..48b6ab66 100644 --- a/Shared/Magic/TweakHandler.swift +++ b/Shared/Magic/TweakHandler.swift @@ -1,131 +1,294 @@ -import UIKit -import ZIPFoundation -import os.log import Foundation +import SWCompression -protocol FileHandlingDelegate: AnyObject { - var documentsDirectory: URL { get } - var activityIndicator: UIActivityIndicatorView { get } - func loadFiles() - func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) +enum FileProcessingError: Error { + case unsupportedFileExtension(String) + case decompressionFailed(String) + case missingFile(String) } -class HomeViewFileHandlers { - private let fileManager = FileManager.default - private let utilities = HomeViewUtilities() - - func uploadFile(viewController: FileHandlingDelegate) { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController as? UIDocumentPickerDelegate - documentPicker.modalPresentationStyle = .formSheet - viewController.present(documentPicker, animated: true, completion: nil) - } - - func importFile(viewController: FileHandlingDelegate) { - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.data], asCopy: true) - documentPicker.delegate = viewController as? UIDocumentPickerDelegate - documentPicker.modalPresentationStyle = .formSheet - viewController.present(documentPicker, animated: true, completion: nil) +class TweakHandler { + + let fileManager = FileManager.default + + private var urls: [String] + private let app: URL + private var urlsToInject: [URL] = [] + private var directoriesToCheck: [URL] = [] + + init(urls: [String], app: URL) { + self.urls = urls + self.app = app } - - func createNewFolder(viewController: FileHandlingDelegate, folderName: String, completion: @escaping (Result) -> Void) { - let folderURL = viewController.documentsDirectory.appendingPathComponent(folderName) + + public func getInputFiles() throws { + guard !urls.isEmpty else { + Debug.shared.log(message: "No dylibs to inject, skipping!") + return + } + + let frameworksPath = app.appendingPathComponent("Frameworks").appendingPathComponent("CydiaSubstrate.framework") + if (!fileManager.fileExists(atPath: frameworksPath.path)) { + if let ellekitURL = Bundle.main.url(forResource: "ellekit", withExtension: "deb") { + self.urls.insert(ellekitURL.absoluteString, at: 0) + } else { + Debug.shared.log(message: "Error: ellekit.deb not found in the app bundle \u{2049}\u{fe0f}", type: .error) + return + } + } + + let baseTmpDir = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString) + do { - try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil) - viewController.loadFiles() - completion(.success(folderURL)) + try TweakHandler.createDirectoryIfNeeded(at: app.appendingPathComponent("Frameworks")) + try TweakHandler.createDirectoryIfNeeded(at: baseTmpDir) + + // check for appropriate files, if theres debs + // it will extract then add a url, if theres no url, i.e. + // you haven't added a deb, it will skip + for url in urls { + let urlf = URL(string: url) + switch urlf!.pathExtension.lowercased() { + case "dylib": + try handleDylib(at: urlf!) + case "deb": + try handleDeb(at: urlf!, baseTmpDir: baseTmpDir) + default: + Debug.shared.log(message: "Unsupported file type: \(urlf!.lastPathComponent), skipping.") + } + } + + // check contents of data.tar's extracted from debs + if !directoriesToCheck.isEmpty { + try handleDirectories(at: directoriesToCheck) + if !urlsToInject.isEmpty { + try handleExtractedDirectoryContents(at: urlsToInject) + } + } + } catch { - utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Creating Folder") - completion(.failure(error)) + throw error } } - - func createNewFile(viewController: FileHandlingDelegate, fileName: String, completion: @escaping (Result) -> Void) { - let fileURL = viewController.documentsDirectory.appendingPathComponent(fileName) - if !fileManager.fileExists(atPath: fileURL.path) { - fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil) - viewController.loadFiles() - completion(.success(fileURL)) - } else { - let error = NSError(domain: "FileExists", code: 1, userInfo: [NSLocalizedDescriptionKey: "File already exists"]) - utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Creating File") - completion(.failure(error)) + + // finally, handle extracted contents + private func handleExtractedDirectoryContents(at urls: [URL]) throws { + for url in urls { + switch url.pathExtension.lowercased() { + case "dylib": + try handleDylib(at: url) + case "framework": + let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + try handleDylib(framework: destinationURL) + case "bundle": + let destinationURL = app.appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + default: + Debug.shared.log(message: "Unsupported file type: \(url.lastPathComponent), skipping.") + } } } - - func renameFile(viewController: FileHandlingDelegate, fileURL: URL, newName: String, completion: @escaping (Result) -> Void) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(newName) - viewController.activityIndicator.startAnimating() - let workItem = DispatchWorkItem { - do { - try self.fileManager.moveItem(at: fileURL, to: destinationURL) - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.loadFiles() - completion(.success(destinationURL)) - } - } catch { - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Renaming File") - completion(.failure(error)) - } + + // Inject imported dylib file + private func handleDylib(at url: URL) throws { + do { + let destinationURL = app.appendingPathComponent("Frameworks").appendingPathComponent(url.lastPathComponent) + try TweakHandler.moveFile(from: url, to: destinationURL) + + // Perform path adjustments if needed + // This is a placeholder for the removed Process command + + // inject if there's a valid app main executable + if let exe = try TweakHandler.findExecutable(at: app) { + _ = injectDylib( + filePath: exe.path, + dylibPath: "@executable_path/Frameworks/\(url.lastPathComponent)", + weakInject: true + ) } + } catch { + throw error } - DispatchQueue.global().async(execute: workItem) } - - func deleteFile(viewController: FileHandlingDelegate, fileURL: URL, completion: @escaping (Result) -> Void) { - viewController.activityIndicator.startAnimating() - let workItem = DispatchWorkItem { - do { - try self.fileManager.removeItem(at: fileURL) - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.loadFiles() - completion(.success(())) + + // Inject imported framework dir + private func handleDylib(framework: URL) throws { + do { + if let fexe = try TweakHandler.findExecutable(at: framework) { + + // Perform path adjustments if needed + // This is a placeholder for the removed Process command + + // inject if there's a valid app main executable + if let appexe = try TweakHandler.findExecutable(at: app) { + _ = injectDylib( + filePath: appexe.path, + dylibPath: "@executable_path/Frameworks/\(framework.lastPathComponent)/\(fexe.lastPathComponent)", + weakInject: true + ) } - } catch { - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Deleting File") - completion(.failure(error)) + } + } catch { + throw error + } + } + + // Extract imported deb file + private func handleDeb(at url: URL, baseTmpDir: URL) throws { + let uniqueSubDir = baseTmpDir.appendingPathComponent(UUID().uuidString) + try TweakHandler.createDirectoryIfNeeded(at: uniqueSubDir) + + // I don't particularly like this code + // but it somehow works well enough, + // do note large lzma's are slow as hell + do { + let arFiles = try extractAR(try Data(contentsOf: url)) + + for arFile in arFiles { + let outputPath = uniqueSubDir.appendingPathComponent(arFile.name) + try arFile.content.write(to: outputPath) + + if ["data.tar.lzma", "data.tar.gz", "data.tar.xz", "data.tar.bz2"].contains(arFile.name) { + var fileToProcess = outputPath + try processFile(at: &fileToProcess) + try processFile(at: &fileToProcess) + directoriesToCheck.append(fileToProcess) } } + } catch { + Debug.shared.log(message: "Error handling file \(url): \(error)") + throw error } - DispatchQueue.global().async(execute: workItem) } - - func unzipFile(viewController: FileHandlingDelegate, fileURL: URL, destinationName: String, progressHandler: ((Double) -> Void)? = nil, completion: @escaping (Result) -> Void) { - let destinationURL = fileURL.deletingLastPathComponent().appendingPathComponent(destinationName) - viewController.activityIndicator.startAnimating() - let workItem = DispatchWorkItem { - do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: { progress in - if let progressHandler = progressHandler { - let progressValue = Double(progress.completedUnitCount) / Double(progress.totalUnitCount) - print("Progress: \(progressValue)") - progressHandler(progressValue) - } - }) - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - viewController.loadFiles() - completion(.success(destinationURL)) + + // Read extracted deb file, locate all necessary contents to copy over to the .app + private func handleDirectories(at urls: [URL]) throws { + let directoriesToCheck = [ + "Library/Frameworks/", "var/jb/Library/Frameworks/", + "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/", + "Library/Application Support/", "var/jb/Library/Application Support/" + ] + + let fileManager = FileManager.default + + for baseURL in urls { + for directory in directoriesToCheck { + let directoryURL = baseURL.appendingPathComponent(directory) + + guard fileManager.fileExists(atPath: directoryURL.path) else { + Debug.shared.log(message: "Directory does not exist: \(directoryURL.path). Skipping.") + continue } - } catch { - DispatchQueue.main.async { - viewController.activityIndicator.stopAnimating() - self.utilities.handleError(in: viewController as! UIViewController, error: error, withTitle: "Unzipping File") - completion(.failure(error)) + + switch directory { + case "Library/MobileSubstrate/DynamicLibraries/", "var/jb/Library/MobileSubstrate/DynamicLibraries/": + let dylibFiles = try locateDylibFiles(in: directoryURL) + for fileURL in dylibFiles { + urlsToInject.append(fileURL) + } + + case "Library/Frameworks/", "var/jb/Library/Frameworks/": + let frameworkDirectories = try locateFrameworkDirectories(in: directoryURL) + for frameworkURL in frameworkDirectories { + urlsToInject.append(frameworkURL) + } + + case "Library/Application Support/", "var/jb/Library/Application Support/": + try searchForBundles(in: directoryURL) + + default: + Debug.shared.log(message: "Unexpected directory path: \(directoryURL.path)") } } } - DispatchQueue.global().async(execute: workItem) + } +} + +// MARK: - Find correct files in debs +extension TweakHandler { + private func searchForBundles(in directory: URL) throws { + let fileManager = FileManager.default + let allFiles = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) + + let bundleDirectories = allFiles.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "bundle" && url.hasDirectoryPath && !isSymlink + } + + for bundleURL in bundleDirectories { + urlsToInject.append(bundleURL) + } + + let directoriesToSearch = allFiles.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.hasDirectoryPath && !bundleDirectories.contains(url) && !isSymlink + } + + for dirURL in directoriesToSearch { + try searchForBundles(in: dirURL) + } + } + + private func locateDylibFiles(in directory: URL) throws -> [URL] { + let fileManager = FileManager.default + let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) + + let dylibFiles = files.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "dylib" && !isSymlink + } + + return dylibFiles + } + + private func locateFrameworkDirectories(in directory: URL) throws -> [URL] { + let fileManager = FileManager.default + let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) + + let frameworkDirectories = files.filter { url in + let attributes = try? fileManager.attributesOfItem(atPath: url.path) + let isSymlink = attributes?[.type] as? FileAttributeType == .typeSymbolicLink + return url.pathExtension.lowercased() == "framework" && url.hasDirectoryPath && !isSymlink + } + + return frameworkDirectories + } +} + +// MARK: - File management +extension TweakHandler { + private static func createDirectoryIfNeeded(at url: URL) throws { + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: url.path) { + try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + } + } + + public static func findExecutable(at frameworkURL: URL) throws -> URL? { + + let infoPlistURL = frameworkURL.appendingPathComponent("Info.plist") + + let plistData = try Data(contentsOf: infoPlistURL) + if let plist = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any], + let executableName = plist["CFBundleExecutable"] as? String { + let executableURL = frameworkURL.appendingPathComponent(executableName) + return executableURL + } else { + Debug.shared.log(message: "CFBundleExecutable not found in Info.plist") + return nil + } } - func shareFile(viewController: UIViewController, fileURL: URL) { - let activityController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) - viewController.present(activityController, animated: true, completion: nil) + private static func moveFile(from sourceURL: URL, to destinationURL: URL) throws { + let fileManager = FileManager.default + if fileManager.fileExists(atPath: destinationURL.path) { + Debug.shared.log(message: "File already exists at destination: \(destinationURL)") + } else { + try fileManager.moveItem(at: sourceURL, to: destinationURL) + } } } \ No newline at end of file From 591a0c3d35c56732413df1eb0b42ff2aed99007e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 00:31:10 -0400 Subject: [PATCH 353/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 464 ++++++++++----------- 1 file changed, 213 insertions(+), 251 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index ef4378e4..2ab04559 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -87,7 +87,6 @@ class LibraryViewController: UITableViewController { present(loaderAlert!, animated: true) - // Create mock source if in debug mode if isDebugMode { let mockSource = SourceRefreshOperation() mockSource.createMockSource { mockSourceData in @@ -101,7 +100,6 @@ class LibraryViewController: UITableViewController { } } } else { - // Normal source fetch SourceGET().downloadURL(from: sourceURL) { [weak self] result in guard let self = self else { return } @@ -137,10 +135,8 @@ class LibraryViewController: UITableViewController { return } - // Look for the version that matches our update version for version in versions { if version.version == updateVersion { - // Found the matching version Debug.shared.log(message: "Found matching version: \(version.version)", type: .info) let uuid = UUID().uuidString @@ -150,7 +146,6 @@ class LibraryViewController: UITableViewController { let tempDirectory = FileManager.default.temporaryDirectory let destinationURL = tempDirectory.appendingPathComponent("\(uuid).ipa") - // Download the file if let data = try? Data(contentsOf: version.downloadURL) { try data.write(to: destinationURL) @@ -159,13 +154,11 @@ class LibraryViewController: UITableViewController { DispatchQueue.main.async { self.loaderAlert?.dismiss(animated: true) { - // Force Sign & Install let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) signingDataWrapper.signingOptions.installAfterSigned = true - // Store the original signed app for deletion after update let originalSignedApp = signedApp let ap = SigningsViewController( @@ -174,7 +167,6 @@ class LibraryViewController: UITableViewController { appsViewController: self ) - // Add completion handler to delete the original app after successful signing ap.signingCompletionHandler = { [weak self] success in if success { CoreDataManager.shared.deleteAllSignedAppContent(for: originalSignedApp) @@ -217,6 +209,37 @@ class LibraryViewController: UITableViewController { }()) return isDebug } + + func presentLoader() -> UIAlertController { + let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.isUserInteractionEnabled = false + activityIndicator.startAnimating() + + alert.view.addSubview(activityIndicator) + + NSLayoutConstraint.activate([ + alert.view.heightAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant: 95), + activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) + ]) + + return alert + } + + func someFunction() { + let someView = UIView() + someView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + someView.widthAnchor.constraint(equalToConstant: 100), + someView.heightAnchor.constraint(equalToConstant: 50), + someView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + someView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor) + ]) + } } extension LibraryViewController { @@ -264,7 +287,6 @@ extension LibraryViewController { let source = getApplication(row: indexPath.row, section: indexPath.section) let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - if let iconURL = source!.value(forKey: "iconURL") as? String { let imagePath = filePath!.appendingPathComponent(iconURL) @@ -295,7 +317,6 @@ extension LibraryViewController { let hasUpdate = (source as? SignedApps)?.value(forKey: "hasUpdate") as? Bool ?? false if let signedApp = source as? SignedApps, hasUpdate { - // Update available menu let updateButton = PopupViewControllerButton( title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), color: UIColor.tintColor.withAlphaComponent(0.9), @@ -322,7 +343,6 @@ extension LibraryViewController { popupVC.configureButtons([updateButton, clearButton]) } else { - // Regular menu let button1 = PopupViewControllerButton( title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.tintColor.withAlphaComponent(0.9) @@ -414,63 +434,6 @@ extension LibraryViewController { popupVC = PopupViewController() popupVC.modalPresentationStyle = .pageSheet - let singingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let button1 = PopupViewControllerButton( - title: singingData.signingOptions.installAfterSigned - ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) - : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: UIColor.tintColor.withAlphaComponent(0.9)) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startSigning(meow: source!) - } - - let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - let alertController = UIAlertController( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), - message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), - preferredStyle: .alert - ) - - let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - - } - - let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - self.present(alertController, animated: true, completion: nil) - } - } - - popupVC.configureButtons([button1, button2]) - - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium(), - - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } - case 1: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet - let signingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) let button1 = PopupViewControllerButton( title: signingData.signingOptions.installAfterSigned @@ -503,225 +466,224 @@ extension LibraryViewController { alertController.addAction(cancelAction) self.present(alertController, animated: true, completion: nil) - } - } - - popupVC.configureButtons([button1, button2]) - - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium() - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } - default: - break + } +} + +popupVC.configureButtons([button1, button2]) + +let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) +if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium() + ] + presentationController.prefersGrabberVisible = true +} + +self.present(popupVC, animated: true) +} else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) +} +default: + break +} + +tableView.deselectRow(at: indexPath, animated: true) +} + +@objc func startSigning(meow: NSManagedObject) { + if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) } - - tableView.deselectRow(at: indexPath, animated: true) } +} + +override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) - @objc func startSigning(meow: NSManagedObject) { - if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) - } + let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break } + completionHandler(true) } - override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - - let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break - } - completionHandler(true) - } - - deleteAction.backgroundColor = UIColor.red - let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) - configuration.performsFirstActionWithFullSwipe = true + deleteAction.backgroundColor = UIColor.red + let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) + configuration.performsFirstActionWithFullSwipe = true - return configuration - } + return configuration +} + +override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - - let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] - } + let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] } - - self.present(navigationController, animated: true) - }), + } - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") - - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") - } + self.present(navigationController, animated: true) + }), + + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") } - }) - ]) - }) - return configuration - } + } + }) + ]) + }) + return configuration +} } extension LibraryViewController { - @objc func afetch() { self.fetchSources() } +@objc func afetch() { self.fetchSources() } + +func fetchSources() { + signedApps = CoreDataManager.shared.getDatedSignedApps() + downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - func fetchSources() { - signedApps = CoreDataManager.shared.getDatedSignedApps() - downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - - DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() - } + DispatchQueue.main.async { + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() } } +} + +func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { + if section == 0 { + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) + } - func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { + if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) + } + return nil +} + +func getApplication(row: Int, section: Int) -> NSManagedObject? { + if isFiltering { if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! + if row < filteredSignedApps.count { + return filteredSignedApps[row] } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) - } - - if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) } - return nil - } - - func getApplication(row: Int, section: Int) -> NSManagedObject? { - if isFiltering { - if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] - } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] - } + } else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] } - } else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] - } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] - } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] } } - return nil } + return nil +} } extension LibraryViewController: UISearchResultsUpdating { - func updateSearchResults(for searchController: UISearchController) { - let searchText = searchController.searchBar.text ?? "" - filterContentForSearchText(searchText) - tableView.reloadData() - } - - private func filterContentForSearchText(_ searchText: String) { - let lowercasedSearchText = searchText.lowercased() +func updateSearchResults(for searchController: UISearchController) { + let searchText = searchController.searchBar.text ?? "" + filterContentForSearchText(searchText) + tableView.reloadData() +} - filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] +private func filterContentForSearchText(_ searchText: String) { + let lowercasedSearchText = searchText.lowercased() - filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] - } + filteredSignedApps = signedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] + + filteredDownloadedApps = downloadedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] +} } extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { - func setupSearchController() { - searchController = UISearchController(searchResultsController: nil) - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = true - searchController.searchResultsUpdater = self - searchController.delegate = self - searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") - navigationItem.searchController = searchController - definesPresentationContext = true - navigationItem.hidesSearchBarWhenScrolling = false - } - - var isFiltering: Bool { - return searchController.isActive && !searchBarIsEmpty - } +func setupSearchController() { + searchController = UISearchController(searchResultsController: nil) + searchController.obscuresBackgroundDuringPresentation = false + searchController.hidesNavigationBarDuringPresentation = true + searchController.searchResultsUpdater = self + searchController.delegate = self + searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") + navigationItem.searchController = searchController + definesPresentationContext = true + navigationItem.hidesSearchBarWhenScrolling = false +} - var searchBarIsEmpty: Bool { - return searchController.searchBar.text?.isEmpty ?? true - } +var isFiltering: Bool { + return searchController.isActive && !searchBarIsEmpty +} + +var searchBarIsEmpty: Bool { + return searchController.searchBar.text?.isEmpty ?? true +} } -/// https://stackoverflow.com/a/75310581 func presentLoader() -> UIAlertController { - let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.isUserInteractionEnabled = false - activityIndicator.startAnimating() +let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) +let activityIndicator = UIActivityIndicatorView(style: .large) +activityIndicator.translatesAutoresizingMaskIntoConstraints = false +activityIndicator.isUserInteractionEnabled = false +activityIndicator.startAnimating() - alert.view.addSubview(activityIndicator) - - NSLayoutConstraint.activate([ - alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant(95), - activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) - ]) - - return alert -} \ No newline at end of file +alert.view.addSubview(activityIndicator) + +NSLayoutConstraint.activate([ + alert.view.heightAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant(95)), + activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) +]) + +return alert +} \ No newline at end of file From 5d5a455453e0740e1a849eaeb7604a7b9d6b6372 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 00:35:57 -0400 Subject: [PATCH 354/391] Update HomeViewFileHandlers.swift --- iOS/Views/Home/HomeViewFileHandlers.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/iOS/Views/Home/HomeViewFileHandlers.swift b/iOS/Views/Home/HomeViewFileHandlers.swift index 2ca5ccd7..e00d187b 100644 --- a/iOS/Views/Home/HomeViewFileHandlers.swift +++ b/iOS/Views/Home/HomeViewFileHandlers.swift @@ -101,13 +101,12 @@ class HomeViewFileHandlers { viewController.activityIndicator.startAnimating() let workItem = DispatchWorkItem { do { - try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: { progress in - if let progressHandler = progressHandler { - let progressValue = Double(progress.completedUnitCount) / Double(progress.totalUnitCount) - print("Progress: \(progressValue)") - progressHandler(progressValue) - } - }) + let progress = Progress(totalUnitCount: 100) + progress.cancellationHandler = { + print("Unzip cancelled") + } + try self.fileManager.unzipItem(at: fileURL, to: destinationURL, progress: progress) + progressHandler?(1.0) DispatchQueue.main.async { viewController.activityIndicator.stopAnimating() viewController.loadFiles() From f329a0963ce304f7aee3c94abadc1a9ee8421634 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 01:19:45 -0400 Subject: [PATCH 355/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 429 ++++++++++----------- 1 file changed, 214 insertions(+), 215 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 2ab04559..db183243 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -1,5 +1,27 @@ import UIKit import CoreData +import UniformTypeIdentifiers + +// Define PopupViewControllerButton if not already defined +class PopupViewControllerButton: UIButton { + var onTap: (() -> Void)? + + init(title: String, color: UIColor, titleColor: UIColor = .white) { + super.init(frame: .zero) + self.setTitle(title, for: .normal) + self.backgroundColor = color + self.setTitleColor(titleColor, for: .normal) + self.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func buttonTapped() { + onTap?() + } +} class LibraryViewController: UITableViewController { var signedApps: [SignedApps]? @@ -87,6 +109,7 @@ class LibraryViewController: UITableViewController { present(loaderAlert!, animated: true) + // Create mock source if in debug mode if isDebugMode { let mockSource = SourceRefreshOperation() mockSource.createMockSource { mockSourceData in @@ -100,6 +123,7 @@ class LibraryViewController: UITableViewController { } } } else { + // Normal source fetch SourceGET().downloadURL(from: sourceURL) { [weak self] result in guard let self = self else { return } @@ -135,8 +159,10 @@ class LibraryViewController: UITableViewController { return } + // Look for the version that matches our update version for version in versions { if version.version == updateVersion { + // Found the matching version Debug.shared.log(message: "Found matching version: \(version.version)", type: .info) let uuid = UUID().uuidString @@ -146,6 +172,7 @@ class LibraryViewController: UITableViewController { let tempDirectory = FileManager.default.temporaryDirectory let destinationURL = tempDirectory.appendingPathComponent("\(uuid).ipa") + // Download the file if let data = try? Data(contentsOf: version.downloadURL) { try data.write(to: destinationURL) @@ -154,11 +181,13 @@ class LibraryViewController: UITableViewController { DispatchQueue.main.async { self.loaderAlert?.dismiss(animated: true) { + // Force Sign & Install let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) signingDataWrapper.signingOptions.installAfterSigned = true + // Store the original signed app for deletion after update let originalSignedApp = signedApp let ap = SigningsViewController( @@ -167,6 +196,7 @@ class LibraryViewController: UITableViewController { appsViewController: self ) + // Add completion handler to delete the original app after successful signing ap.signingCompletionHandler = { [weak self] success in if success { CoreDataManager.shared.deleteAllSignedAppContent(for: originalSignedApp) @@ -209,37 +239,6 @@ class LibraryViewController: UITableViewController { }()) return isDebug } - - func presentLoader() -> UIAlertController { - let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.isUserInteractionEnabled = false - activityIndicator.startAnimating() - - alert.view.addSubview(activityIndicator) - - NSLayoutConstraint.activate([ - alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant: 95), - activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) - ]) - - return alert - } - - func someFunction() { - let someView = UIView() - someView.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - someView.widthAnchor.constraint(equalToConstant: 100), - someView.heightAnchor.constraint(equalToConstant: 50), - someView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), - someView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor) - ]) - } } extension LibraryViewController { @@ -267,12 +266,10 @@ extension LibraryViewController { }) return headerWithButton case 1: - let headerWithButton = GroupedSectionHeader( title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS"), subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL", arguments: String(downloadedApps?.count ?? 0)) ) - return headerWithButton default: return nil @@ -458,232 +455,234 @@ extension LibraryViewController { let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + } let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) alertController.addAction(confirmAction) - alertController.addAction(cancelAction) + alertController.addAction(cancelAction) self.present(alertController, animated: true, completion: nil) - } -} - -popupVC.configureButtons([button1, button2]) - -let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) -if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium() - ] - presentationController.prefersGrabberVisible = true -} - -self.present(popupVC, animated: true) -} else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) -} -default: - break -} - -tableView.deselectRow(at: indexPath, animated: true) -} - -@objc func startSigning(meow: NSManagedObject) { - if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) + } + } + + popupVC.configureButtons([button1, button2]) + + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium() + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } + default: + break } + + tableView.deselectRow(at: indexPath, animated: true) } -} - -override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break + @objc func startSigning(meow: NSManagedObject) { + if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) + } } - completionHandler(true) } - deleteAction.backgroundColor = UIColor.red - let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) - configuration.performsFirstActionWithFullSwipe = true - - return configuration -} + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + + let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break + } + completionHandler(true) + } + + deleteAction.backgroundColor = UIColor.red + let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) + configuration.performsFirstActionWithFullSwipe = true -override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + return configuration + } - let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] + override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + + let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] + } } - } - - self.present(navigationController, animated: true) - }), - - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + self.present(navigationController, animated: true) + }), - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") + } } - } - }) - ]) - }) - return configuration -} + }) + ]) + }) + return configuration + } } extension LibraryViewController { -@objc func afetch() { self.fetchSources() } - -func fetchSources() { - signedApps = CoreDataManager.shared.getDatedSignedApps() - downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + @objc func afetch() { self.fetchSources() } - DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() - } - } -} - -func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { - if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! + func fetchSources() { + signedApps = CoreDataManager.shared.getDatedSignedApps() + downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + + DispatchQueue.main.async { + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() + } } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) } - if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) - } - return nil -} - -func getApplication(row: Int, section: Int) -> NSManagedObject? { - if isFiltering { + func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) + } + + if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) } - } else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] + return nil + } + + func getApplication(row: Int, section: Int) -> NSManagedObject? { + if isFiltering { + if section == 0 { + if row < filteredSignedApps.count { + return filteredSignedApps[row] + } + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] + } } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] + } else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] + } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] + } } } + return nil } - return nil -} } extension LibraryViewController: UISearchResultsUpdating { -func updateSearchResults(for searchController: UISearchController) { - let searchText = searchController.searchBar.text ?? "" - filterContentForSearchText(searchText) - tableView.reloadData() -} - -private func filterContentForSearchText(_ searchText: String) { - let lowercasedSearchText = searchText.lowercased() + func updateSearchResults(for searchController: UISearchController) { + let searchText = searchController.searchBar.text ?? "" + filterContentForSearchText(searchText) + tableView.reloadData() + } + + private func filterContentForSearchText(_ searchText: String) { + let lowercasedSearchText = searchText.lowercased() - filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] + filteredSignedApps = signedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] - filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] -} + filteredDownloadedApps = downloadedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] + } } extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { -func setupSearchController() { - searchController = UISearchController(searchResultsController: nil) - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = true - searchController.searchResultsUpdater = self - searchController.delegate = self - searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") - navigationItem.searchController = searchController - definesPresentationContext = true - navigationItem.hidesSearchBarWhenScrolling = false -} - -var isFiltering: Bool { - return searchController.isActive && !searchBarIsEmpty -} + func setupSearchController() { + searchController = UISearchController(searchResultsController: nil) + searchController.obscuresBackgroundDuringPresentation = false + searchController.hidesNavigationBarDuringPresentation = true + searchController.searchResultsUpdater = self + searchController.delegate = self + searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") + navigationItem.searchController = searchController + definesPresentationContext = true + navigationItem.hidesSearchBarWhenScrolling = false + } + + var isFiltering: Bool { + return searchController.isActive && !searchBarIsEmpty + } -var searchBarIsEmpty: Bool { - return searchController.searchBar.text?.isEmpty ?? true -} + var searchBarIsEmpty: Bool { + return searchController.searchBar.text?.isEmpty ?? true + } } +/// https://stackoverflow.com/a/75310581 func presentLoader() -> UIAlertController { -let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) -let activityIndicator = UIActivityIndicatorView(style: .large) -activityIndicator.translatesAutoresizingMaskIntoConstraints = false -activityIndicator.isUserInteractionEnabled = false -activityIndicator.startAnimating() - -alert.view.addSubview(activityIndicator) + let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.isUserInteractionEnabled = false + activityIndicator.startAnimating() -NSLayoutConstraint.activate([ - alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant(95)), - activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) -]) - -return alert + alert.view.addSubview(activityIndicator) + + NSLayoutConstraint.activate([ + alert.view.heightAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant: 95), + activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) + ]) + + return alert } \ No newline at end of file From 073b7766a9cfb04a364b83103cbbbe461d2163f3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 02:01:52 -0400 Subject: [PATCH 356/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 350 ++++++++++----------- 1 file changed, 170 insertions(+), 180 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index db183243..0c6e0111 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -2,8 +2,8 @@ import UIKit import CoreData import UniformTypeIdentifiers -// Define PopupViewControllerButton if not already defined -class PopupViewControllerButton: UIButton { +// Ensure PopupViewControllerButton conforms to PopupViewController.PopupButton +class PopupViewControllerButton: UIButton, PopupViewController.PopupButton { var onTap: (() -> Void)? init(title: String, color: UIColor, titleColor: UIColor = .white) { @@ -109,7 +109,6 @@ class LibraryViewController: UITableViewController { present(loaderAlert!, animated: true) - // Create mock source if in debug mode if isDebugMode { let mockSource = SourceRefreshOperation() mockSource.createMockSource { mockSourceData in @@ -123,7 +122,6 @@ class LibraryViewController: UITableViewController { } } } else { - // Normal source fetch SourceGET().downloadURL(from: sourceURL) { [weak self] result in guard let self = self else { return } @@ -159,10 +157,8 @@ class LibraryViewController: UITableViewController { return } - // Look for the version that matches our update version for version in versions { if version.version == updateVersion { - // Found the matching version Debug.shared.log(message: "Found matching version: \(version.version)", type: .info) let uuid = UUID().uuidString @@ -172,7 +168,6 @@ class LibraryViewController: UITableViewController { let tempDirectory = FileManager.default.temporaryDirectory let destinationURL = tempDirectory.appendingPathComponent("\(uuid).ipa") - // Download the file if let data = try? Data(contentsOf: version.downloadURL) { try data.write(to: destinationURL) @@ -181,13 +176,11 @@ class LibraryViewController: UITableViewController { DispatchQueue.main.async { self.loaderAlert?.dismiss(animated: true) { - // Force Sign & Install let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) signingDataWrapper.signingOptions.installAfterSigned = true - // Store the original signed app for deletion after update let originalSignedApp = signedApp let ap = SigningsViewController( @@ -196,7 +189,6 @@ class LibraryViewController: UITableViewController { appsViewController: self ) - // Add completion handler to delete the original app after successful signing ap.signingCompletionHandler = { [weak self] success in if success { CoreDataManager.shared.deleteAllSignedAppContent(for: originalSignedApp) @@ -455,13 +447,12 @@ extension LibraryViewController { let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - } let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) alertController.addAction(confirmAction) - alertController.addAction(cancelAction) + alertController.addAction(cancelAction) self.present(alertController, animated: true, completion: nil) } @@ -478,211 +469,210 @@ extension LibraryViewController { presentationController.prefersGrabberVisible = true } - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } - default: - break + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } +default: + break +} + +tableView.deselectRow(at: indexPath, animated: true) +} + +@objc func startSigning(meow: NSManagedObject) { + if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) } - - tableView.deselectRow(at: indexPath, animated: true) } +} + +override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) - @objc func startSigning(meow: NSManagedObject) { - if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) - } + let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break } + completionHandler(true) } - override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - - let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break - } - completionHandler(true) - } - - deleteAction.backgroundColor = UIColor.red - let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) - configuration.performsFirstActionWithFullSwipe = true + deleteAction.backgroundColor = UIColor.red + let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) + configuration.performsFirstActionWithFullSwipe = true - return configuration - } + return configuration +} + +override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - - let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] - } + let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] } - - self.present(navigationController, animated: true) - }), + } - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") - - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") - } + self.present(navigationController, animated: true) + }), + + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") } - }) - ]) - }) - return configuration - } + } + }) + ]) + }) + return configuration +} } extension LibraryViewController { - @objc func afetch() { self.fetchSources() } +@objc func afetch() { self.fetchSources() } + +func fetchSources() { + signedApps = CoreDataManager.shared.getDatedSignedApps() + downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - func fetchSources() { - signedApps = CoreDataManager.shared.getDatedSignedApps() - downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - - DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() - } + DispatchQueue.main.async { + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() + } + } +} + +func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { + if section == 0 { + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! } + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) } - func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { + if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) + } + return nil +} + +func getApplication(row: Int, section: Int) -> NSManagedObject? { + if isFiltering { if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! + if row < filteredSignedApps.count { + return filteredSignedApps[row] } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) - } - - if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) } - return nil - } - - func getApplication(row: Int, section: Int) -> NSManagedObject? { - if isFiltering { - if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] - } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] - } + } else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] } - } else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] - } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] - } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] } } - return nil } + return nil +} } extension LibraryViewController: UISearchResultsUpdating { - func updateSearchResults(for searchController: UISearchController) { - let searchText = searchController.searchBar.text ?? "" - filterContentForSearchText(searchText) - tableView.reloadData() - } - - private func filterContentForSearchText(_ searchText: String) { - let lowercasedSearchText = searchText.lowercased() +func updateSearchResults(for searchController: UISearchController) { + let searchText = searchController.searchBar.text ?? "" + filterContentForSearchText(searchText) + tableView.reloadData() +} - filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] +private func filterContentForSearchText(_ searchText: String) { + let lowercasedSearchText = searchText.lowercased() - filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] - } + filteredSignedApps = signedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] + + filteredDownloadedApps = downloadedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] +} } extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { - func setupSearchController() { - searchController = UISearchController(searchResultsController: nil) - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = true - searchController.searchResultsUpdater = self - searchController.delegate = self - searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") - navigationItem.searchController = searchController - definesPresentationContext = true - navigationItem.hidesSearchBarWhenScrolling = false - } - - var isFiltering: Bool { - return searchController.isActive && !searchBarIsEmpty - } +func setupSearchController() { + searchController = UISearchController(searchResultsController: nil) + searchController.obscuresBackgroundDuringPresentation = false + searchController.hidesNavigationBarDuringPresentation = true + searchController.searchResultsUpdater = self + searchController.delegate = self + searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") + navigationItem.searchController = searchController + definesPresentationContext = true + navigationItem.hidesSearchBarWhenScrolling = false +} - var searchBarIsEmpty: Bool { - return searchController.searchBar.text?.isEmpty ?? true - } +var isFiltering: Bool { + return searchController.isActive && !searchBarIsEmpty +} + +var searchBarIsEmpty: Bool { + return searchController.searchBar.text?.isEmpty ?? true +} } -/// https://stackoverflow.com/a/75310581 func presentLoader() -> UIAlertController { - let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.isUserInteractionEnabled = false - activityIndicator.startAnimating() +let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) +let activityIndicator = UIActivityIndicatorView(style: .large) +activityIndicator.translatesAutoresizingMaskIntoConstraints = false +activityIndicator.isUserInteractionEnabled = false +activityIndicator.startAnimating() - alert.view.addSubview(activityIndicator) - - NSLayoutConstraint.activate([ - alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant: 95), - activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) - ]) - - return alert +alert.view.addSubview(activityIndicator) + +NSLayoutConstraint.activate([ + alert.view.heightAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant: 95), + activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) +]) + +return alert } \ No newline at end of file From 2ce083d09ad24aa4c9e72f2a0bc924addf0c0908 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 02:07:25 -0400 Subject: [PATCH 357/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 332 ++++++++++----------- 1 file changed, 166 insertions(+), 166 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 0c6e0111..11b1eea3 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -462,217 +462,217 @@ extension LibraryViewController { let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ + presentationController.detents = [ detent2, .medium() ] presentationController.prefersGrabberVisible = true } - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } -default: - break -} - -tableView.deselectRow(at: indexPath, animated: true) -} - -@objc func startSigning(meow: NSManagedObject) { - if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } + default: + break } + + tableView.deselectRow(at: indexPath, animated: true) } -} - -override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break + @objc func startSigning(meow: NSManagedObject) { + if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) + } } - completionHandler(true) } - deleteAction.backgroundColor = UIColor.red - let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) - configuration.performsFirstActionWithFullSwipe = true - - return configuration -} + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + + let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break + } + completionHandler(true) + } + + deleteAction.backgroundColor = UIColor.red + let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) + configuration.performsFirstActionWithFullSwipe = true -override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + return configuration + } - let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] + override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + + let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] + } } - } - - self.present(navigationController, animated: true) - }), - - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + self.present(navigationController, animated: true) + }), - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") + } } - } - }) - ]) - }) - return configuration -} + }) + ]) + }) + return configuration + } } extension LibraryViewController { -@objc func afetch() { self.fetchSources() } - -func fetchSources() { - signedApps = CoreDataManager.shared.getDatedSignedApps() - downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + @objc func afetch() { self.fetchSources() } - DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() - } - } -} - -func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { - if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! + func fetchSources() { + signedApps = CoreDataManager.shared.getDatedSignedApps() + downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + + DispatchQueue.main.async { + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() + } } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) } - if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) - } - return nil -} - -func getApplication(row: Int, section: Int) -> NSManagedObject? { - if isFiltering { + func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) + } + + if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) } - } else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] + return nil + } + + func getApplication(row: Int, section: Int) -> NSManagedObject? { + if isFiltering { + if section == 0 { + if row < filteredSignedApps.count { + return filteredSignedApps[row] + } + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] + } } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] + } else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] + } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] + } } } + return nil } - return nil -} } extension LibraryViewController: UISearchResultsUpdating { -func updateSearchResults(for searchController: UISearchController) { - let searchText = searchController.searchBar.text ?? "" - filterContentForSearchText(searchText) - tableView.reloadData() -} - -private func filterContentForSearchText(_ searchText: String) { - let lowercasedSearchText = searchText.lowercased() + func updateSearchResults(for searchController: UISearchController) { + let searchText = searchController.searchBar.text ?? "" + filterContentForSearchText(searchText) + tableView.reloadData() + } + + private func filterContentForSearchText(_ searchText: String) { + let lowercasedSearchText = searchText.lowercased() - filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] + filteredSignedApps = signedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] - filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] -} + filteredDownloadedApps = downloadedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] + } } extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { -func setupSearchController() { - searchController = UISearchController(searchResultsController: nil) - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = true - searchController.searchResultsUpdater = self - searchController.delegate = self - searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") - navigationItem.searchController = searchController - definesPresentationContext = true - navigationItem.hidesSearchBarWhenScrolling = false -} - -var isFiltering: Bool { - return searchController.isActive && !searchBarIsEmpty -} + func setupSearchController() { + searchController = UISearchController(searchResultsController: nil) + searchController.obscuresBackgroundDuringPresentation = false + searchController.hidesNavigationBarDuringPresentation = true + searchController.searchResultsUpdater = self + searchController.delegate = self + searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") + navigationItem.searchController = searchController + definesPresentationContext = true + navigationItem.hidesSearchBarWhenScrolling = false + } + + var isFiltering: Bool { + return searchController.isActive && !searchBarIsEmpty + } -var searchBarIsEmpty: Bool { - return searchController.searchBar.text?.isEmpty ?? true -} + var searchBarIsEmpty: Bool { + return searchController.searchBar.text?.isEmpty ?? true + } } func presentLoader() -> UIAlertController { -let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) -let activityIndicator = UIActivityIndicatorView(style: .large) -activityIndicator.translatesAutoresizingMaskIntoConstraints = false -activityIndicator.isUserInteractionEnabled = false -activityIndicator.startAnimating() + let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.isUserInteractionEnabled = false + activityIndicator.startAnimating() -alert.view.addSubview(activityIndicator) + alert.view.addSubview(activityIndicator) -NSLayoutConstraint.activate([ - alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant: 95), - activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) -]) + NSLayoutConstraint.activate([ + alert.view.heightAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant: 95), + activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) + ]) -return alert + return alert } \ No newline at end of file From a6a4c7d60d1ccca0f21c2c31d060814706f9eec0 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 03:16:20 -0400 Subject: [PATCH 358/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 668 +++++++++++++-------- 1 file changed, 420 insertions(+), 248 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 11b1eea3..b103dea7 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -2,8 +2,8 @@ import UIKit import CoreData import UniformTypeIdentifiers -// Ensure PopupViewControllerButton conforms to PopupViewController.PopupButton -class PopupViewControllerButton: UIButton, PopupViewController.PopupButton { +// Ensure PopupViewControllerButton is a subclass of PopupViewController.PopupButton +class PopupViewControllerButton: PopupViewController.PopupButton { var onTap: (() -> Void)? init(title: String, color: UIColor, titleColor: UIColor = .white) { @@ -373,267 +373,457 @@ extension LibraryViewController { if success { CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - Debug.shared.log(message: "Done action??") - self.tableView.reloadRows(at: [indexPath], with: .left) - } - } + self.loaderAlert?.dismiss(animated: true) { + let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + signingDataWrapper.signingOptions.installAfterSigned = true + + let originalSignedApp = signedApp + + let ap = SigningsViewController( + signingDataWrapper: signingDataWrapper, + application: downloadedApp, + appsViewController: self + ) + + ap.signingCompletionHandler = { [weak self] success in + if success { + CoreDataManager.shared.deleteAllSignedAppContent(for: originalSignedApp) + self?.fetchSources() + self?.tableView.reloadData() + } + } + + let navigationController = UINavigationController(rootViewController: ap) + + navigationController.shouldPresentFullScreen() + + self.present(navigationController, animated: true) + } + } +} +return +} + +// If no matching version found +Debug.shared.log(message: "Could not find version \(updateVersion) in source", type: .error) +DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) +} +} + +private var isDebugMode: Bool { +var isDebug = false +assert({ + isDebug = true + return true +}()) +return isDebug +} +} + +extension LibraryViewController { +override func numberOfSections(in tableView: UITableView) -> Int { return 2 } +override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { +switch section { +case 0: + return isFiltering ? filteredSignedApps.count : signedApps?.count ?? 0 +case 1: + return isFiltering ? filteredDownloadedApps.count : downloadedApps?.count ?? 0 +default: + return 0 +} +} + +override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { +switch section { +case 0: + let headerWithButton = GroupedSectionHeader( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS"), + subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL", arguments: String(signedApps?.count ?? 0)), + buttonTitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_BUTTON_IMPORT"), + buttonAction: { + self.startImporting() + }) + return headerWithButton +case 1: + let headerWithButton = GroupedSectionHeader( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS"), + subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL", arguments: String(downloadedApps?.count ?? 0)) + ) + return headerWithButton +default: + return nil +} +} + +override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { +let cell = AppsTableViewCell(style: .subtitle, reuseIdentifier: "RoundedBackgroundCell") +cell.selectionStyle = .default +cell.accessoryType = .disclosureIndicator +cell.backgroundColor = .clear +let source = getApplication(row: indexPath.row, section: indexPath.section) +let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + +if let iconURL = source!.value(forKey: "iconURL") as? String { + let imagePath = filePath!.appendingPathComponent(iconURL) + + if let image = CoreDataManager.shared.loadImage(from: imagePath) { + SectionIcons.sectionImage(to: cell, with: image) + } else { + SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) + } +} else { + SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) +} + +cell.configure(with: source!, filePath: filePath!) +return cell +} + +override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { +let source = getApplication(row: indexPath.row, section: indexPath.section) +let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: true) +let filePath2 = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: false) +let appName = "\((source!.value(forKey: "name") as? String ?? ""))" +switch indexPath.section { +case 0: + if FileManager.default.fileExists(atPath: filePath2!.path) { + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet + + let hasUpdate = (source as? SignedApps)?.value(forKey: "hasUpdate") as? Bool ?? false + + if let signedApp = source as? SignedApps, hasUpdate { + let updateButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), + color: UIColor.tintColor.withAlphaComponent(0.9), + titleColor: UIColor.white + ) + updateButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + self.handleAppUpdate(for: signedApp) + } + } + + let clearButton = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE"), + color: UIColor.quaternarySystemFill, + titleColor: UIColor.tintColor + ) + clearButton.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + CoreDataManager.shared.clearUpdateState(for: signedApp) + self.tableView.reloadRows(at: [indexPath], with: .none) + } + + popupVC.configureButtons([updateButton, clearButton]) + } else { + let button1 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), + color: UIColor.tintColor.withAlphaComponent(0.9) + ) + button1.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + } + + let button4 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN", arguments: appName), + color: UIColor.quaternarySystemFill, + titleColor: UIColor.tintColor + ) + button4.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + if let workspace = LSApplicationWorkspace.default() { + let success = workspace.openApplication(withBundleID: "\((source!.value(forKey: "bundleidentifier") as? String ?? ""))") + if !success { + Debug.shared.log(message: "Unable to open, do you have the app installed?", type: .warning) + } + } + } + + let button3 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN", arguments: appName), + color: UIColor.quaternarySystemFill, + titleColor: UIColor.tintColor + ) + button3.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + if let cert = CoreDataManager.shared.getCurrentCertificate() { + self.present(self.loaderAlert!, animated: true) + + resignApp(certificate: cert, appPath: filePath2!) { success in + if success { + CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in + DispatchQueue.main.async { + self.loaderAlert?.dismiss(animated: true) + Debug.shared.log(message: "Done action??") + self.tableView.reloadRows(at: [indexPath], with: .left) } } - } else { - let alert = UIAlertController( - title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), - message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) - self.present(alert, animated: true) } } + } else { + let alert = UIAlertController( + title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), + message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) + self.present(alert, animated: true) } - - let button2 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), - color: UIColor.quaternarySystemFill, - titleColor: UIColor.tintColor - ) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.shareFile(meow: source!, filePath: filePath?.path ?? "") - } - - popupVC.configureButtons([button1, button4, button3, button2]) - } - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: hasUpdate ? 150.0 : 270.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium() - ] - presentationController.prefersGrabberVisible = true } - - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) } - case 1: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet + + let button2 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), + color: UIColor.quaternarySystemFill, + titleColor: UIColor.tintColor + ) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.shareFile(meow: source!, filePath: filePath?.path ?? "") + } + + popupVC.configureButtons([button1, button4, button3, button2]) + } + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: hasUpdate ? 150.0 : 270.0) + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium() + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } +case 1: + if FileManager.default.fileExists(atPath: filePath2!.path) { + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet + + let signingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let button1 = PopupViewControllerButton( + title: signingData.signingOptions.installAfterSigned + ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) + : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), + color: UIColor.tintColor.withAlphaComponent(0.9)) + button1.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startSigning(meow: source!) + } + + let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + let alertController = UIAlertController( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), + message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), + preferredStyle: .alert + ) - let signingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let button1 = PopupViewControllerButton( - title: signingData.signingOptions.installAfterSigned - ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) - : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: UIColor.tintColor.withAlphaComponent(0.9)) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startSigning(meow: source!) + let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in + self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") } - let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - let alertController = UIAlertController( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), - message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), - preferredStyle: .alert - ) - - let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - } - - let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - self.present(alertController, animated: true, completion: nil) - } - } + let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) - popupVC.configureButtons([button1, button2]) - - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium() - ] - presentationController.prefersGrabberVisible = true - } + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + self.present(alertController, animated: true, completion: nil) } - default: - break } - tableView.deselectRow(at: indexPath, animated: true) - } - - @objc func startSigning(meow: NSManagedObject) { - if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) - } - } - } - - override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) + popupVC.configureButtons([button1, button2]) - let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break - } - completionHandler(true) + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium() + ] + presentationController.prefersGrabberVisible = true } - deleteAction.backgroundColor = UIColor.red - let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) - configuration.performsFirstActionWithFullSwipe = true + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } +default: + break +} - return configuration +tableView.deselectRow(at: indexPath, animated: true) +} + +@objc func startSigning(meow: NSManagedObject) { +if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) } - - override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - - let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] - } - } - - self.present(navigationController, animated: true) - }), - - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") - - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") - } - } - }) - ]) - }) - return configuration +} +} + +override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { +let source = getApplication(row: indexPath.row, section: indexPath.section) + +let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break } + completionHandler(true) } -extension LibraryViewController { - @objc func afetch() { self.fetchSources() } - - func fetchSources() { - signedApps = CoreDataManager.shared.getDatedSignedApps() - downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() +deleteAction.backgroundColor = UIColor.red +let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) +configuration.performsFirstActionWithFullSwipe = true + +return configuration +} + +override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { +let source = getApplication(row: indexPath.row, section: indexPath.section) +let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + +let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] + } + } + + self.present(navigationController, animated: true) + }), - DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") + } } - } + }) + ]) +}) +return configuration +} +} + +extension LibraryViewController { +@objc func afetch() { self.fetchSources() } + +func fetchSources() { +signedApps = CoreDataManager.shared.getDatedSignedApps() +downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + +DispatchQueue.main.async { + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() } - - func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { - if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) +} +} + +func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { +if section == 0 { + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) +} + +if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) +} +return nil +} + +func getApplication(row: Int, section: Int) -> NSManagedObject? { +if isFiltering { + if section == 0 { + if row < filteredSignedApps.count { + return filteredSignedApps[row] } - - if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] } - return nil } - - func getApplication(row: Int, section: Int) -> NSManagedObject? { - if isFiltering { - if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] - } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] - } - } - } else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] - } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] - } - } +} else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] + } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] } - return nil } } +return nil +} +} extension LibraryViewController: UISearchResultsUpdating { - func updateSearchResults(for searchController: UISearchController) { - let searchText = searchController.searchBar.text ?? "" - filterContentForSearchText(searchText) - tableView.reloadData() - } - - private func filterContentForSearchText(_ searchText: String) { - let lowercasedSearchText = searchText.lowercased() - - filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] - - filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] - } +func updateSearchResults(for searchController: UISearchController) { +let searchText = searchController.searchBar.text ?? "" +filterContentForSearchText(searchText) +tableView.reloadData() +} + +private func filterContentForSearchText(_ searchText: String) { +let lowercasedSearchText = searchText.lowercased() + +filteredSignedApps = signedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) +} ?? [] + +filteredDownloadedApps = downloadedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) +} ?? [] +} } extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { @@ -643,6 +833,7 @@ extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate searchController.hidesNavigationBarDuringPresentation = true searchController.searchResultsUpdater = self searchController.delegate = self + searchController.searchBar.delegate = self searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") navigationItem.searchController = searchController definesPresentationContext = true @@ -656,23 +847,4 @@ extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate var searchBarIsEmpty: Bool { return searchController.searchBar.text?.isEmpty ?? true } -} - -func presentLoader() -> UIAlertController { - let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.isUserInteractionEnabled = false - activityIndicator.startAnimating() - - alert.view.addSubview(activityIndicator) - - NSLayoutConstraint.activate([ - alert.view.heightAnchor.constraint(equalToConstant: 95), - alert.view.widthAnchor.constraint(equalToConstant: 95), - activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) - ]) - - return alert } \ No newline at end of file From 9b03101dadee6ee9f2560b82507ac0b140a73664 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 03:38:09 -0400 Subject: [PATCH 359/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 636 ++++++++------------- 1 file changed, 223 insertions(+), 413 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index b103dea7..75a8c45a 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -11,14 +11,14 @@ class PopupViewControllerButton: PopupViewController.PopupButton { self.setTitle(title, for: .normal) self.backgroundColor = color self.setTitleColor(titleColor, for: .normal) - self.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + self.addTarget(self, action: #selector(buttonTappedAction), for: .touchUpInside) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - @objc private func buttonTapped() { + @objc private func buttonTappedAction() { onTap?() } } @@ -373,457 +373,267 @@ extension LibraryViewController { if success { CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) { - let downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - if let downloadedApp = downloadedApps.first(where: { $0.uuid == uuid }) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - signingDataWrapper.signingOptions.installAfterSigned = true - - let originalSignedApp = signedApp - - let ap = SigningsViewController( - signingDataWrapper: signingDataWrapper, - application: downloadedApp, - appsViewController: self - ) - - ap.signingCompletionHandler = { [weak self] success in - if success { - CoreDataManager.shared.deleteAllSignedAppContent(for: originalSignedApp) - self?.fetchSources() - self?.tableView.reloadData() - } - } - - let navigationController = UINavigationController(rootViewController: ap) - - navigationController.shouldPresentFullScreen() - - self.present(navigationController, animated: true) - } - } -} -return -} - -// If no matching version found -Debug.shared.log(message: "Could not find version \(updateVersion) in source", type: .error) -DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) -} -} - -private var isDebugMode: Bool { -var isDebug = false -assert({ - isDebug = true - return true -}()) -return isDebug -} -} - -extension LibraryViewController { -override func numberOfSections(in tableView: UITableView) -> Int { return 2 } -override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { -switch section { -case 0: - return isFiltering ? filteredSignedApps.count : signedApps?.count ?? 0 -case 1: - return isFiltering ? filteredDownloadedApps.count : downloadedApps?.count ?? 0 -default: - return 0 -} -} - -override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { -switch section { -case 0: - let headerWithButton = GroupedSectionHeader( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS"), - subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_SIGNED_APPS_TOTAL", arguments: String(signedApps?.count ?? 0)), - buttonTitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_BUTTON_IMPORT"), - buttonAction: { - self.startImporting() - }) - return headerWithButton -case 1: - let headerWithButton = GroupedSectionHeader( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_DOWNLOADED_APPS"), - subtitle: String.localized("LIBRARY_VIEW_CONTROLLER_SECTION_TITLE_DOWNLOADED_APPS_TOTAL", arguments: String(downloadedApps?.count ?? 0)) - ) - return headerWithButton -default: - return nil -} -} - -override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { -let cell = AppsTableViewCell(style: .subtitle, reuseIdentifier: "RoundedBackgroundCell") -cell.selectionStyle = .default -cell.accessoryType = .disclosureIndicator -cell.backgroundColor = .clear -let source = getApplication(row: indexPath.row, section: indexPath.section) -let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - -if let iconURL = source!.value(forKey: "iconURL") as? String { - let imagePath = filePath!.appendingPathComponent(iconURL) - - if let image = CoreDataManager.shared.loadImage(from: imagePath) { - SectionIcons.sectionImage(to: cell, with: image) - } else { - SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) - } -} else { - SectionIcons.sectionImage(to: cell, with: UIImage(named: "unknown")!) -} - -cell.configure(with: source!, filePath: filePath!) -return cell -} - -override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { -let source = getApplication(row: indexPath.row, section: indexPath.section) -let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: true) -let filePath2 = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section, getuuidonly: false) -let appName = "\((source!.value(forKey: "name") as? String ?? ""))" -switch indexPath.section { -case 0: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet - - let hasUpdate = (source as? SignedApps)?.value(forKey: "hasUpdate") as? Bool ?? false - - if let signedApp = source as? SignedApps, hasUpdate { - let updateButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_UPDATE", arguments: appName), - color: UIColor.tintColor.withAlphaComponent(0.9), - titleColor: UIColor.white - ) - updateButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - self.handleAppUpdate(for: signedApp) - } - } - - let clearButton = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_CLEAR_UPDATE"), - color: UIColor.quaternarySystemFill, - titleColor: UIColor.tintColor - ) - clearButton.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - CoreDataManager.shared.clearUpdateState(for: signedApp) - self.tableView.reloadRows(at: [indexPath], with: .none) - } - - popupVC.configureButtons([updateButton, clearButton]) - } else { - let button1 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), - color: UIColor.tintColor.withAlphaComponent(0.9) - ) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - } - - let button4 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN", arguments: appName), - color: UIColor.quaternarySystemFill, - titleColor: UIColor.tintColor - ) - button4.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - if let workspace = LSApplicationWorkspace.default() { - let success = workspace.openApplication(withBundleID: "\((source!.value(forKey: "bundleidentifier") as? String ?? ""))") - if !success { - Debug.shared.log(message: "Unable to open, do you have the app installed?", type: .warning) - } - } - } - - let button3 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_RESIGN", arguments: appName), - color: UIColor.quaternarySystemFill, - titleColor: UIColor.tintColor - ) - button3.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - if let cert = CoreDataManager.shared.getCurrentCertificate() { - self.present(self.loaderAlert!, animated: true) - - resignApp(certificate: cert, appPath: filePath2!) { success in - if success { - CoreDataManager.shared.updateSignedApp(app: source as! SignedApps, newTimeToLive: (cert.certData?.expirationDate)!, newTeamName: (cert.certData?.name)!) { _ in - DispatchQueue.main.async { - self.loaderAlert?.dismiss(animated: true) - Debug.shared.log(message: "Done action??") - self.tableView.reloadRows(at: [indexPath], with: .left) + self.loaderAlert?.dismiss(animated: true) + Debug.shared.log(message: "Done action??") + self.tableView.reloadRows(at: [indexPath], with: .left) + } + } } } + } else { + let alert = UIAlertController( + title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), + message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) + self.present(alert, animated: true) } } - } else { - let alert = UIAlertController( - title: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_TITLE"), - message: String.localized("APP_SIGNING_VIEW_CONTROLLER_NO_CERTS_ALERT_DESCRIPTION"), + } + + let button2 = PopupViewControllerButton( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), + color: UIColor.quaternarySystemFill, + titleColor: UIColor.tintColor + ) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.shareFile(meow: source!, filePath: filePath?.path ?? "") + } + + popupVC.configureButtons([button1, button4, button3, button2]) + } + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: hasUpdate ? 150.0 : 270.0) + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium() + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + } + case 1: + if FileManager.default.fileExists(atPath: filePath2!.path) { + popupVC = PopupViewController() + popupVC.modalPresentationStyle = .pageSheet + + let signingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let button1 = PopupViewControllerButton( + title: signingData.signingOptions.installAfterSigned + ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) + : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), + color: UIColor.tintColor.withAlphaComponent(0.9)) + button1.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) + self.startSigning(meow: source!) + } + + let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor) + button2.onTap = { [weak self] in + guard let self = self else { return } + self.popupVC.dismiss(animated: true) { + let alertController = UIAlertController( + title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), + message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), preferredStyle: .alert ) - alert.addAction(UIAlertAction(title: String.localized("LAME"), style: .default)) - self.present(alert, animated: true) + + let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in + self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") + } + + let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) + + alertController.addAction(confirmAction) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true, completion: nil) } } + + popupVC.configureButtons([button1, button2]) + + let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) + if let presentationController = popupVC.presentationController as? UISheetPresentationController { + presentationController.detents = [ + detent2, + .medium() + ] + presentationController.prefersGrabberVisible = true + } + + self.present(popupVC, animated: true) + } else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) } - - let button2 = PopupViewControllerButton( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SHARE", arguments: appName), - color: UIColor.quaternarySystemFill, - titleColor: UIColor.tintColor - ) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.shareFile(meow: source!, filePath: filePath?.path ?? "") - } - - popupVC.configureButtons([button1, button4, button3, button2]) - } - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: hasUpdate ? 150.0 : 270.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium() - ] - presentationController.prefersGrabberVisible = true + default: + break } - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + tableView.deselectRow(at: indexPath, animated: true) } -case 1: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet - - let signingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let button1 = PopupViewControllerButton( - title: signingData.signingOptions.installAfterSigned - ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) - : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: UIColor.tintColor.withAlphaComponent(0.9)) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startSigning(meow: source!) + + @objc func startSigning(meow: NSManagedObject) { + if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) + } } + } + + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) - let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - let alertController = UIAlertController( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), - message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), - preferredStyle: .alert - ) - - let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - } - - let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - self.present(alertController, animated: true, completion: nil) + let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break } + completionHandler(true) } - popupVC.configureButtons([button1, button2]) + deleteAction.backgroundColor = UIColor.red + let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) + configuration.performsFirstActionWithFullSwipe = true - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium() - ] - presentationController.prefersGrabberVisible = true - } + return configuration + } + + override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + let source = getApplication(row: indexPath.row, section: indexPath.section) + let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) + let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] + } + } + + self.present(navigationController, animated: true) + }), + + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") + } + } + }) + ]) + }) + return configuration } -default: - break } -tableView.deselectRow(at: indexPath, animated: true) -} +extension LibraryViewController { + @objc func afetch() { self.fetchSources() } -@objc func startSigning(meow: NSManagedObject) { -if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) + func fetchSources() { + signedApps = CoreDataManager.shared.getDatedSignedApps() + downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + + DispatchQueue.main.async { + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() + } + } } -} -} -override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { -let source = getApplication(row: indexPath.row, section: indexPath.section) + func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { + if section == 0 { + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) + } -let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break + if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) + } + return nil } - completionHandler(true) -} -deleteAction.backgroundColor = UIColor.red -let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) -configuration.performsFirstActionWithFullSwipe = true - -return configuration -} - -override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { -let source = getApplication(row: indexPath.row, section: indexPath.section) -let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) - -let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] + func getApplication(row: Int, section: Int) -> NSManagedObject? { + if isFiltering { + if section == 0 { + if row < filteredSignedApps.count { + return filteredSignedApps[row] + } + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] } } - - self.present(navigationController, animated: true) - }), - - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") - - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") + } else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] + } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] } } - }) - ]) -}) -return configuration -} -} - -extension LibraryViewController { -@objc func afetch() { self.fetchSources() } - -func fetchSources() { -signedApps = CoreDataManager.shared.getDatedSignedApps() -downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - -DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() - } -} -} - -func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { -if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) -} - -if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) -} -return nil -} - -func getApplication(row: Int, section: Int) -> NSManagedObject? { -if isFiltering { - if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] - } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] - } - } -} else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] - } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] } + return nil } } -return nil -} -} extension LibraryViewController: UISearchResultsUpdating { -func updateSearchResults(for searchController: UISearchController) { -let searchText = searchController.searchBar.text ?? "" -filterContentForSearchText(searchText) -tableView.reloadData() -} + func updateSearchResults(for searchController: UISearchController) { + let searchText = searchController.searchBar.text ?? "" + filterContentForSearchText(searchText) + tableView.reloadData() + } -private func filterContentForSearchText(_ searchText: String) { -let lowercasedSearchText = searchText.lowercased() + private func filterContentForSearchText(_ searchText: String) { + let lowercasedSearchText = searchText.lowercased() -filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) -} ?? [] + filteredSignedApps = signedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] -filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) -} ?? [] -} + filteredDownloadedApps = downloadedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) + } ?? [] + } } extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { From fd3803a224dcec24afc497a433a5d0cf013691bf Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 03:50:35 -0400 Subject: [PATCH 360/391] Create idk --- .github/workflows/idk | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/idk diff --git a/.github/workflows/idk b/.github/workflows/idk new file mode 100644 index 00000000..77417fd9 --- /dev/null +++ b/.github/workflows/idk @@ -0,0 +1,42 @@ +name: Build iOS Dylib + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install Xcode Command Line Tools + run: xcode-select --install + + - name: Create Framework Project + run: | + mkdir iOSFramework + cd iOSFramework + xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + + - name: Create Framework + run: | + mkdir cpux_lib.framework + mkdir cpux_lib.framework/Headers + cp cpux_lib.h cpux_lib.framework/Headers/ + clang -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -fPIC -c cpux_lib.c -o cpux_lib.o + libtool -static cpux_lib.o -o cpux_lib.framework/cpux_lib + mkdir cpux_lib.framework/Modules + echo 'framework module cpux_lib { header "cpux_lib.h" export * }' > cpux_lib.framework/Modules/module.modulemap + + - name: Upload Framework Artifact + uses: actions/upload-artifact@v3 + with: + name: cpux_lib_framework + path: iOSFramework/cpux_lib.xcframework \ No newline at end of file From 783f6c9696af5b6d29ea21aa25e5f7b50aa647ba Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 03:51:18 -0400 Subject: [PATCH 361/391] Create idk.yml --- .github/workflows/idk.yml | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/idk.yml diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml new file mode 100644 index 00000000..77417fd9 --- /dev/null +++ b/.github/workflows/idk.yml @@ -0,0 +1,42 @@ +name: Build iOS Dylib + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install Xcode Command Line Tools + run: xcode-select --install + + - name: Create Framework Project + run: | + mkdir iOSFramework + cd iOSFramework + xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + + - name: Create Framework + run: | + mkdir cpux_lib.framework + mkdir cpux_lib.framework/Headers + cp cpux_lib.h cpux_lib.framework/Headers/ + clang -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -fPIC -c cpux_lib.c -o cpux_lib.o + libtool -static cpux_lib.o -o cpux_lib.framework/cpux_lib + mkdir cpux_lib.framework/Modules + echo 'framework module cpux_lib { header "cpux_lib.h" export * }' > cpux_lib.framework/Modules/module.modulemap + + - name: Upload Framework Artifact + uses: actions/upload-artifact@v3 + with: + name: cpux_lib_framework + path: iOSFramework/cpux_lib.xcframework \ No newline at end of file From 661f00008bf4f58583fdeec9978ae95012359d4b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 03:56:56 -0400 Subject: [PATCH 362/391] Update idk.yml --- .github/workflows/idk.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 77417fd9..1296f4cb 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -17,26 +17,23 @@ jobs: uses: actions/checkout@v3 - name: Install Xcode Command Line Tools - run: xcode-select --install - - - name: Create Framework Project - run: | - mkdir iOSFramework - cd iOSFramework - xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + run: sudo xcode-select --install - name: Create Framework run: | - mkdir cpux_lib.framework - mkdir cpux_lib.framework/Headers + mkdir -p cpux_lib.framework/Headers cp cpux_lib.h cpux_lib.framework/Headers/ clang -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -fPIC -c cpux_lib.c -o cpux_lib.o libtool -static cpux_lib.o -o cpux_lib.framework/cpux_lib mkdir cpux_lib.framework/Modules echo 'framework module cpux_lib { header "cpux_lib.h" export * }' > cpux_lib.framework/Modules/module.modulemap + - name: Create XCFramework + run: | + xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + - name: Upload Framework Artifact uses: actions/upload-artifact@v3 with: name: cpux_lib_framework - path: iOSFramework/cpux_lib.xcframework \ No newline at end of file + path: cpux_lib.xcframework \ No newline at end of file From e8f243e7b7fcb51f56ce85d859c3a017a0af7f95 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:07:09 -0400 Subject: [PATCH 363/391] Update LibraryViewController.swift --- iOS/Views/Apps/LibraryViewController.swift | 432 ++++++++++----------- 1 file changed, 196 insertions(+), 236 deletions(-) diff --git a/iOS/Views/Apps/LibraryViewController.swift b/iOS/Views/Apps/LibraryViewController.swift index 75a8c45a..e0265de1 100644 --- a/iOS/Views/Apps/LibraryViewController.swift +++ b/iOS/Views/Apps/LibraryViewController.swift @@ -2,15 +2,11 @@ import UIKit import CoreData import UniformTypeIdentifiers -// Ensure PopupViewControllerButton is a subclass of PopupViewController.PopupButton class PopupViewControllerButton: PopupViewController.PopupButton { - var onTap: (() -> Void)? + var onButtonTap: (() -> Void)? init(title: String, color: UIColor, titleColor: UIColor = .white) { - super.init(frame: .zero) - self.setTitle(title, for: .normal) - self.backgroundColor = color - self.setTitleColor(titleColor, for: .normal) + super.init(title: title, color: color, titleColor: titleColor) self.addTarget(self, action: #selector(buttonTappedAction), for: .touchUpInside) } @@ -19,7 +15,7 @@ class PopupViewControllerButton: PopupViewController.PopupButton { } @objc private func buttonTappedAction() { - onTap?() + onButtonTap?() } } @@ -231,6 +227,25 @@ class LibraryViewController: UITableViewController { }()) return isDebug } + + func presentLoader() -> UIAlertController { + let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert) + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.isUserInteractionEnabled = false + activityIndicator.startAnimating() + + alert.view.addSubview(activityIndicator) + + NSLayoutConstraint.activate([ + alert.view.heightAnchor.constraint(equalToConstant: 95), + alert.view.widthAnchor.constraint(equalToConstant: 95), + activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor) + ]) + + return alert + } } extension LibraryViewController { @@ -311,7 +326,7 @@ extension LibraryViewController { color: UIColor.tintColor.withAlphaComponent(0.9), titleColor: UIColor.white ) - updateButton.onTap = { [weak self] in + updateButton.onButtonTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) { self.handleAppUpdate(for: signedApp) @@ -323,7 +338,7 @@ extension LibraryViewController { color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor ) - clearButton.onTap = { [weak self] in + clearButton.onButtonTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) CoreDataManager.shared.clearUpdateState(for: signedApp) @@ -336,7 +351,7 @@ extension LibraryViewController { title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.tintColor.withAlphaComponent(0.9) ) - button1.onTap = { [weak self] in + button1.onButtonTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") @@ -347,7 +362,7 @@ extension LibraryViewController { color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor ) - button4.onTap = { [weak self] in + button4.onButtonTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) if let workspace = LSApplicationWorkspace.default() { @@ -363,7 +378,7 @@ extension LibraryViewController { color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor ) - button3.onTap = { [weak self] in + button3.onButtonTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) { if let cert = CoreDataManager.shared.getCurrentCertificate() { @@ -397,7 +412,7 @@ extension LibraryViewController { color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor ) - button2.onTap = { [weak self] in + button2.onButtonTap = { [weak self] in guard let self = self else { return } self.popupVC.dismiss(animated: true) self.shareFile(meow: source!, filePath: filePath?.path ?? "") @@ -408,253 +423,198 @@ extension LibraryViewController { let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: hasUpdate ? 150.0 : 270.0) if let presentationController = popupVC.presentationController as? UISheetPresentationController { presentationController.detents = [ - detent2, - .medium() - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } - case 1: - if FileManager.default.fileExists(atPath: filePath2!.path) { - popupVC = PopupViewController() - popupVC.modalPresentationStyle = .pageSheet - - let signingData = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let button1 = PopupViewControllerButton( - title: signingData.signingOptions.installAfterSigned - ? String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN_INSTALL", arguments: appName) - : String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_SIGN", arguments: appName), - color: UIColor.tintColor.withAlphaComponent(0.9)) - button1.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) - self.startSigning(meow: source!) - } - - let button2 = PopupViewControllerButton(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL", arguments: appName), color: UIColor.quaternarySystemFill, titleColor: UIColor.tintColor) - button2.onTap = { [weak self] in - guard let self = self else { return } - self.popupVC.dismiss(animated: true) { - let alertController = UIAlertController( - title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM"), - message: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_INSTALL_CONFIRM_DESCRIPTION"), - preferredStyle: .alert - ) - - let confirmAction = UIAlertAction(title: String.localized("INSTALL"), style: .default) { _ in - self.startInstallProcess(meow: source!, filePath: filePath?.path ?? "") - } - - let cancelAction = UIAlertAction(title: String.localized("CANCEL"), style: .cancel, handler: nil) - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - self.present(alertController, animated: true, completion: nil) - } - } - - popupVC.configureButtons([button1, button2]) - - let detent2: UISheetPresentationController.Detent = ._detent(withIdentifier: "Test2", constant: 150.0) - if let presentationController = popupVC.presentationController as? UISheetPresentationController { - presentationController.detents = [ - detent2, - .medium() - ] - presentationController.prefersGrabberVisible = true - } - - self.present(popupVC, animated: true) - } else { - Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) - } - default: - break - } - - tableView.deselectRow(at: indexPath, animated: true) + detent2, + .medium() +] +presentationController.prefersGrabberVisible = true +} + +self.present(popupVC, animated: true) +} else { + Debug.shared.log(message: "The file has been deleted for this entry, please remove it manually.", type: .critical) +} +default: + break +} + +tableView.deselectRow(at: indexPath, animated: true) +} + +@objc func startSigning(meow: NSManagedObject) { +if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { + let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) + let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) + let navigationController = UINavigationController(rootViewController: ap) + navigationController.shouldPresentFullScreen() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.present(navigationController, animated: true, completion: nil) } - - @objc func startSigning(meow: NSManagedObject) { - if FileManager.default.fileExists(atPath: CoreDataManager.shared.getFilesForDownloadedApps(for: meow as! DownloadedApps).path) { - let signingDataWrapper = SigningDataWrapper(signingOptions: UserDefaults.standard.signingOptions) - let ap = SigningsViewController(signingDataWrapper: signingDataWrapper, application: meow, appsViewController: self) - let navigationController = UINavigationController(rootViewController: ap) - navigationController.shouldPresentFullScreen() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.present(navigationController, animated: true, completion: nil) - } - } +} +} + +override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { +let source = getApplication(row: indexPath.row, section: indexPath.section) + +let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in + switch indexPath.section { + case 0: + CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) + self.signedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) + case 1: + CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) + self.downloadedApps?.remove(at: indexPath.row) + self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) + default: + break } - - override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - - let deleteAction = UIContextualAction(style: .destructive, title: String.localized("DELETE")) { (action, view, completionHandler) in - switch indexPath.section { - case 0: - CoreDataManager.shared.deleteAllSignedAppContent(for: source! as! SignedApps) - self.signedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) - case 1: - CoreDataManager.shared.deleteAllDownloadedAppContent(for: source! as! DownloadedApps) - self.downloadedApps?.remove(at: indexPath.row) - self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic) - default: - break + completionHandler(true) +} + +deleteAction.backgroundColor = UIColor.red +let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) +configuration.performsFirstActionWithFullSwipe = true + +return configuration +} + +override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { +let source = getApplication(row: indexPath.row, section: indexPath.section) +let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + +let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in + return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in + let viewController = AppsInformationViewController() + viewController.source = source + viewController.filePath = filePath + let navigationController = UINavigationController(rootViewController: viewController) + + if #available(iOS 15.0, *) { + if let presentationController = navigationController.presentationController as? UISheetPresentationController { + presentationController.detents = [.medium(), .large()] + } } - completionHandler(true) - } - - deleteAction.backgroundColor = UIColor.red - let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) - configuration.performsFirstActionWithFullSwipe = true - - return configuration - } - - override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let source = getApplication(row: indexPath.row, section: indexPath.section) - let filePath = getApplicationFilePath(with: source!, row: indexPath.row, section: indexPath.section) + + self.present(navigationController, animated: true) + }), - let configuration = UIContextMenuConfiguration(identifier: nil, actionProvider: { _ in - return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_VIEW_DETAILS"), image: UIImage(systemName: "info.circle"), handler: { _ in - let viewController = AppsInformationViewController() - viewController.source = source - viewController.filePath = filePath - let navigationController = UINavigationController(rootViewController: viewController) - - if #available(iOS 15.0, *) { - if let presentationController = navigationController.presentationController as? UISheetPresentationController { - presentationController.detents = [.medium(), .large()] - } - } - - self.present(navigationController, animated: true) - }), - - UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in - let path = filePath?.deletingLastPathComponent() - let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") - - UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in - if success { - Debug.shared.log(message: "File opened successfully.") - } else { - Debug.shared.log(message: "Failed to open file.") - } - } - }) - ]) + UIAction(title: String.localized("LIBRARY_VIEW_CONTROLLER_SIGN_ACTION_OPEN_IN_FILES"), image: UIImage(systemName: "folder"), handler: { _ in + let path = filePath?.deletingLastPathComponent() + let path2 = path?.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") + + UIApplication.shared.open(URL(string: path2 ?? "")!, options: [:]) { success in + if success { + Debug.shared.log(message: "File opened successfully.") + } else { + Debug.shared.log(message: "Failed to open file.") + } + } }) - return configuration - } + ]) +}) +return configuration +} } extension LibraryViewController { - @objc func afetch() { self.fetchSources() } +@objc func afetch() { self.fetchSources() } - func fetchSources() { - signedApps = CoreDataManager.shared.getDatedSignedApps() - downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() - - DispatchQueue.main.async { - UIView.animate(withDuration: 0.1) { - self.tableView.reloadData() - } - } +func fetchSources() { +signedApps = CoreDataManager.shared.getDatedSignedApps() +downloadedApps = CoreDataManager.shared.getDatedDownloadedApps() + +DispatchQueue.main.async { + UIView.animate(withDuration: 0.1) { + self.tableView.reloadData() } +} +} - func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { - if section == 0 { - guard let source = getApplication(row: row, section: section) as? SignedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) - } +func getApplicationFilePath(with app: NSManagedObject, row: Int, section: Int, getuuidonly: Bool = false) -> URL? { +if section == 0 { + guard let source = getApplication(row: row, section: section) as? SignedApps else { + return URL(string: "")! + } + return CoreDataManager.shared.getFilesForSignedApps(for: source, getuuidonly: getuuidonly) +} - if section == 1 { - guard let source = getApplication(row: row, section: section) as? DownloadedApps else { - return URL(string: "")! - } - return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) - } - return nil +if section == 1 { + guard let source = getApplication(row: row, section: section) as? DownloadedApps else { + return URL(string: "")! } + return CoreDataManager.shared.getFilesForDownloadedApps(for: source, getuuidonly: getuuidonly) +} +return nil +} - func getApplication(row: Int, section: Int) -> NSManagedObject? { - if isFiltering { - if section == 0 { - if row < filteredSignedApps.count { - return filteredSignedApps[row] - } - } else if section == 1 { - if row < filteredDownloadedApps.count { - return filteredDownloadedApps[row] - } - } - } else { - if section == 0 { - if row < signedApps?.count ?? 0 { - return signedApps?[row] - } - } else if section == 1 { - if row < downloadedApps?.count ?? 0 { - return downloadedApps?[row] - } - } +func getApplication(row: Int, section: Int) -> NSManagedObject? { +if isFiltering { + if section == 0 { + if row < filteredSignedApps.count { + return filteredSignedApps[row] + } + } else if section == 1 { + if row < filteredDownloadedApps.count { + return filteredDownloadedApps[row] + } + } +} else { + if section == 0 { + if row < signedApps?.count ?? 0 { + return signedApps?[row] + } + } else if section == 1 { + if row < downloadedApps?.count ?? 0 { + return downloadedApps?[row] } - return nil } } +return nil +} +} extension LibraryViewController: UISearchResultsUpdating { - func updateSearchResults(for searchController: UISearchController) { - let searchText = searchController.searchBar.text ?? "" - filterContentForSearchText(searchText) - tableView.reloadData() - } +func updateSearchResults(for searchController: UISearchController) { +let searchText = searchController.searchBar.text ?? "" +filterContentForSearchText(searchText) +tableView.reloadData() +} - private func filterContentForSearchText(_ searchText: String) { - let lowercasedSearchText = searchText.lowercased() +private func filterContentForSearchText(_ searchText: String) { +let lowercasedSearchText = searchText.lowercased() - filteredSignedApps = signedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] +filteredSignedApps = signedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) +} ?? [] - filteredDownloadedApps = downloadedApps?.filter { app in - let name = (app.value(forKey: "name") as? String ?? "").lowercased() - return name.contains(lowercasedSearchText) - } ?? [] - } +filteredDownloadedApps = downloadedApps?.filter { app in + let name = (app.value(forKey: "name") as? String ?? "").lowercased() + return name.contains(lowercasedSearchText) +} ?? [] +} } extension LibraryViewController: UISearchControllerDelegate, UISearchBarDelegate { - func setupSearchController() { - searchController = UISearchController(searchResultsController: nil) - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = true - searchController.searchResultsUpdater = self - searchController.delegate = self - searchController.searchBar.delegate = self - searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") - navigationItem.searchController = searchController - definesPresentationContext = true - navigationItem.hidesSearchBarWhenScrolling = false - } - - var isFiltering: Bool { - return searchController.isActive && !searchBarIsEmpty - } +func setupSearchController() { +searchController = UISearchController(searchResultsController: nil) +searchController.obscuresBackgroundDuringPresentation = false +searchController.hidesNavigationBarDuringPresentation = true +searchController.searchResultsUpdater = self +searchController.delegate = self +searchController.searchBar.delegate = self +searchController.searchBar.placeholder = String.localized("SETTINGS_VIEW_CONTROLLER_SEARCH_PLACEHOLDER") +navigationItem.searchController = searchController +definesPresentationContext = true +navigationItem.hidesSearchBarWhenScrolling = false +} - var searchBarIsEmpty: Bool { - return searchController.searchBar.text?.isEmpty ?? true - } +var isFiltering: Bool { +return searchController.isActive && !searchBarIsEmpty +} + +var searchBarIsEmpty: Bool { +return searchController.searchBar.text?.isEmpty ?? true +} } \ No newline at end of file From d267fa174581f468472964352578ce2152cc8d65 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:11:51 -0400 Subject: [PATCH 364/391] Update idk.yml --- .github/workflows/idk.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 1296f4cb..b63256f7 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - main + workflow_dispatch: jobs: build: @@ -33,7 +34,7 @@ jobs: xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework - name: Upload Framework Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 with: name: cpux_lib_framework path: cpux_lib.xcframework \ No newline at end of file From cb5f0fba86891c8269e4f21dd1cdfd8657e19b03 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:13:58 -0400 Subject: [PATCH 365/391] Update idk.yml --- .github/workflows/idk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index b63256f7..9c5f69c5 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -34,7 +34,7 @@ jobs: xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework - name: Upload Framework Artifact - uses: actions/upload-artifact@v3.1.2 + uses: actions/upload-artifact@v3 with: name: cpux_lib_framework path: cpux_lib.xcframework \ No newline at end of file From 930a70e6e029454aae4d18d6a962f74b89bc10bb Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:16:58 -0400 Subject: [PATCH 366/391] Update idk.yml --- .github/workflows/idk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 9c5f69c5..115704d1 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -26,7 +26,7 @@ jobs: cp cpux_lib.h cpux_lib.framework/Headers/ clang -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -fPIC -c cpux_lib.c -o cpux_lib.o libtool -static cpux_lib.o -o cpux_lib.framework/cpux_lib - mkdir cpux_lib.framework/Modules + mkdir -p cpux_lib.framework/Modules echo 'framework module cpux_lib { header "cpux_lib.h" export * }' > cpux_lib.framework/Modules/module.modulemap - name: Create XCFramework From 1e22977d94208d2c19a7aa4da1422438219acb5c Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:24:12 -0400 Subject: [PATCH 367/391] Create BDG --- BDG | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 BDG diff --git a/BDG b/BDG new file mode 100644 index 00000000..900b70d9 --- /dev/null +++ b/BDG @@ -0,0 +1,34 @@ +# BDG file for building iOS dylib and XCFramework + +# Variables +SDK = $(shell xcrun --sdk iphoneos --show-sdk-path) +TARGET = arm64-apple-ios14.0 +LIB_NAME = cpux_lib +FRAMEWORK_NAME = $(LIB_NAME).framework +XCFRAMEWORK_NAME = $(LIB_NAME).xcframework +HEADERS_DIR = $(FRAMEWORK_NAME)/Headers +MODULES_DIR = $(FRAMEWORK_NAME)/Modules + +# Build rules +all: $(XCFRAMEWORK_NAME) + +$(XCFRAMEWORK_NAME): $(FRAMEWORK_NAME) + xcodebuild -create-xcframework -framework $(FRAMEWORK_NAME) -output $(XCFRAMEWORK_NAME) + +$(FRAMEWORK_NAME): $(FRAMEWORK_NAME)/$(LIB_NAME) + mkdir -p $(HEADERS_DIR) + cp $(LIB_NAME).h $(HEADERS_DIR) + mkdir -p $(MODULES_DIR) + echo 'framework module $(LIB_NAME) { header "$(LIB_NAME).h" export * }' > $(MODULES_DIR)/module.modulemap + +$(FRAMEWORK_NAME)/$(LIB_NAME): $(LIB_NAME).o + mkdir -p $(FRAMEWORK_NAME) + libtool -static $< -o $@ + +$(LIB_NAME).o: $(LIB_NAME).c $(LIB_NAME).h + clang -target $(TARGET) -isysroot $(SDK) -fPIC -c $< -o $@ + +clean: + rm -rf $(LIB_NAME).o $(FRAMEWORK_NAME) $(XCFRAMEWORK_NAME) + +.PHONY: all clean \ No newline at end of file From b7b5cfb159b047145ce6ca48b566d1087c7ec57e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:26:08 -0400 Subject: [PATCH 368/391] Update idk.yml --- .github/workflows/idk.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 115704d1..449e2a46 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -31,10 +31,4 @@ jobs: - name: Create XCFramework run: | - xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework - - - name: Upload Framework Artifact - uses: actions/upload-artifact@v3 - with: - name: cpux_lib_framework - path: cpux_lib.xcframework \ No newline at end of file + xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework \ No newline at end of file From 5b26850e0a12a0f40e5cb5b8b75dd5fe284a6e33 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:28:13 -0400 Subject: [PATCH 369/391] Update idk.yml --- .github/workflows/idk.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 449e2a46..31893a72 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -17,9 +17,6 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Install Xcode Command Line Tools - run: sudo xcode-select --install - - name: Create Framework run: | mkdir -p cpux_lib.framework/Headers From e70c9f84aed77e0aaf5914df104018c289deac06 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:35:06 -0400 Subject: [PATCH 370/391] Add files via upload --- cpux_lib.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ cpux_lib.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 cpux_lib.c create mode 100644 cpux_lib.h diff --git a/cpux_lib.c b/cpux_lib.c new file mode 100644 index 00000000..247cbddd --- /dev/null +++ b/cpux_lib.c @@ -0,0 +1,49 @@ +// cpux_lib.c + +#include "cpux_lib.h" +#include +#include +#include + +CPUInfo* getCPUInfo() { + CPUInfo* info = (CPUInfo*)malloc(sizeof(CPUInfo)); + if (!info) return NULL; + + memset(info, 0, sizeof(CPUInfo)); + + size_t size = sizeof(info->model); + if (sysctlbyname("hw.machine", &info->model, &size, NULL, 0) != 0) { + strncpy(info->model, "Unknown", sizeof(info->model) - 1); + } + + int coreCount; + size = sizeof(coreCount); + if (sysctlbyname("hw.ncpu", &coreCount, &size, NULL, 0) == 0){ + info->coreCount = coreCount; + info->threadCount = coreCount; + } + + return info; +} + +void freeCPUInfo(CPUInfo* info) { + free(info); +} + +MemoryInfo* getMemoryInfo() { + MemoryInfo* memInfo = (MemoryInfo*)malloc(sizeof(MemoryInfo)); + if (!memInfo) return NULL; + + int mib[] = {CTL_HW, HW_MEMSIZE}; + size_t length = sizeof(memInfo->totalMemory); + if (sysctl(mib, 2, &memInfo->totalMemory, &length, NULL, 0) != 0) { + free(memInfo); + return NULL; + } + + return memInfo; +} + +void freeMemoryInfo(MemoryInfo* info) { + free(info); +} \ No newline at end of file diff --git a/cpux_lib.h b/cpux_lib.h new file mode 100644 index 00000000..2dc09067 --- /dev/null +++ b/cpux_lib.h @@ -0,0 +1,32 @@ +// cpux_lib.h + +#ifndef CPUX_LIB_H +#define CPUX_LIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include // For uint64_t + +typedef struct { + char model[256]; + uint32_t coreCount; + uint32_t threadCount; +} CPUInfo; + +typedef struct { + uint64_t totalMemory; +} MemoryInfo; + +CPUInfo* getCPUInfo(); +MemoryInfo* getMemoryInfo(); + +void freeCPUInfo(CPUInfo* info); +void freeMemoryInfo(MemoryInfo* info); + +#ifdef __cplusplus +} +#endif + +#endif // CPUX_LIB_H \ No newline at end of file From 39dc733c39c93548b3bee0a7259ff600027d426b Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:39:12 -0400 Subject: [PATCH 371/391] Update idk.yml --- .github/workflows/idk.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 31893a72..abb7c631 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -28,4 +28,10 @@ jobs: - name: Create XCFramework run: | - xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework \ No newline at end of file + xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + + - name: Upload Framework Artifact + uses: actions/upload-artifact@v3 + with: + name: cpux_lib_framework + path: cpux_lib.xcframework \ No newline at end of file From 2b3ba472bd788587cbaf682db795ba1e9b9b1533 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:42:33 -0400 Subject: [PATCH 372/391] Update idk.yml --- .github/workflows/idk.yml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index abb7c631..1f9e3ab0 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -30,8 +30,23 @@ jobs: run: | xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework - - name: Upload Framework Artifact - uses: actions/upload-artifact@v3 + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - name: cpux_lib_framework - path: cpux_lib.xcframework \ No newline at end of file + tag_name: v1.0.0 + release_name: Release v1.0.0 + draft: false + prerelease: false + + - name: Upload Framework to Release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: cpux_lib.xcframework + asset_name: cpux_lib.xcframework + asset_content_type: application/zip \ No newline at end of file From af651559cbc1cbf04c5af3d19c0208e392dd0308 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:43:51 -0400 Subject: [PATCH 373/391] Update idk.yml --- .github/workflows/idk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 1f9e3ab0..188b35e4 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -34,7 +34,7 @@ jobs: id: create_release uses: actions/create-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} with: tag_name: v1.0.0 release_name: Release v1.0.0 From 2e1726f2da3307c3b2cae675b35af5616645c3af Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:48:35 -0400 Subject: [PATCH 374/391] Update idk.yml --- .github/workflows/idk.yml | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 188b35e4..4f6c1fcb 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -30,23 +30,38 @@ jobs: run: | xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} + - name: Set Release Tag + id: set_release_tag + run: echo "RELEASE_TAG=v1.0.0" >> $GITHUB_ENV + + - name: Update Release + id: update_release + uses: actions/github-script@v6 with: - tag_name: v1.0.0 - release_name: Release v1.0.0 - draft: false - prerelease: false + github-token: ${{ secrets.WORKFLOW_SECRET }} + script: | + const { data: release } = await github.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: process.env.RELEASE_TAG, + }); + + await github.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.id, + tag_name: process.env.RELEASE_TAG, + name: `Release ${process.env.RELEASE_TAG}`, + draft: false, + prerelease: false, + }); - name: Upload Framework to Release uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} with: - upload_url: ${{ steps.create_release.outputs.upload_url }} + upload_url: ${{ steps.update_release.outputs.upload_url }} asset_path: cpux_lib.xcframework asset_name: cpux_lib.xcframework asset_content_type: application/zip \ No newline at end of file From 8d4d410d6c9585d85a19b0019e941957efae08fb Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:52:31 -0400 Subject: [PATCH 375/391] Update idk.yml --- .github/workflows/idk.yml | 43 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 4f6c1fcb..669aa331 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -32,36 +32,35 @@ jobs: - name: Set Release Tag id: set_release_tag - run: echo "RELEASE_TAG=v1.0.0" >> $GITHUB_ENV + run: echo "RELEASE_TAG=Dylib" >> $GITHUB_ENV + + - name: Get Release By Tag + id: get_release + uses: octokit/request-action@v2.x + with: + route: GET /repos/${{ github.repository }}/releases/tags/${{ env.RELEASE_TAG }} + env: + WORKFLOW_SECRET: ${{ secrets.WORKFLOW_SECRET }} - name: Update Release id: update_release - uses: actions/github-script@v6 + uses: octokit/request-action@v2.x with: - github-token: ${{ secrets.WORKFLOW_SECRET }} - script: | - const { data: release } = await github.repos.getReleaseByTag({ - owner: context.repo.owner, - repo: context.repo.repo, - tag: process.env.RELEASE_TAG, - }); - - await github.repos.updateRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: release.id, - tag_name: process.env.RELEASE_TAG, - name: `Release ${process.env.RELEASE_TAG}`, - draft: false, - prerelease: false, - }); + route: PATCH /repos/${{ github.repository }}/releases/${{ steps.get_release.outputs.data.id }} + release_id: ${{ steps.get_release.outputs.data.id }} + tag_name: ${{ env.RELEASE_TAG }} + name: Release Dylib + draft: false + prerelease: false + env: + WORKFLOW_SECRET: ${{ secrets.WORKFLOW_SECRET }} - name: Upload Framework to Release uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} with: upload_url: ${{ steps.update_release.outputs.upload_url }} asset_path: cpux_lib.xcframework asset_name: cpux_lib.xcframework - asset_content_type: application/zip \ No newline at end of file + asset_content_type: application/zip + env: + WORKFLOW_SECRET: ${{ secrets.WORKFLOW_SECRET }} \ No newline at end of file From 14165de71ebbce77e59cf045dae738a832954425 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 04:54:32 -0400 Subject: [PATCH 376/391] Update idk.yml --- .github/workflows/idk.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 669aa331..ef955640 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -40,7 +40,7 @@ jobs: with: route: GET /repos/${{ github.repository }}/releases/tags/${{ env.RELEASE_TAG }} env: - WORKFLOW_SECRET: ${{ secrets.WORKFLOW_SECRET }} + GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} - name: Update Release id: update_release @@ -53,7 +53,7 @@ jobs: draft: false prerelease: false env: - WORKFLOW_SECRET: ${{ secrets.WORKFLOW_SECRET }} + GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} - name: Upload Framework to Release uses: actions/upload-release-asset@v1 From d035887410a37eda5446107d9ddbba3ee5d493e2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:03:31 -0400 Subject: [PATCH 377/391] Update idk.yml --- .github/workflows/idk.yml | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index ef955640..31893a72 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -28,39 +28,4 @@ jobs: - name: Create XCFramework run: | - xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework - - - name: Set Release Tag - id: set_release_tag - run: echo "RELEASE_TAG=Dylib" >> $GITHUB_ENV - - - name: Get Release By Tag - id: get_release - uses: octokit/request-action@v2.x - with: - route: GET /repos/${{ github.repository }}/releases/tags/${{ env.RELEASE_TAG }} - env: - GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} - - - name: Update Release - id: update_release - uses: octokit/request-action@v2.x - with: - route: PATCH /repos/${{ github.repository }}/releases/${{ steps.get_release.outputs.data.id }} - release_id: ${{ steps.get_release.outputs.data.id }} - tag_name: ${{ env.RELEASE_TAG }} - name: Release Dylib - draft: false - prerelease: false - env: - GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} - - - name: Upload Framework to Release - uses: actions/upload-release-asset@v1 - with: - upload_url: ${{ steps.update_release.outputs.upload_url }} - asset_path: cpux_lib.xcframework - asset_name: cpux_lib.xcframework - asset_content_type: application/zip - env: - WORKFLOW_SECRET: ${{ secrets.WORKFLOW_SECRET }} \ No newline at end of file + xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework \ No newline at end of file From 99d8ad36fb4a94e1de525eb4580fd3c122bcd463 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:08:19 -0400 Subject: [PATCH 378/391] Update idk.yml --- .github/workflows/idk.yml | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 31893a72..6f7aed0c 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -8,6 +8,8 @@ on: branches: - main workflow_dispatch: + release: + types: [created] jobs: build: @@ -28,4 +30,31 @@ jobs: - name: Create XCFramework run: | - xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework \ No newline at end of file + xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + + release: + runs-on: macos-latest + needs: build + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Create Framework + run: | + mkdir -p cpux_lib.framework/Headers + cp cpux_lib.h cpux_lib.framework/Headers/ + clang -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -fPIC -c cpux_lib.c -o cpux_lib.o + libtool -static cpux_lib.o -o cpux_lib.framework/cpux_lib + mkdir -p cpux_lib.framework/Modules + echo 'framework module cpux_lib { header "cpux_lib.h" export * }' > cpux_lib.framework/Modules/module.modulemap + + - name: Create XCFramework + run: | + xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + + - name: Create Release and Set Tag + run: | + git tag Dylib + git push origin Dylib + gh release create Dylib cpux_lib.xcframework --title "Dylib Release" --notes "Release notes for Dylib" \ No newline at end of file From 579dfaf36686c9edf098d1fcc74eb32b6c7775b2 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:11:40 -0400 Subject: [PATCH 379/391] Update idk.yml --- .github/workflows/idk.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 6f7aed0c..ba86f8dc 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -18,6 +18,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + with: + token: ${{ secrets.WORKFLOW_SECRET }} - name: Create Framework run: | @@ -39,6 +41,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + with: + token: ${{ secrets.WORKFLOW_SECRET }} - name: Create Framework run: | @@ -54,6 +58,8 @@ jobs: xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework - name: Create Release and Set Tag + env: + GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} run: | git tag Dylib git push origin Dylib From 96e452d55f3614562087c7774921db7ee5c82252 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:16:58 -0400 Subject: [PATCH 380/391] Update idk.yml --- .github/workflows/idk.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index ba86f8dc..db2a5d67 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -57,6 +57,13 @@ jobs: run: | xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + - name: Delete Existing Release and Tag + env: + GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} + run: | + gh release delete Dylib -y + git push --delete origin Dylib || true + - name: Create Release and Set Tag env: GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} From 1faa9e1c11536a5d6cd4933189853b90edd98c4d Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:24:38 -0400 Subject: [PATCH 381/391] Update idk.yml --- .github/workflows/idk.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index db2a5d67..0783d14c 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -34,6 +34,10 @@ jobs: run: | xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + - name: Zip XCFramework + run: | + zip -r cpux_lib.xcframework.zip cpux_lib.xcframework + release: runs-on: macos-latest needs: build @@ -57,6 +61,10 @@ jobs: run: | xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + - name: Zip XCFramework + run: | + zip -r cpux_lib.xcframework.zip cpux_lib.xcframework + - name: Delete Existing Release and Tag env: GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} @@ -70,4 +78,4 @@ jobs: run: | git tag Dylib git push origin Dylib - gh release create Dylib cpux_lib.xcframework --title "Dylib Release" --notes "Release notes for Dylib" \ No newline at end of file + gh release create Dylib cpux_lib.xcframework.zip --title "Dylib Release" --notes "Release notes for Dylib" \ No newline at end of file From 8d09cc05397c7d9dfd0b3caae2a874988bec2418 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:28:15 -0400 Subject: [PATCH 382/391] Update idk.yml --- .github/workflows/idk.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 0783d14c..1fc87aad 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -34,6 +34,10 @@ jobs: run: | xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + - name: Zip Framework + run: | + zip -r cpux_lib.framework.zip cpux_lib.framework + - name: Zip XCFramework run: | zip -r cpux_lib.xcframework.zip cpux_lib.xcframework @@ -61,6 +65,10 @@ jobs: run: | xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework + - name: Zip Framework + run: | + zip -r cpux_lib.framework.zip cpux_lib.framework + - name: Zip XCFramework run: | zip -r cpux_lib.xcframework.zip cpux_lib.xcframework @@ -78,4 +86,4 @@ jobs: run: | git tag Dylib git push origin Dylib - gh release create Dylib cpux_lib.xcframework.zip --title "Dylib Release" --notes "Release notes for Dylib" \ No newline at end of file + gh release create Dylib cpux_lib.framework.zip cpux_lib.xcframework.zip --title "Dylib Release" --notes "Release notes for Dylib" \ No newline at end of file From 9e9187d84b4d7903f98ee3339e50224ca94b39b8 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:35:28 -0400 Subject: [PATCH 383/391] Update idk.yml --- .github/workflows/idk.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 1fc87aad..4d5ae0cb 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -21,6 +21,10 @@ jobs: with: token: ${{ secrets.WORKFLOW_SECRET }} + - name: Install Dependencies + run: | + xcode-select --install || true + - name: Create Framework run: | mkdir -p cpux_lib.framework/Headers @@ -42,6 +46,10 @@ jobs: run: | zip -r cpux_lib.xcframework.zip cpux_lib.xcframework + - name: Create Dylib + run: | + clang -dynamiclib -o libcpux.dylib cpux_lib.o -framework Foundation + release: runs-on: macos-latest needs: build @@ -52,6 +60,10 @@ jobs: with: token: ${{ secrets.WORKFLOW_SECRET }} + - name: Install Dependencies + run: | + xcode-select --install || true + - name: Create Framework run: | mkdir -p cpux_lib.framework/Headers @@ -73,6 +85,10 @@ jobs: run: | zip -r cpux_lib.xcframework.zip cpux_lib.xcframework + - name: Create Dylib + run: | + clang -dynamiclib -o libcpux.dylib cpux_lib.o -framework Foundation + - name: Delete Existing Release and Tag env: GITHUB_TOKEN: ${{ secrets.WORKFLOW_SECRET }} @@ -86,4 +102,4 @@ jobs: run: | git tag Dylib git push origin Dylib - gh release create Dylib cpux_lib.framework.zip cpux_lib.xcframework.zip --title "Dylib Release" --notes "Release notes for Dylib" \ No newline at end of file + gh release create Dylib cpux_lib.framework.zip cpux_lib.xcframework.zip libcpux.dylib --title "Dylib Release" --notes "Release notes for Dylib" \ No newline at end of file From 2116d708a0139dd9f8690585daaa345dc7c9f47e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:38:08 -0400 Subject: [PATCH 384/391] Update idk.yml --- .github/workflows/idk.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/idk.yml b/.github/workflows/idk.yml index 4d5ae0cb..b9e2fe9e 100644 --- a/.github/workflows/idk.yml +++ b/.github/workflows/idk.yml @@ -25,30 +25,30 @@ jobs: run: | xcode-select --install || true - - name: Create Framework + - name: Create iOS Framework run: | mkdir -p cpux_lib.framework/Headers cp cpux_lib.h cpux_lib.framework/Headers/ - clang -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -fPIC -c cpux_lib.c -o cpux_lib.o - libtool -static cpux_lib.o -o cpux_lib.framework/cpux_lib + clang -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -fPIC -c cpux_lib.c -o cpux_lib_ios.o + libtool -static cpux_lib_ios.o -o cpux_lib.framework/cpux_lib mkdir -p cpux_lib.framework/Modules echo 'framework module cpux_lib { header "cpux_lib.h" export * }' > cpux_lib.framework/Modules/module.modulemap - - name: Create XCFramework + - name: Create iOS XCFramework run: | xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework - - name: Zip Framework + - name: Zip iOS Framework run: | zip -r cpux_lib.framework.zip cpux_lib.framework - - name: Zip XCFramework + - name: Zip iOS XCFramework run: | zip -r cpux_lib.xcframework.zip cpux_lib.xcframework - - name: Create Dylib + - name: Create iOS Dylib run: | - clang -dynamiclib -o libcpux.dylib cpux_lib.o -framework Foundation + clang -dynamiclib -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -o libcpux.dylib cpux_lib_ios.o -framework Foundation release: runs-on: macos-latest @@ -64,30 +64,30 @@ jobs: run: | xcode-select --install || true - - name: Create Framework + - name: Create iOS Framework run: | mkdir -p cpux_lib.framework/Headers cp cpux_lib.h cpux_lib.framework/Headers/ - clang -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -fPIC -c cpux_lib.c -o cpux_lib.o - libtool -static cpux_lib.o -o cpux_lib.framework/cpux_lib + clang -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -fPIC -c cpux_lib.c -o cpux_lib_ios.o + libtool -static cpux_lib_ios.o -o cpux_lib.framework/cpux_lib mkdir -p cpux_lib.framework/Modules echo 'framework module cpux_lib { header "cpux_lib.h" export * }' > cpux_lib.framework/Modules/module.modulemap - - name: Create XCFramework + - name: Create iOS XCFramework run: | xcodebuild -create-xcframework -framework cpux_lib.framework -output cpux_lib.xcframework - - name: Zip Framework + - name: Zip iOS Framework run: | zip -r cpux_lib.framework.zip cpux_lib.framework - - name: Zip XCFramework + - name: Zip iOS XCFramework run: | zip -r cpux_lib.xcframework.zip cpux_lib.xcframework - - name: Create Dylib + - name: Create iOS Dylib run: | - clang -dynamiclib -o libcpux.dylib cpux_lib.o -framework Foundation + clang -dynamiclib -target arm64-apple-ios14.0 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -o libcpux.dylib cpux_lib_ios.o -framework Foundation - name: Delete Existing Release and Tag env: From 1d4aad77a45319b17c3ee635d19521283d9be96e Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:44:03 -0400 Subject: [PATCH 385/391] Update cpux_lib.c --- cpux_lib.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/cpux_lib.c b/cpux_lib.c index 247cbddd..d8e08730 100644 --- a/cpux_lib.c +++ b/cpux_lib.c @@ -4,6 +4,34 @@ #include #include #include +#include +#include +#include + +#ifdef __APPLE__ +#include +#include +#include +#include +#endif + +// Helper function to handle sysctl errors +static int sysctl_safe(const int* name, u_int namelen, void* oldp, size_t* oldlenp, const void* newp, size_t newlen) { + if (sysctl(name, namelen, oldp, oldlenp, newp, newlen) != 0) { + perror("sysctl failed"); + return -1; + } + return 0; +} + +// Helper function to handle sysctlbyname errors +static int sysctlbyname_safe(const char* name, void* oldp, size_t* oldlenp, const void* newp, size_t newlen) { + if (sysctlbyname(name, oldp, oldlenp, newp, newlen) != 0) { + perror("sysctlbyname failed"); + return -1; + } + return 0; +} CPUInfo* getCPUInfo() { CPUInfo* info = (CPUInfo*)malloc(sizeof(CPUInfo)); @@ -11,18 +39,39 @@ CPUInfo* getCPUInfo() { memset(info, 0, sizeof(CPUInfo)); +#ifdef __APPLE__ size_t size = sizeof(info->model); - if (sysctlbyname("hw.machine", &info->model, &size, NULL, 0) != 0) { + if (sysctlbyname_safe("hw.machine", &info->model, &size, NULL, 0) != 0) { strncpy(info->model, "Unknown", sizeof(info->model) - 1); + info->model[sizeof(info->model) - 1] = '\0'; } - int coreCount; + int coreCount, threadCount; size = sizeof(coreCount); - if (sysctlbyname("hw.ncpu", &coreCount, &size, NULL, 0) == 0){ + if (sysctlbyname_safe("hw.physicalcpu", &coreCount, &size, NULL, 0) == 0) { info->coreCount = coreCount; - info->threadCount = coreCount; + } + size = sizeof(threadCount); + if (sysctlbyname_safe("hw.logicalcpu", &threadCount, &size, NULL, 0) == 0) { + info->threadCount = threadCount; + } + + char cpuBrand[256]; + size = sizeof(cpuBrand); + if (sysctlbyname_safe("machdep.cpu.brand_string", &cpuBrand, &size, NULL, 0) == 0) { + strncpy(info->cpuBrand, cpuBrand, sizeof(info->cpuBrand) - 1); + info->cpuBrand[sizeof(info->cpuBrand) - 1] = '\0'; + } else { + strncpy(info->cpuBrand, "Unknown", sizeof(info->cpuBrand) - 1); + info->cpuBrand[sizeof(info->cpuBrand) - 1] = '\0'; } +#else + strncpy(info->model, "Unknown", sizeof(info->model) - 1); + info->model[sizeof(info->model) - 1] = '\0'; + strncpy(info->cpuBrand, "Unknown", sizeof(info->cpuBrand) - 1); + info->cpuBrand[sizeof(info->cpuBrand) - 1] = '\0'; +#endif return info; } @@ -34,13 +83,25 @@ MemoryInfo* getMemoryInfo() { MemoryInfo* memInfo = (MemoryInfo*)malloc(sizeof(MemoryInfo)); if (!memInfo) return NULL; +#ifdef __APPLE__ int mib[] = {CTL_HW, HW_MEMSIZE}; size_t length = sizeof(memInfo->totalMemory); - if (sysctl(mib, 2, &memInfo->totalMemory, &length, NULL, 0) != 0) { + if (sysctl_safe(mib, 2, &memInfo->totalMemory, &length, NULL, 0) != 0) { free(memInfo); return NULL; } + vm_statistics64_data_t vm_stats; + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vm_stats, &count) == KERN_SUCCESS) { + memInfo->freeMemory = (int64_t)vm_stats.free_count * vm_page_size; + } + +#else + memInfo->totalMemory = 0; + memInfo->freeMemory = 0; +#endif + return memInfo; } From eb7d11f296592cb66456669d77851fcf84f3fa5a Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 05:45:05 -0400 Subject: [PATCH 386/391] Update cpux_lib.h --- cpux_lib.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpux_lib.h b/cpux_lib.h index 2dc09067..940de3af 100644 --- a/cpux_lib.h +++ b/cpux_lib.h @@ -11,12 +11,14 @@ extern "C" { typedef struct { char model[256]; + char cpuBrand[256]; uint32_t coreCount; uint32_t threadCount; } CPUInfo; typedef struct { uint64_t totalMemory; + uint64_t freeMemory; } MemoryInfo; CPUInfo* getCPUInfo(); From 365f02111cc14eb6ccb1b746d24a221cc407f7fb Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 09:38:22 -0400 Subject: [PATCH 387/391] Update feather-Bridging-Header.h --- Shared/Magic/feather-Bridging-Header.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Shared/Magic/feather-Bridging-Header.h b/Shared/Magic/feather-Bridging-Header.h index 09f8d680..cd7fcaa7 100644 --- a/Shared/Magic/feather-Bridging-Header.h +++ b/Shared/Magic/feather-Bridging-Header.h @@ -1,9 +1,11 @@ // -// Use this file to import your target's public headers that you would like to expose to Swift. -// + // Use this file to import your target's public headers that you would like to expose to Swift. + // + + #include "UISheetPresentationControllerDetent+Private.h" + #include "LSApplicationWorkspace.h" -#include "UISheetPresentationControllerDetent+Private.h" -#include "LSApplicationWorkspace.h" + #include "zsign.hpp" + #include "openssl_tools.hpp" -#include "zsign.hpp" -#include "openssl_tools.hpp" + #import "CPUXLib.h" // Added this line \ No newline at end of file From c7d5b2f079ae8c382b6a8d363ff525d9df1a8d01 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 09:42:26 -0400 Subject: [PATCH 388/391] Add files via upload --- Shared/CPU-X/CPUXLib.c | 119 ++++++++++++++++++++++++++++++++++++ Shared/CPU-X/CPUXLib.h | 34 +++++++++++ Shared/CPU-X/InfoProvider.h | 91 +++++++++++++++++++++++++++ Shared/CPU-X/Makefile | 17 ++++++ Shared/CPU-X/Tweak.xm | 80 ++++++++++++++++++++++++ 5 files changed, 341 insertions(+) create mode 100644 Shared/CPU-X/CPUXLib.c create mode 100644 Shared/CPU-X/CPUXLib.h create mode 100644 Shared/CPU-X/InfoProvider.h create mode 100644 Shared/CPU-X/Makefile create mode 100644 Shared/CPU-X/Tweak.xm diff --git a/Shared/CPU-X/CPUXLib.c b/Shared/CPU-X/CPUXLib.c new file mode 100644 index 00000000..cf683b40 --- /dev/null +++ b/Shared/CPU-X/CPUXLib.c @@ -0,0 +1,119 @@ +// CPUXLib.c + + #include "CPUXLib.h" + #include + #include + #include + #include + #include + #include + + #ifdef __APPLE__ + #include + #include + #include + #include + #endif + + // Helper function to handle sysctl errors + static int sysctl_safe(const int* name, u_int namelen, void* oldp, size_t* oldlenp, const void* newp, size_t newlen) { + if (sysctl(name, namelen, oldp, oldlenp, newp, newlen) != 0) { + perror("sysctl failed"); + return -1; + } + return 0; + } + + // Helper function to handle sysctlbyname errors + static int sysctlbyname_safe(const char* name, void* oldp, size_t* oldlenp, const void* newp, size_t newlen) { + if (sysctlbyname(name, oldp, oldlenp, newp, newlen) != 0) { + perror("sysctlbyname failed"); + return -1; + } + return 0; + } + + CPUInfo* getCPUInfo() { + CPUInfo* info = (CPUInfo*)malloc(sizeof(CPUInfo)); + if (!info) { + fprintf(stderr, "Error: Could not allocate memory for CPUInfo.\n"); + return NULL; + } + + memset(info, 0, sizeof(CPUInfo)); + + #ifdef __APPLE__ + size_t size = sizeof(info->model); + if (sysctlbyname_safe("hw.machine", &info->model, &size, NULL, 0) != 0) { + strncpy(info->model, "Unknown", sizeof(info->model) - 1); + info->model[sizeof(info->model) - 1] = '\0'; + } + + int coreCount, threadCount; + size = sizeof(coreCount); + if (sysctlbyname_safe("hw.physicalcpu", &coreCount, &size, NULL, 0) == 0) { + info->coreCount = coreCount; + } + size = sizeof(threadCount); + if (sysctlbyname_safe("hw.logicalcpu", &threadCount, &size, NULL, 0) == 0) { + info->threadCount = threadCount; + } + + char cpuBrand[256]; + size = sizeof(cpuBrand); + if (sysctlbyname_safe("machdep.cpu.brand_string", &cpuBrand, &size, NULL, 0) == 0) { + strncpy(info->cpuBrand, cpuBrand, sizeof(info->cpuBrand) - 1); + info->cpuBrand[sizeof(info->cpuBrand) - 1] = '\0'; + } else { + strncpy(info->cpuBrand, "Unknown", sizeof(info->cpuBrand) - 1); + info->cpuBrand[sizeof(info->cpuBrand) - 1] = '\0'; + } + + #else + strncpy(info->model, "Unknown", sizeof(info->model) - 1); + info->model[sizeof(info->model) - 1] = '\0'; + strncpy(info->cpuBrand, "Unknown", sizeof(info->cpuBrand) - 1); + info->cpuBrand[sizeof(info->cpuBrand) - 1] = '\0'; + #endif + return info; + } + + void freeCPUInfo(CPUInfo* info) { + free(info); + } + + MemoryInfo* getMemoryInfo() { + MemoryInfo* memInfo = (MemoryInfo*)malloc(sizeof(MemoryInfo)); + if (!memInfo) { + fprintf(stderr, "Error: Could not allocate memory for MemoryInfo.\n"); + return NULL; + } + + #ifdef __APPLE__ + int mib= {CTL_HW, HW_MEMSIZE}; + size_t length = sizeof(memInfo->totalMemory); + if (sysctl_safe(mib, 2, &memInfo->totalMemory, &length, NULL, 0) != 0) { + fprintf(stderr, "Error: sysctl failed to get total memory.\n"); + free(memInfo); + return NULL; + } + + vm_statistics64_data_t vm_stats; + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vm_stats, &count) == KERN_SUCCESS) { + memInfo->freeMemory = (int64_t)vm_stats.free_count * vm_page_size; + } else { + fprintf(stderr, "Error: host_statistics64 failed.\n"); + } + + #else + memInfo->totalMemory = 0; + memInfo->freeMemory = 0; + #endif + + return memInfo; + } + + void freeMemoryInfo(MemoryInfo* info) { + free(info); + } \ No newline at end of file diff --git a/Shared/CPU-X/CPUXLib.h b/Shared/CPU-X/CPUXLib.h new file mode 100644 index 00000000..125a0679 --- /dev/null +++ b/Shared/CPU-X/CPUXLib.h @@ -0,0 +1,34 @@ +// CPUXLib.h + + #ifndef CPUX_LIB_H + #define CPUX_LIB_H + + #ifdef __cplusplus + extern "C" { + #endif + + #include + + typedef struct { + char model[256]; + char cpuBrand[256]; + uint32_t coreCount; + uint32_t threadCount; + } CPUInfo; + + typedef struct { + uint64_t totalMemory; + uint64_t freeMemory; + } MemoryInfo; + + CPUInfo* getCPUInfo(); + MemoryInfo* getMemoryInfo(); + + void freeCPUInfo(CPUInfo* info); + void freeMemoryInfo(MemoryInfo* info); + + #ifdef __cplusplus + } + #endif + + #endif /* CPUX_LIB_H */ \ No newline at end of file diff --git a/Shared/CPU-X/InfoProvider.h b/Shared/CPU-X/InfoProvider.h new file mode 100644 index 00000000..d81b474a --- /dev/null +++ b/Shared/CPU-X/InfoProvider.h @@ -0,0 +1,91 @@ +// InfoProvider.m + + #import "InfoProvider.h" + + @interface InfoProvider () + + @property (nonatomic, strong) UIButton *floatingButton; + @property (nonatomic, strong) UIWindow *infoWindow; + + @end + + @implementation InfoProvider + + + (instancetype)sharedProvider { + static InfoProvider *sharedProvider = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedProvider = [[InfoProvider alloc] init]; + }); + return sharedProvider; + } + + - (instancetype)init { + self = [super init]; + if (self) { + [self setupFloatingButton]; + } + return self; + } + + - (void)setupFloatingButton { + self.floatingButton = [UIButton buttonWithType:UIButtonTypeSystem]; + self.floatingButton.frame = CGRectMake(20, 60, 60, 60); + [self.floatingButton setTitle:@"Info" forState:UIControlStateNormal]; + self.floatingButton.backgroundColor = [UIColor colorWithWhite:0.2 alpha:0.8]; + self.floatingButton.layer.cornerRadius = 30; + self.floatingButton.clipsToBounds = YES; + [self.floatingButton addTarget:self action:@selector(toggleInfo) forControlEvents:UIControlEventTouchUpInside]; + self.floatingButton.windowLevel = UIWindowLevelAlert + 1; + + // Find the key window and add the button + UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + if (keyWindow) { + [keyWindow addSubview:self.floatingButton]; + } + } + + - (void)toggleInfo { + if (self.infoWindow) { + [self hideInfo]; + } else { + [self showInfo]; + } + } + + - (void)showInfo { + CPUInfo *cpuInfo = getCPUInfo(); + MemoryInfo *memInfo = getMemoryInfo(); + + self.infoWindow = [[UIWindow alloc] initWithFrame:CGRectMake(80, 80, 250, 200)]; + self.infoWindow.windowLevel = UIWindowLevelAlert; + self.infoWindow.backgroundColor = [UIColor colorWithWhite:0.2 alpha:0.8]; + self.infoWindow.layer.cornerRadius = 10; + self.infoWindow.clipsToBounds = YES; + + UILabel *cpuLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 230, 80)]; + cpuLabel.numberOfLines = 0; + cpuLabel.textColor = [UIColor whiteColor]; + cpuLabel.font = [UIFont systemFontOfSize:14]; + cpuLabel.text = [NSString stringWithFormat:@"Model: %s\nBrand: %s\nCores: %u\nThreads: %u", cpuInfo->model, cpuInfo->cpuBrand, cpuInfo->coreCount, cpuInfo->threadCount]; + [self.infoWindow addSubview:cpuLabel]; + + UILabel *memLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 100, 230, 80)]; + memLabel.numberOfLines = 0; + memLabel.textColor = [UIColor whiteColor]; + memLabel.font = [UIFont systemFontOfSize:14]; + memLabel.text = [NSString stringWithFormat:@"Total Memory: %llu bytes\nFree Memory: %llu bytes", memInfo->totalMemory, memInfo->freeMemory]; + [self.infoWindow addSubview:memLabel]; + + [self.infoWindow makeKeyAndVisible]; + + freeCPUInfo(cpuInfo); + freeMemoryInfo(memInfo); + } + + - (void)hideInfo { + [self.infoWindow removeFromSuperview]; + self.infoWindow = nil; + } + + @end \ No newline at end of file diff --git a/Shared/CPU-X/Makefile b/Shared/CPU-X/Makefile new file mode 100644 index 00000000..776079ee --- /dev/null +++ b/Shared/CPU-X/Makefile @@ -0,0 +1,17 @@ +# Makefile + + TARGET = SystemInfo + + include $(THEOS)/makefiles/common.mk + + TUSD_FRAMEWORKS = UIKit + + ARCHS = iphoneos-arm64 + + include $(THEOS_MAKE_PATH)/tweak.mk + + $(TARGET)_FILES = InfoProvider.m CPUXLib.c + + $(TARGET)_PRIVATE_FRAMEWORKS = CoreGraphics + + INSTALL_TARGET_PROCESSES = SpringBoard \ No newline at end of file diff --git a/Shared/CPU-X/Tweak.xm b/Shared/CPU-X/Tweak.xm new file mode 100644 index 00000000..34451449 --- /dev/null +++ b/Shared/CPU-X/Tweak.xm @@ -0,0 +1,80 @@ +// Tweak.xm + + #import + #import "cpux_lib.h" + + static UIWindow *infoWindow = nil; + static UIButton *floatingButton = nil; + + %hook UIWindow + + - (void)makeKeyAndVisible { + %orig; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [self setupFloatingButton]; + }); + } + + - (void)setupFloatingButton { + floatingButton = [UIButton buttonWithType:UIButtonTypeSystem]; + floatingButton.frame = CGRectMake(20, 60, 60, 60); + [floatingButton setTitle:@"Info" forState:UIControlStateNormal]; + floatingButton.backgroundColor = [UIColor colorWithWhite:0.2 alpha:0.8]; + floatingButton.layer.cornerRadius = 30; + floatingButton.clipsToBounds = YES; + [floatingButton addTarget:self action:@selector(toggleInfo) forControlEvents:UIControlEventTouchUpInside]; + floatingButton.windowLevel = UIWindowLevelAlert + 1; + + // Find the key window and add the button + UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + if (keyWindow) { + [keyWindow addSubview:floatingButton]; + } + } + + - (void)toggleInfo { + if (infoWindow) { + [self hideInfo]; + } else { + [self showInfo]; + } + } + + - (void)showInfo { + CPUInfo *cpuInfo = getCPUInfo(); + MemoryInfo *memInfo = getMemoryInfo(); + + infoWindow = [[UIWindow alloc] initWithFrame:CGRectMake(80, 80, 250, 200)]; + infoWindow.windowLevel = UIWindowLevelAlert; + infoWindow.backgroundColor = [UIColor colorWithWhite:0.2 alpha:0.8]; + infoWindow.layer.cornerRadius = 10; + infoWindow.clipsToBounds = YES; + + UILabel *cpuLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 230, 80)]; + cpuLabel.numberOfLines = 0; + cpuLabel.textColor = [UIColor whiteColor]; + cpuLabel.font = [UIFont systemFontOfSize:14]; + cpuLabel.text = [NSString stringWithFormat:@"Model: %s\nBrand: %s\nCores: %u\nThreads: %u", cpuInfo->model, cpuInfo->cpuBrand, cpuInfo->coreCount, cpuInfo->threadCount]; + [infoWindow addSubview:cpuLabel]; + + UILabel *memLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 100, 230, 80)]; + memLabel.numberOfLines = 0; + memLabel.textColor = [UIColor whiteColor]; + memLabel.font = [UIFont systemFontOfSize:14]; + memLabel.text = [NSString stringWithFormat:@"Total Memory: %llu bytes\nFree Memory: %llu bytes", memInfo->totalMemory, memInfo->freeMemory]; + [infoWindow addSubview:memLabel]; + + [infoWindow makeKeyAndVisible]; + + freeCPUInfo(cpuInfo); + freeMemoryInfo(memInfo); + } + + - (void)hideInfo { + [infoWindow removeFromSuperview]; + infoWindow = nil; + } + + %end \ No newline at end of file From 7c15b451c9efd1cf4efa1d6a190966dd7ab81bda Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 09:44:16 -0400 Subject: [PATCH 389/391] Delete BDG --- BDG | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 BDG diff --git a/BDG b/BDG deleted file mode 100644 index 900b70d9..00000000 --- a/BDG +++ /dev/null @@ -1,34 +0,0 @@ -# BDG file for building iOS dylib and XCFramework - -# Variables -SDK = $(shell xcrun --sdk iphoneos --show-sdk-path) -TARGET = arm64-apple-ios14.0 -LIB_NAME = cpux_lib -FRAMEWORK_NAME = $(LIB_NAME).framework -XCFRAMEWORK_NAME = $(LIB_NAME).xcframework -HEADERS_DIR = $(FRAMEWORK_NAME)/Headers -MODULES_DIR = $(FRAMEWORK_NAME)/Modules - -# Build rules -all: $(XCFRAMEWORK_NAME) - -$(XCFRAMEWORK_NAME): $(FRAMEWORK_NAME) - xcodebuild -create-xcframework -framework $(FRAMEWORK_NAME) -output $(XCFRAMEWORK_NAME) - -$(FRAMEWORK_NAME): $(FRAMEWORK_NAME)/$(LIB_NAME) - mkdir -p $(HEADERS_DIR) - cp $(LIB_NAME).h $(HEADERS_DIR) - mkdir -p $(MODULES_DIR) - echo 'framework module $(LIB_NAME) { header "$(LIB_NAME).h" export * }' > $(MODULES_DIR)/module.modulemap - -$(FRAMEWORK_NAME)/$(LIB_NAME): $(LIB_NAME).o - mkdir -p $(FRAMEWORK_NAME) - libtool -static $< -o $@ - -$(LIB_NAME).o: $(LIB_NAME).c $(LIB_NAME).h - clang -target $(TARGET) -isysroot $(SDK) -fPIC -c $< -o $@ - -clean: - rm -rf $(LIB_NAME).o $(FRAMEWORK_NAME) $(XCFRAMEWORK_NAME) - -.PHONY: all clean \ No newline at end of file From 5942898ca1af72ffe888270e2335846dcda704d3 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 09:44:30 -0400 Subject: [PATCH 390/391] Delete cpux_lib.c --- cpux_lib.c | 110 ----------------------------------------------------- 1 file changed, 110 deletions(-) delete mode 100644 cpux_lib.c diff --git a/cpux_lib.c b/cpux_lib.c deleted file mode 100644 index d8e08730..00000000 --- a/cpux_lib.c +++ /dev/null @@ -1,110 +0,0 @@ -// cpux_lib.c - -#include "cpux_lib.h" -#include -#include -#include -#include -#include -#include - -#ifdef __APPLE__ -#include -#include -#include -#include -#endif - -// Helper function to handle sysctl errors -static int sysctl_safe(const int* name, u_int namelen, void* oldp, size_t* oldlenp, const void* newp, size_t newlen) { - if (sysctl(name, namelen, oldp, oldlenp, newp, newlen) != 0) { - perror("sysctl failed"); - return -1; - } - return 0; -} - -// Helper function to handle sysctlbyname errors -static int sysctlbyname_safe(const char* name, void* oldp, size_t* oldlenp, const void* newp, size_t newlen) { - if (sysctlbyname(name, oldp, oldlenp, newp, newlen) != 0) { - perror("sysctlbyname failed"); - return -1; - } - return 0; -} - -CPUInfo* getCPUInfo() { - CPUInfo* info = (CPUInfo*)malloc(sizeof(CPUInfo)); - if (!info) return NULL; - - memset(info, 0, sizeof(CPUInfo)); - -#ifdef __APPLE__ - size_t size = sizeof(info->model); - if (sysctlbyname_safe("hw.machine", &info->model, &size, NULL, 0) != 0) { - strncpy(info->model, "Unknown", sizeof(info->model) - 1); - info->model[sizeof(info->model) - 1] = '\0'; - } - - int coreCount, threadCount; - size = sizeof(coreCount); - if (sysctlbyname_safe("hw.physicalcpu", &coreCount, &size, NULL, 0) == 0) { - info->coreCount = coreCount; - } - size = sizeof(threadCount); - if (sysctlbyname_safe("hw.logicalcpu", &threadCount, &size, NULL, 0) == 0) { - info->threadCount = threadCount; - } - - char cpuBrand[256]; - size = sizeof(cpuBrand); - if (sysctlbyname_safe("machdep.cpu.brand_string", &cpuBrand, &size, NULL, 0) == 0) { - strncpy(info->cpuBrand, cpuBrand, sizeof(info->cpuBrand) - 1); - info->cpuBrand[sizeof(info->cpuBrand) - 1] = '\0'; - } else { - strncpy(info->cpuBrand, "Unknown", sizeof(info->cpuBrand) - 1); - info->cpuBrand[sizeof(info->cpuBrand) - 1] = '\0'; - } - -#else - strncpy(info->model, "Unknown", sizeof(info->model) - 1); - info->model[sizeof(info->model) - 1] = '\0'; - strncpy(info->cpuBrand, "Unknown", sizeof(info->cpuBrand) - 1); - info->cpuBrand[sizeof(info->cpuBrand) - 1] = '\0'; -#endif - return info; -} - -void freeCPUInfo(CPUInfo* info) { - free(info); -} - -MemoryInfo* getMemoryInfo() { - MemoryInfo* memInfo = (MemoryInfo*)malloc(sizeof(MemoryInfo)); - if (!memInfo) return NULL; - -#ifdef __APPLE__ - int mib[] = {CTL_HW, HW_MEMSIZE}; - size_t length = sizeof(memInfo->totalMemory); - if (sysctl_safe(mib, 2, &memInfo->totalMemory, &length, NULL, 0) != 0) { - free(memInfo); - return NULL; - } - - vm_statistics64_data_t vm_stats; - mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; - if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vm_stats, &count) == KERN_SUCCESS) { - memInfo->freeMemory = (int64_t)vm_stats.free_count * vm_page_size; - } - -#else - memInfo->totalMemory = 0; - memInfo->freeMemory = 0; -#endif - - return memInfo; -} - -void freeMemoryInfo(MemoryInfo* info) { - free(info); -} \ No newline at end of file From 6a1c0207ac9544cfcb9dbe68931da98d0f7f11a4 Mon Sep 17 00:00:00 2001 From: BDGHubNoKey Date: Tue, 18 Mar 2025 09:44:43 -0400 Subject: [PATCH 391/391] Delete cpux_lib.h --- cpux_lib.h | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 cpux_lib.h diff --git a/cpux_lib.h b/cpux_lib.h deleted file mode 100644 index 940de3af..00000000 --- a/cpux_lib.h +++ /dev/null @@ -1,34 +0,0 @@ -// cpux_lib.h - -#ifndef CPUX_LIB_H -#define CPUX_LIB_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include // For uint64_t - -typedef struct { - char model[256]; - char cpuBrand[256]; - uint32_t coreCount; - uint32_t threadCount; -} CPUInfo; - -typedef struct { - uint64_t totalMemory; - uint64_t freeMemory; -} MemoryInfo; - -CPUInfo* getCPUInfo(); -MemoryInfo* getMemoryInfo(); - -void freeCPUInfo(CPUInfo* info); -void freeMemoryInfo(MemoryInfo* info); - -#ifdef __cplusplus -} -#endif - -#endif // CPUX_LIB_H \ No newline at end of file