From 8e1280beda20fa72d611e06cc86da18cfcbc0043 Mon Sep 17 00:00:00 2001 From: mszhangopopop <128134674+mszhangopopop@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:43:39 +0800 Subject: [PATCH 1/6] Update AppListModel.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 中文路径未经过 URL 编码,导致在调用 Filza 打开时无法正确解析并跳转到目标 dylib 文件 --- TrollFools/AppListModel.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/TrollFools/AppListModel.swift b/TrollFools/AppListModel.swift index 1e2b1dd0..cb18adff 100644 --- a/TrollFools/AppListModel.swift +++ b/TrollFools/AppListModel.swift @@ -202,11 +202,20 @@ final class AppListModel: ObservableObject { extension AppListModel { func openInFilza(_ url: URL) { - guard let filzaURL else { + // 获取原始文件路径字符串 + let rawPath = url.path + // 对路径进行百分号编码 + guard let encodedPath = rawPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { + // 如果编码失败,尝试打开 + UIApplication.shared.open(url) return } - let fileURL = filzaURL.appendingPathComponent(url.path) - UIApplication.shared.open(fileURL) + + let finalURLString = "filza://view" + encodedPath + + guard let finalURL = URL(string: finalURLString) else { return } + + UIApplication.shared.open(finalURL) } func rebuildIconCache() { From 3394423f8cd2451afa0bb7cce8778a4c79f59d35 Mon Sep 17 00:00:00 2001 From: mszhangopopop <128134674+mszhangopopop@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:50:39 +0800 Subject: [PATCH 2/6] Update OptionView.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为添加一个兼容iOS 14系统的警告框,解决在 iOS 14 系统上选择注入 deb 文件进行注入时没有反应 --- TrollFools/OptionView.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/TrollFools/OptionView.swift b/TrollFools/OptionView.swift index 286bf95b..f2876f53 100644 --- a/TrollFools/OptionView.swift +++ b/TrollFools/OptionView.swift @@ -64,6 +64,24 @@ struct OptionView: View { } } else { wrappedContent + .alert(isPresented: $isWarningPresented) { + guard case .success(let urls) = temporaryResult else { + return Alert(title: Text("Error")) + } + + return Alert( + title: Text(NSLocalizedString("Notice", comment: "")), + message: Text(Self.warningMessage(urls)), + primaryButton: .destructive(Text(NSLocalizedString("Continue and Don’t Show Again", comment: ""))) { + importerResult = temporaryResult + isImporterSelected = true + isWarningHidden = true + }, + secondaryButton: .cancel() { + temporaryResult = nil + } + ) + } } } From 3e55b4fa49f503cda5ca36d1d7116f3f243d0fc4 Mon Sep 17 00:00:00 2001 From: mszhangopopop <128134674+mszhangopopop@users.noreply.github.com> Date: Fri, 12 Sep 2025 02:41:15 +0800 Subject: [PATCH 3/6] Add files via upload --- TrollFools.xcodeproj/project.pbxproj | 8 ++++++++ .../UserInterfaceState.xcuserstate | Bin 0 -> 166849 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 ++++++ .../xcschemes/xcschememanagement.plist | 19 ++++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 TrollFools.xcodeproj/project.xcworkspace/xcuserdata/z.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 TrollFools.xcodeproj/xcuserdata/z.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 TrollFools.xcodeproj/xcuserdata/z.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/TrollFools.xcodeproj/project.pbxproj b/TrollFools.xcodeproj/project.pbxproj index 42a5291a..afb67922 100644 --- a/TrollFools.xcodeproj/project.pbxproj +++ b/TrollFools.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 192F2BE62E73339A0054FB6C /* UnsupportedAppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192F2BE52E7333960054FB6C /* UnsupportedAppListView.swift */; }; + 199A9C5B2E72C5D900407E7B /* RenameManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199A9C5A2E72C5D600407E7B /* RenameManager.swift */; }; 6124A06D2CD2322400C52253 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6124A06C2CD2322400C52253 /* App.swift */; }; 6124A06F2CD2324200C52253 /* AppListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6124A06E2CD2324200C52253 /* AppListModel.swift */; }; 6124A0712CD2326500C52253 /* FilterOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6124A0702CD2326500C52253 /* FilterOptions.swift */; }; @@ -142,6 +144,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 192F2BE52E7333960054FB6C /* UnsupportedAppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedAppListView.swift; sourceTree = ""; }; + 199A9C5A2E72C5D600407E7B /* RenameManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameManager.swift; sourceTree = ""; }; 6124A06C2CD2322400C52253 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 6124A06E2CD2324200C52253 /* AppListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListModel.swift; sourceTree = ""; }; 6124A0702CD2326500C52253 /* FilterOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterOptions.swift; sourceTree = ""; }; @@ -278,6 +282,7 @@ children = ( 6124A06C2CD2322400C52253 /* App.swift */, CCD3FCA92E7033A300AEF7F0 /* App+Ads.swift */, + 192F2BE52E7333960054FB6C /* UnsupportedAppListView.swift */, 6124A06E2CD2324200C52253 /* AppListModel.swift */, 614C0C672D7C72AC007E9184 /* AppListSearchModel.swift */, 6124A0802CD233DA00C52253 /* EjectListModel.swift */, @@ -436,6 +441,7 @@ isa = PBXGroup; children = ( CC60FBEB2CC50F5A00A6D21A /* BartyCrouch.swift */, + 199A9C5A2E72C5D600407E7B /* RenameManager.swift */, CCF470592C4A4649008D8197 /* TrollFoolsApp.swift */, 6124A0822CD2345300C52253 /* Core */, 6124A0722CD2327E00C52253 /* View */, @@ -657,6 +663,7 @@ 6124A06D2CD2322400C52253 /* App.swift in Sources */, CC15490E2C4B80AF00A4173E /* EjectListView.swift in Sources */, CC60FBEC2CC50F5A00A6D21A /* BartyCrouch.swift in Sources */, + 192F2BE62E73339A0054FB6C /* UnsupportedAppListView.swift in Sources */, 61EFA3772D30403900159442 /* InjectorV3+Metadata.swift in Sources */, CCD3FCAA2E7033A700AEF7F0 /* App+Ads.swift in Sources */, 6124A0762CD232C600C52253 /* Option.swift in Sources */, @@ -679,6 +686,7 @@ CC19E4BB2C561D7300E0F1B5 /* SettingsView.swift in Sources */, 61EFA36F2D30338B00159442 /* InjectorV3+Bundle.swift in Sources */, 61EFA3642D30267E00159442 /* InjectorV3.swift in Sources */, + 199A9C5B2E72C5D900407E7B /* RenameManager.swift in Sources */, 61743CDA2D882CE80032696C /* PlaceholderView.swift in Sources */, 61C7D54C2D82DBAD0064D626 /* DisclaimerView.swift in Sources */, 6124A07F2CD233BA00C52253 /* PlugInCell.swift in Sources */, diff --git a/TrollFools.xcodeproj/project.xcworkspace/xcuserdata/z.xcuserdatad/UserInterfaceState.xcuserstate b/TrollFools.xcodeproj/project.xcworkspace/xcuserdata/z.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..58f9b3c95e4c509b9238ae6ae68304c33b232174 GIT binary patch literal 166849 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 a{O^BVaNhs@&xbDjzyJCF|2;fA>VE*N8lWWr literal 0 HcmV?d00001 diff --git a/TrollFools.xcodeproj/xcuserdata/z.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/TrollFools.xcodeproj/xcuserdata/z.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 00000000..e45076c8 --- /dev/null +++ b/TrollFools.xcodeproj/xcuserdata/z.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/TrollFools.xcodeproj/xcuserdata/z.xcuserdatad/xcschemes/xcschememanagement.plist b/TrollFools.xcodeproj/xcuserdata/z.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..f3fb68e4 --- /dev/null +++ b/TrollFools.xcodeproj/xcuserdata/z.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + TrollFools.xcscheme_^#shared#^_ + + orderHint + 1 + + trollfoolscli.xcscheme_^#shared#^_ + + orderHint + 0 + + + + From f6389cfd9fd8ecf41c0ef62de511d05570dc5ebf Mon Sep 17 00:00:00 2001 From: mszhangopopop <128134674+mszhangopopop@users.noreply.github.com> Date: Fri, 12 Sep 2025 02:42:05 +0800 Subject: [PATCH 4/6] Add files via upload --- TrollFools/App.swift | 10 +- TrollFools/AppListModel.swift | 22 +- TrollFools/AppListView.swift | 20 +- TrollFools/EjectListModel.swift | 1 + TrollFools/EjectListView.swift | 204 ++++++++++++++++--- TrollFools/InjectorV3+Persistent.swift | 6 +- TrollFools/OptionCell.swift | 3 - TrollFools/OptionView.swift | 15 +- TrollFools/PlugInCell.swift | 79 ++++++- TrollFools/RenameManager.swift | 19 ++ TrollFools/UnsupportedAppListView.swift | 36 ++++ TrollFools/Version.Debug.xcconfig | 4 +- TrollFools/Version.xcconfig | 4 +- TrollFools/en.lproj/Localizable.strings | 41 +++- TrollFools/it.lproj/Localizable.strings | 41 +++- TrollFools/vi.lproj/Localizable.strings | 93 +++++---- TrollFools/zh-Hans.lproj/Localizable.strings | 42 +++- 17 files changed, 510 insertions(+), 130 deletions(-) create mode 100644 TrollFools/RenameManager.swift create mode 100644 TrollFools/UnsupportedAppListView.swift diff --git a/TrollFools/App.swift b/TrollFools/App.swift index c363c6ee..a277c4a4 100644 --- a/TrollFools/App.swift +++ b/TrollFools/App.swift @@ -8,7 +8,7 @@ import Combine import Foundation -final class App: Identifiable, ObservableObject { +final class App: Identifiable, ObservableObject, Hashable { let id: String let name: String let latinName: String @@ -89,4 +89,12 @@ final class App: Identifiable, ObservableObject { self.isInjected = InjectorV3.main.checkIsInjectedAppBundle(url) self.hasPersistedAssets = InjectorV3.main.hasPersistedAssets(id: id) } + + static func == (lhs: App, rhs: App) -> Bool { + return lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } } diff --git a/TrollFools/AppListModel.swift b/TrollFools/AppListModel.swift index cb18adff..8bb62048 100644 --- a/TrollFools/AppListModel.swift +++ b/TrollFools/AppListModel.swift @@ -54,6 +54,7 @@ final class AppListModel: ObservableObject { @Published var activeScopeApps: OrderedDictionary = [:] @Published var unsupportedCount: Int = 0 + @Published var unsupportedApps: [App] = [] lazy var isFilzaInstalled: Bool = { if let filzaURL { @@ -105,9 +106,10 @@ final class AppListModel: ObservableObject { } func reload() { - let allApplications = Self.fetchApplications(&unsupportedCount) - allApplications.forEach { $0.appList = self } - _allApplications = allApplications + let (supportedApps, unsupportedApps) = Self.fetchApplications(&unsupportedCount) + supportedApps.forEach { $0.appList = self } + self.unsupportedApps = unsupportedApps + _allApplications = supportedApps performFilter() } @@ -148,7 +150,7 @@ final class AppListModel: ObservableObject { "xyz.willy.Zebra", ] - private static func fetchApplications(_ unsupportedCount: inout Int) -> [App] { + private static func fetchApplications(_ unsupportedCount: inout Int) -> (supported: [App], unsupported: [App]) { let allApps: [App] = LSApplicationWorkspace.default() .allApplications() .compactMap { proxy in @@ -195,18 +197,21 @@ final class AppListModel: ObservableObject { .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } unsupportedCount = allApps.count - filteredApps.count + + let allAppsSet = Set(allApps) + let filteredAppsSet = Set(filteredApps) + let unsupportedAppsList = allAppsSet.subtracting(filteredAppsSet) + .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } - return filteredApps + return (filteredApps, unsupportedAppsList) } } extension AppListModel { func openInFilza(_ url: URL) { - // 获取原始文件路径字符串 let rawPath = url.path - // 对路径进行百分号编码 + guard let encodedPath = rawPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - // 如果编码失败,尝试打开 UIApplication.shared.open(url) return } @@ -219,7 +224,6 @@ extension AppListModel { } func rebuildIconCache() { - // Sadly, we can't call `trollstorehelper` directly because only TrollStore can launch it without error. DispatchQueue.global(qos: .userInitiated).async { LSApplicationWorkspace.default().openApplication(withBundleID: "com.opa334.TrollStore") } diff --git a/TrollFools/AppListView.swift b/TrollFools/AppListView.swift index bf6e0c3a..46e3e3bb 100644 --- a/TrollFools/AppListView.swift +++ b/TrollFools/AppListView.swift @@ -26,6 +26,7 @@ struct AppListView: View { @State var temporaryOpenedURL: URLIdentifiable? = nil @State var latestVersionString: String? + @State private var isUnsupportedSheetPresented = false @AppStorage("isAdvertisementHiddenV2") var isAdvertisementHidden: Bool = false @@ -122,7 +123,8 @@ struct AppListView: View { if Double.random(in: 0 ..< 1) < 0.1 { isAdvertisementHidden = false } - + } + /* CheckUpdateManager.shared.checkUpdateIfNeeded { latestVersion, _ in DispatchQueue.main.async { withAnimation { @@ -130,7 +132,7 @@ struct AppListView: View { } } } - } + */ } var styledNavigationView: some View { @@ -171,6 +173,10 @@ struct AppListView: View { PlaceholderView() } } + + .sheet(isPresented: $isUnsupportedSheetPresented) { + UnsupportedAppListView(unsupportedApps: appList.unsupportedApps, isPresented: $isUnsupportedSheetPresented) + } } var refreshableListView: some View { @@ -299,13 +305,13 @@ struct AppListView: View { else if !appList.filter.isSearching && !appList.filter.showPatchedOnly && !appList.isRebuildNeeded && appList.unsupportedCount > 0 { unsupportedSection } - + /* if #available(iOS 15, *) { if shouldShowAdvertisement { advertisementSection } } - + */ appSections } } @@ -433,7 +439,11 @@ struct AppListView: View { var unsupportedSection: some View { Section { } footer: { - paddedHeaderFooterText(String(format: NSLocalizedString("And %d more unsupported user applications.", comment: ""), appList.unsupportedCount)) + Button { + isUnsupportedSheetPresented = true + } label: { + paddedHeaderFooterText(String(format: NSLocalizedString("And %d more unsupported user applications.", comment: ""), appList.unsupportedCount)) + } } .textCase(.none) .transition(.opacity) diff --git a/TrollFools/EjectListModel.swift b/TrollFools/EjectListModel.swift index b9d3fadf..513dc174 100644 --- a/TrollFools/EjectListModel.swift +++ b/TrollFools/EjectListModel.swift @@ -19,6 +19,7 @@ final class EjectListModel: ObservableObject { @Published var isOkToDisableAll = false @Published var processingPlugIn: InjectedPlugIn? + @Published var plugInToReplace: InjectedPlugIn? private var cancellables = Set() diff --git a/TrollFools/EjectListView.swift b/TrollFools/EjectListView.swift index b0c76021..fca50a7c 100644 --- a/TrollFools/EjectListView.swift +++ b/TrollFools/EjectListView.swift @@ -19,54 +19,94 @@ struct EjectListView: View { @State var isDisablingAll = false @State var isDeletingAll = false @State var isExportingAll = false - @State var isErrorOccurred = false + @State var isReplaceImporterPresented = false + @State var isErrorOccurred: Bool = false + @State private var isReplacing: Bool = false @State var lastError: Error? - @State var isWarningPresented = false + @Environment(\.colorScheme) var colorScheme @StateObject var viewControllerHost = ViewControllerHost() @AppStorage var useWeakReference: Bool @AppStorage var preferMainExecutable: Bool @AppStorage var injectStrategy: InjectorV3.Strategy + @StateObject private var renameManager: RenameManager init(_ app: App) { _ejectList = StateObject(wrappedValue: EjectListModel(app)) _useWeakReference = AppStorage(wrappedValue: true, "UseWeakReference-\(app.id)") _preferMainExecutable = AppStorage(wrappedValue: false, "PreferMainExecutable-\(app.id)") _injectStrategy = AppStorage(wrappedValue: .lexicographic, "InjectStrategy-\(app.id)") + _renameManager = StateObject(wrappedValue: RenameManager(appId: app.id)) + } + + @ViewBuilder + private var loadingView: some View { + ZStack { + Color.primary.opacity(0.2) + .ignoresSafeArea() + + if #available(iOS 15.0, *) { + VStack(spacing: 15) { + ProgressView() + .scaleEffect(1.5) + + Text(NSLocalizedString("Replacing...", comment: "")) + .font(.headline) + } + .padding(25) + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 15)) + .shadow(radius: 10) + } else { + VStack(spacing: 15) { + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .white)) + .scaleEffect(1.5) + + Text(NSLocalizedString("Replacing...", comment: "")) + .font(.headline) + .foregroundColor(.white) + } + .padding(25) + .background(Color.black.opacity(0.75)) + .cornerRadius(15) + .shadow(radius: 10) + } + } + .transition(.opacity) } var body: some View { - if #available(iOS 15, *) { - content - .alert(NSLocalizedString("Eject All", comment: ""), isPresented: $isWarningPresented) { - Button(role: .destructive) { - deleteAll(shouldDesist: true) - } label: { - Text(NSLocalizedString("Confirm", comment: "")) - } - Button(role: .cancel) { - isWarningPresented = false - } label: { - Text(NSLocalizedString("Cancel", comment: "")) + ZStack { + if #available(iOS 15, *) { + content + .alert(NSLocalizedString("Eject All", comment: ""), isPresented: $isWarningPresented) { + Button(role: .destructive) { + deleteAll(shouldDesist: true) + } label: { + Text(NSLocalizedString("Confirm", comment: "")) + } + Button(role: .cancel) { + isWarningPresented = false + } label: { + Text(NSLocalizedString("Cancel", comment: "")) + } + } message: { + Text(NSLocalizedString("Are you sure you want to eject all plug-ins? This action cannot be undone.", comment: "")) } - } message: { - Text(NSLocalizedString("Are you sure you want to eject all plug-ins? This action cannot be undone.", comment: "")) - } - } else { - content + } else { + content + } + + if isReplacing { + loadingView + } } + .animation(.easeOut(duration: 0.2), value: isReplacing) } var content: some View { - refreshableListView - .toolbar { toolbarContent } - .animation(.easeOut, value: isExportingAll) - .quickLookPreview($quickLookExport) - } - - var refreshableListView: some View { Group { if #available(iOS 15, *) { searchableListView @@ -93,6 +133,7 @@ struct EjectListView: View { } } + var searchableListView: some View { Group { if #available(iOS 15, *) { @@ -192,6 +233,40 @@ struct EjectListView: View { togglePlugIn(plugIn) } } + + .fileImporter( + isPresented: $isReplaceImporterPresented, + allowedContentTypes: [ + .init(filenameExtension: "dylib")!, + .init(filenameExtension: "deb")!, + .bundle, + .framework, + .package, + .zip, + ], + allowsMultipleSelection: false + ) { result in + switch result { + case .success(let urls): + if let url = urls.first { + performReplace(with: url) + } + case .failure(let error): + self.lastError = error + self.isErrorOccurred = true + } + } + .onChange(of: ejectList.plugInToReplace) { plugIn in + if plugIn != nil { + isReplaceImporterPresented = true + } + } + + .onChange(of: isReplaceImporterPresented) { isPresented in + if !isPresented { + ejectList.plugInToReplace = nil + } + } } var enableAllButton: some View { @@ -314,9 +389,11 @@ struct EjectListView: View { if #available(iOS 16, *) { PlugInCell(plugin, quickLookExport: $quickLookExport) .environmentObject(ejectList) + .environmentObject(renameManager) } else { PlugInCell(plugin, quickLookExport: $quickLookExport) .environmentObject(ejectList) + .environmentObject(renameManager) .padding(.vertical, 4) } } @@ -611,6 +688,81 @@ struct EjectListView: View { } } + private func performReplace(with newURL: URL) { + guard let plugInToReplace = ejectList.plugInToReplace else { return } + + let wasEnabled = plugInToReplace.isEnabled + var logFileURL: URL? + + self.isReplacing = true + + // let view = viewControllerHost.viewController?.navigationController?.view + //view?.isUserInteractionEnabled = false + + DispatchQueue.global(qos: .userInitiated).async { + defer { + DispatchQueue.main.async { + ejectList.plugInToReplace = nil + ejectList.app.reload() + ejectList.reload() + //view?.isUserInteractionEnabled = true + self.isReplacing = false + } + } + + do { + let injector = try InjectorV3(ejectList.app.url) + logFileURL = injector.latestLogFileURL + if injector.appID.isEmpty { injector.appID = ejectList.app.id } + if injector.teamID.isEmpty { injector.teamID = ejectList.app.teamID } + + injector.useWeakReference = useWeakReference + injector.preferMainExecutable = preferMainExecutable + injector.injectStrategy = injectStrategy + + if wasEnabled { + + try injector.eject([plugInToReplace.url], shouldDesist: true) + DDLogInfo("Successfully ejected old enabled plugin: \(plugInToReplace.url.lastPathComponent)", ddlog: injector.logger) + + try injector.inject([newURL], shouldPersist: true) + DDLogInfo("Successfully injected new plugin: \(newURL.lastPathComponent)", ddlog: injector.logger) + + } else { + + let preparedAssetURLs = try injector.preprocessAssets([newURL]) + guard let newAssetURL = preparedAssetURLs.first else { + throw InjectorV3.Error.generic(NSLocalizedString("No valid plug-ins found in the selected file.", comment: "")) + } + + let oldPersistentURL = injector.persistentPlugInsDirectoryURL.appendingPathComponent(plugInToReplace.url.lastPathComponent) + + if FileManager.default.fileExists(atPath: oldPersistentURL.path) { + try injector.cmdRemove(oldPersistentURL, recursively: injector.checkIsDirectory(oldPersistentURL)) + DDLogInfo("Removed old disabled plugin from persistent storage: \(oldPersistentURL.lastPathComponent)", ddlog: injector.logger) + } + + let newPersistentURL = injector.persistentPlugInsDirectoryURL.appendingPathComponent(newAssetURL.lastPathComponent) + try injector.cmdCopy(from: newAssetURL, to: newPersistentURL, overwrite: true) + try injector.cmdChangeOwner(newPersistentURL, owner: 501, groupOwner: 501, recursively: injector.checkIsDirectory(newPersistentURL)) + DDLogInfo("Copied new plugin to persistent storage: \(newPersistentURL.lastPathComponent)", ddlog: injector.logger) + } + + } catch { + DispatchQueue.main.async { + DDLogError("\(error)", ddlog: InjectorV3.main.logger) + var userInfo: [String: Any] = [NSLocalizedDescriptionKey: error.localizedDescription] + if let logFileURL { + userInfo[NSURLErrorKey] = logFileURL + } + let nsErr = NSError(domain: Constants.gErrorDomain, code: 0, userInfo: userInfo) + lastError = nsErr + isErrorOccurred = true + } + } + } + } + @ViewBuilder private func paddedHeaderFooterText(_ content: String) -> some View { if #available(iOS 15, *) { diff --git a/TrollFools/InjectorV3+Persistent.swift b/TrollFools/InjectorV3+Persistent.swift index 6aaca7f3..35f4a1d0 100644 --- a/TrollFools/InjectorV3+Persistent.swift +++ b/TrollFools/InjectorV3+Persistent.swift @@ -40,11 +40,11 @@ extension InjectorV3 { guard let contents = try? FileManager.default.contentsOfDirectory(atPath: base.path) else { return [] } - return filteredURLs(contents + return contents .sorted { $0.localizedStandardCompare($1) == .orderedAscending } - .map { base.appendingPathComponent($0) }) + .map { base.appendingPathComponent($0) } } - + func hasPersistedAssets(id: String) -> Bool { let base = Self.persistentPlugInsRootURL.appendingPathComponent(id, isDirectory: true) guard let contents = try? FileManager.default.contentsOfDirectory(atPath: base.path) else { diff --git a/TrollFools/OptionCell.swift b/TrollFools/OptionCell.swift index 30512a57..f6b53bf4 100644 --- a/TrollFools/OptionCell.swift +++ b/TrollFools/OptionCell.swift @@ -32,8 +32,6 @@ struct OptionCell: View { .frame(width: 32, height: 32) .foregroundColor(tintColor) .padding(.all, 40) - .accessibilityHidden(true) - if option == .detach, detachCount > 0 { Text("\(detachCount)") .font(.footnote) @@ -56,7 +54,6 @@ struct OptionCell: View { : NSLocalizedString("Manage", comment: "")) .font(.headline) .foregroundColor(tintColor) - .accessibilityHidden(true) } } } diff --git a/TrollFools/OptionView.swift b/TrollFools/OptionView.swift index f2876f53..317d2707 100644 --- a/TrollFools/OptionView.swift +++ b/TrollFools/OptionView.swift @@ -11,18 +11,13 @@ struct OptionView: View { let app: App @Environment(\.verticalSizeClass) var verticalSizeClass - @State var isImporterPresented = false @State var isImporterSelected = false - @State var isWarningPresented = false @State var temporaryResult: Result<[URL], any Error>? - @State var isSettingsPresented = false @State var importerResult: Result<[URL], any Error>? - @State var numberOfPlugIns: Int = 0 - @AppStorage("isWarningHidden") var isWarningHidden: Bool = false @@ -58,7 +53,7 @@ struct OptionView: View { Text(NSLocalizedString("Cancel", comment: "")) } } message: { - if case let .success(urls) = $0 { + if case .success(let urls) = $0 { Text(Self.warningMessage(urls)) } } @@ -132,10 +127,10 @@ struct OptionView: View { NavigationLink(isActive: $isImporterSelected) { if let result = importerResult { switch result { - case let .success(urls): + case .success(let urls): InjectView(app, urlList: urls .sorted(by: { $0.lastPathComponent < $1.lastPathComponent })) - case let .failure(error): + case .failure(let error): FailureView( title: NSLocalizedString("Error", comment: ""), error: error @@ -161,7 +156,7 @@ struct OptionView: View { ) { result in switch result { - case let .success(theSuccess): + case .success(let theSuccess): if !isWarningHidden && theSuccess.contains(where: { $0.pathExtension.lowercased() == "deb" }) { temporaryResult = result isWarningPresented = true @@ -204,7 +199,7 @@ struct OptionView: View { } return String(format: NSLocalizedString("You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing.", comment: ""), firstDylibName) } - + private func recalculatePlugInCount() { var urls = [URL]() urls += InjectorV3.main.injectedAssetURLsInBundle(app.url) diff --git a/TrollFools/PlugInCell.swift b/TrollFools/PlugInCell.swift index afff4013..10540d64 100644 --- a/TrollFools/PlugInCell.swift +++ b/TrollFools/PlugInCell.swift @@ -20,18 +20,26 @@ struct PlugInCell: View { @Environment(\.verticalSizeClass) var verticalSizeClass @Binding var quickLookExport: URL? + + @EnvironmentObject var renameManager: RenameManager + @State private var isRenameSheetPresented = false + @State var isEnabled: Bool = false let plugIn: InjectedPlugIn - init(_ plugIn: InjectedPlugIn, quickLookExport: Binding) { - self.plugIn = plugIn - _quickLookExport = quickLookExport + init (_ plugIn: InjectedPlugIn, quickLookExport: Binding) { + self.plugIn = plugIn + self._quickLookExport = quickLookExport + } + + private var displayName: String { + renameManager.plugInRenames[plugIn.url.lastPathComponent] ?? plugIn.url.lastPathComponent } @available(iOS 15, *) var highlightedName: AttributedString { - let name = plugIn.url.lastPathComponent + let name = displayName var attributedString = AttributedString(name) if let range = attributedString.range(of: ejectList.filter.searchKeyword, options: [.caseInsensitive, .diacriticInsensitive]) { attributedString[range].foregroundColor = .accentColor @@ -70,7 +78,7 @@ struct PlugInCell: View { .font(.headline) .lineLimit(2) } else { - Text(plugIn.url.lastPathComponent) + Text(displayName) .font(.headline) .lineLimit(2) } @@ -88,6 +96,16 @@ struct PlugInCell: View { ejectList.togglePlugIn(plugIn, isEnabled: value) } .contextMenu { + Button { + isRenameSheetPresented = true + } label: { + Label(NSLocalizedString("Rename", comment: ""), systemImage: "pencil") + } + Button { + ejectList.plugInToReplace = plugIn + } label: { + Label(NSLocalizedString("Replace", comment: ""), systemImage: "arrow.triangle.2.circlepath") + } if #available(iOS 16.4, *) { ShareLink(item: plugIn.url) { Label(NSLocalizedString("Export", comment: ""), systemImage: "square.and.arrow.up") @@ -111,8 +129,12 @@ struct PlugInCell: View { } .disabled(!isFilzaInstalled) } + .sheet(isPresented: $isRenameSheetPresented) { + RenameSheetView(isPresented: $isRenameSheetPresented, plugInFilename: plugIn.url.lastPathComponent, currentName: displayName) + .environmentObject(renameManager) + } } - + private func exportPlugIn() { quickLookExport = plugIn.url } @@ -122,4 +144,49 @@ struct PlugInCell: View { private func openInFilza() { ejectList.app.appList?.openInFilza(plugIn.url) } + + private struct RenameSheetView: View { + @Binding var isPresented: Bool + @EnvironmentObject var renameManager: RenameManager + + let plugInFilename: String + let currentName: String + + @State private var newName: String = "" + + var body: some View { + NavigationView { + Form { + Section(header: Text(NSLocalizedString("Custom Name", comment: ""))) { + TextField(NSLocalizedString("Enter new name", comment: ""), text: $newName) + Text(NSLocalizedString("Leave it empty to restore the original name.", comment: "")) + .font(.caption) + .foregroundColor(.secondary) + } + } + .navigationTitle(NSLocalizedString("Rename", comment: "")) + .navigationBarTitleDisplayMode(.inline) + .onAppear { + newName = currentName + } + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button(NSLocalizedString("Cancel", comment: "")) { + isPresented = false + } + } + ToolbarItem(placement: .confirmationAction) { + Button(NSLocalizedString("Save", comment: "")) { + if newName.trimmingCharacters(in: .whitespaces).isEmpty { + renameManager.plugInRenames.removeValue(forKey: plugInFilename) + } else { + renameManager.plugInRenames[plugInFilename] = newName + } + isPresented = false + } + } + } + } + } + } } diff --git a/TrollFools/RenameManager.swift b/TrollFools/RenameManager.swift new file mode 100644 index 00000000..6bc8ee49 --- /dev/null +++ b/TrollFools/RenameManager.swift @@ -0,0 +1,19 @@ +import Foundation +import Combine +import SwiftUI + +final class RenameManager: ObservableObject { + @Published var plugInRenames: [String: String] { + didSet { + storage.wrappedValue = plugInRenames + } + } + + private var storage: CodableStorage<[String: String]> + + init(appId: String) { + let initialStorage = CodableStorage<[String: String]>(key: "PlugInRenames-\(appId)", defaultValue: [:]) + self.storage = initialStorage + self.plugInRenames = initialStorage.wrappedValue + } +} diff --git a/TrollFools/UnsupportedAppListView.swift b/TrollFools/UnsupportedAppListView.swift new file mode 100644 index 00000000..51f8c214 --- /dev/null +++ b/TrollFools/UnsupportedAppListView.swift @@ -0,0 +1,36 @@ +// +// UnsupportedAppListView.swift +// TrollFools +// +// Created by Z on 2025/9/12. +// + +import SwiftUI + +struct UnsupportedAppListView: View { + let unsupportedApps: [App] + @Binding var isPresented: Bool + + init(unsupportedApps: [App], isPresented: Binding) { + self.unsupportedApps = unsupportedApps + self._isPresented = isPresented + } + + var body: some View { + NavigationView { + List(unsupportedApps) { app in + AppListCell(app: app) + } + .listStyle(.insetGrouped) + .navigationTitle(NSLocalizedString("Unsupported Applications", comment: "")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button(NSLocalizedString("Done", comment: "")) { + isPresented = false + } + } + } + } + } +} diff --git a/TrollFools/Version.Debug.xcconfig b/TrollFools/Version.Debug.xcconfig index 67bfb1c9..8e70489c 100644 --- a/TrollFools/Version.Debug.xcconfig +++ b/TrollFools/Version.Debug.xcconfig @@ -8,5 +8,5 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -DEBUG_VERSION = 4.1 -DEBUG_BUILD_NUMBER = 202509111 +DEBUG_VERSION = 4.0 +DEBUG_BUILD_NUMBER = 202509102 diff --git a/TrollFools/Version.xcconfig b/TrollFools/Version.xcconfig index 3888e059..5d51eb75 100644 --- a/TrollFools/Version.xcconfig +++ b/TrollFools/Version.xcconfig @@ -8,5 +8,5 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -VERSION = 4.1 -BUILD_NUMBER = 219 +VERSION = 4.0 +BUILD_NUMBER = 212 diff --git a/TrollFools/en.lproj/Localizable.strings b/TrollFools/en.lproj/Localizable.strings index 5896bc8f..3f741c5d 100644 --- a/TrollFools/en.lproj/Localizable.strings +++ b/TrollFools/en.lproj/Localizable.strings @@ -31,9 +31,6 @@ /* No comment provided by engineer. */ "And %d more unsupported user applications." = "And %d more unsupported user applications."; -/* No comment provided by engineer. */ -"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Are you sure you want to eject all plug-ins? This action cannot be undone."; - /* No comment provided by engineer. */ "Bringing back the most advanced system and security analysis tool." = "Bringing back the most advanced system and security analysis tool."; @@ -124,9 +121,6 @@ /* No comment provided by engineer. */ "If you do not know what these options mean, please do not change them." = "If you do not know what these options mean, please do not change them."; -/* No comment provided by engineer. */ -"Includes Disabled PlugIns" = "Includes Disabled PlugIns"; - /* No comment provided by engineer. */ "Inject" = "Inject"; @@ -166,9 +160,6 @@ /* No comment provided by engineer. */ "Manage" = "Manage"; -/* No comment provided by engineer. */ -"Manage %d Plug-Ins" = "Manage %d Plug-Ins"; - /* No comment provided by engineer. */ "New version %@ available!" = "New version %@ available!"; @@ -297,3 +288,35 @@ /* No comment provided by engineer. */ "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing." = "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing."; + +/* No comment provided by engineer. */ +"Rename" = "Rename"; + +/* No comment provided by engineer. */ +"Replace" = "Replace"; + +/* No comment provided by engineer. */ +"Custom Name" = "Custom Name"; + +/* No comment provided by engineer. */ +"Enter new name" = "Enter new name"; + +/* No comment provided by engineer. */ +"Leave it empty to restore the original name." = "Leave it empty to restore the original name."; + +/* No comment provided by engineer. */ +"Cancel" = "Cancel"; + +/* No comment provided by engineer. */ +"Save" = "Save"; + +/* For the accessibility label on the Manage button */ +"Manage %d Plug-Ins" = "Manage %d Plug-Ins"; + +/* For the confirmation alert when ejecting all plugins */ +"Eject All" = "Eject All"; +"Confirm" = "Confirm"; +"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Are you sure you want to eject all plug-ins? This action cannot be undone."; + +/* For the badge on apps with only disabled plugins */ +"Includes Disabled PlugIns" = "Includes Disabled PlugIns"; diff --git a/TrollFools/it.lproj/Localizable.strings b/TrollFools/it.lproj/Localizable.strings index 387259a5..9232ce7f 100644 --- a/TrollFools/it.lproj/Localizable.strings +++ b/TrollFools/it.lproj/Localizable.strings @@ -31,9 +31,6 @@ /* No comment provided by engineer. */ "And %d more unsupported user applications." = "E %d più non supportate dalle applicazioni."; -/* TODO */ -"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Are you sure you want to eject all plug-ins? This action cannot be undone."; - /* No comment provided by engineer. */ "Bringing back the most advanced system and security analysis tool." = "Portiamo indietro il più avanzato strumento di analisi di sistema e della sicurezza."; @@ -124,9 +121,6 @@ /* No comment provided by engineer. */ "If you do not know what these options mean, please do not change them." = "Se non sai cosa significano queste opzioni, non cambiarle."; -/* TODO */ -"Includes Disabled PlugIns" = "Includes Disabled PlugIns"; - /* No comment provided by engineer. */ "Inject" = "Inietta"; @@ -166,9 +160,6 @@ /* TODO */ "Manage" = "Manage"; -/* TODO */ -"Manage %d Plug-Ins" = "Manage %d Plug-Ins"; - /* TODO */ "New version %@ available!" = "New version %@ available!"; @@ -297,3 +288,35 @@ /* TODO */ "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing." = "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing."; + +/* No comment provided by engineer. */ +"Rename" = "Rinomina"; + +/* No comment provided by engineer. */ +"Replace" = "Sostituisci"; + +/* No comment provided by engineer. */ +"Custom Name" = "Nome personalizzato"; + +/* No comment provided by engineer. */ +"Enter new name" = "Inserisci un nuovo nome"; + +/* No comment provided by engineer. */ +"Leave it empty to restore the original name." = "Lascia vuoto per ripristinare il nome originale."; + +/* No comment provided by engineer. */ +"Cancel" = "Annulla"; + +/* No comment provided by engineer. */ +"Save" = "Salva"; + +/* For the accessibility label on the Manage button */ +"Manage %d Plug-Ins" = "Gestisci %d plugin"; + +/* For the confirmation alert when ejecting all plugins */ +"Eject All" = "Espelli tutto"; +"Confirm" = "Conferma"; +"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Sei sicuro di voler espellere tutti i plugin? Questa azione non può essere annullata."; + +/* For the badge on apps with only disabled plugins */ +"Includes Disabled PlugIns" = "Include plugin disabilitati"; diff --git a/TrollFools/vi.lproj/Localizable.strings b/TrollFools/vi.lproj/Localizable.strings index d7c064fd..bb485f4a 100644 --- a/TrollFools/vi.lproj/Localizable.strings +++ b/TrollFools/vi.lproj/Localizable.strings @@ -14,31 +14,28 @@ "Advanced Settings" = "Cài đặt nâng cao"; /* No comment provided by engineer. */ -"Advertisement" = "Quảng Cáo"; +"Advertisement" = "Advertisement"; /* TODO */ -"After the app upgrade, any injected plugins will be disabled. You will need to manually re-enable them." = "Sau khi nâng cấp ứng dụng, bất kỳ plugin nào đã được tiêm sẽ bị vô hiệu hóa. Bạn sẽ cần phải kích hoạt lại chúng một cách thủ công."; +"After the app upgrade, any injected plugins will be disabled. You will need to manually re-enable them." = "After the app upgrade, any injected plugins will be disabled. You will need to manually re-enable them."; /* TODO */ -"All" = "Tất cả"; +"All" = "All"; /* TODO */ -"All Applications" = "Tất cả ứng dụng"; +"All Applications" = "All Applications"; /* TODO */ -"An awesome music visualizer." = "Một trình tạo hình ảnh âm nhạc tuyệt vời."; +"An awesome music visualizer." = "An awesome music visualizer."; /* No comment provided by engineer. */ "And %d more unsupported user applications." = "Có %d ứng dụng người dùng không được hỗ trợ."; /* TODO */ -"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Bạn có chắc chắn muốn gỡ bỏ tất cả plug-in không? Hành động này không thể hoàn tác."; +"Bringing back the most advanced system and security analysis tool." = "Bringing back the most advanced system and security analysis tool."; /* TODO */ -"Bringing back the most advanced system and security analysis tool." = "Mang trở lại công cụ phân tích hệ thống và bảo mật tiên tiến nhất."; - -/* TODO */ -"Buy our paid products to support us if you like TrollFools!" = "Mua sản phẩm trả phí của chúng tôi để ủng hộ chúng tôi nếu bạn thích TrollFools!"; +"Buy our paid products to support us if you like TrollFools!" = "Buy our paid products to support us if you like TrollFools!"; /* No comment provided by engineer. */ "Cancel" = "Hủy"; @@ -53,10 +50,10 @@ "Confirm" = "Xác nhận"; /* TODO */ -"Continue" = "Tiếp tục"; +"Continue" = "Continue"; /* TODO */ -"Continue and Don’t Show Again" = "Tiếp tục và không hiển thị lại"; +"Continue and Don’t Show Again" = "Continue and Don’t Show Again"; /* No comment provided by engineer. */ "Copy" = "Sao chép"; @@ -65,7 +62,7 @@ "Copyright" = "Bản quyền"; /* TODO */ -"Disable All" = "Vô hiệu hóa tất cả"; +"Disable All" = "Disable All"; /* No comment provided by engineer. */ "Do you want to clear this log file “%@”?" = "Bạn có muốn xóa tệp nhật ký này “%@”?"; @@ -86,10 +83,10 @@ "Error" = "Lỗi"; /* TODO */ -"Export" = "Xuất ra"; +"Export" = "Export"; /* TODO */ -"Export All" = "Xuất tất cả"; +"Export All" = "Export All"; /* No comment provided by engineer. */ "Failed" = "Thất bại"; @@ -110,23 +107,20 @@ "Fast" = "Tiêm nhanh"; /* TODO */ -"Fast, feature-rich VNC server for iOS: remote control made simple." = "Máy chủ VNC nhanh, nhiều tính năng cho iOS: điều khiển từ xa trở nên đơn giản."; +"Fast, feature-rich VNC server for iOS: remote control made simple." = "Fast, feature-rich VNC server for iOS: remote control made simple."; /* No comment provided by engineer. */ "Filza (URL Scheme) Not Installed" = "Filza (URL Scheme) chưa được cài đặt."; /* TODO */ -"Full-Fledged Automation Framework for TrollStore." = "Framework tự động hóa toàn diện cho TrollStore."; +"Full-Fledged Automation Framework for TrollStore." = "Full-Fledged Automation Framework for TrollStore."; /* No comment provided by engineer. */ -"Hide" = "Ẩn"; +"Hide" = "Hide"; /* No comment provided by engineer. */ "If you do not know what these options mean, please do not change them." = "Nếu bạn không biết các tùy chọn này có ý nghĩa gì, đừng thay đổi chúng."; -/* TODO */ -"Includes Disabled PlugIns" = "Bao gồm PlugIns bị vô hiệu hóa"; - /* No comment provided by engineer. */ "Inject" = "Tiêm"; @@ -149,7 +143,7 @@ "Launch" = "Mở ứng dụng"; /* TODO */ -"Lessica, huami1314, iosdump and other contributors" = "Lessica, huami1314, iosdump và các cộng tác viên khác"; +"Lessica, huami1314, iosdump and other contributors" = "Lessica, huami1314, iosdump and other contributors"; /* TODO */ "Letterpress" = "Letterpress"; @@ -164,19 +158,16 @@ "Log Viewer" = "Trình xem nhật ký"; /* TODO */ -"Manage" = "Quản lý"; - -/* TODO */ -"Manage %d Plug-Ins" = "Quản lý %d Plug-Ins"; +"Manage" = "Manage"; /* TODO */ -"New version %@ available!" = "Phiên bản mới %@ có sẵn!"; +"New version %@ available!" = "New version %@ available!"; /* No comment provided by engineer. */ -"No Applications" = "Không có ứng dụng nào"; +"No Applications" = "No Applications"; /* TODO */ -"No dylib found in the Debian package." = "Không tìm thấy dylib trong gói Debian."; +"No dylib found in the Debian package." = "No dylib found in the Debian package."; /* No comment provided by engineer. */ "No eligible framework found." = "Không tìm thấy framework đủ điều kiện."; @@ -191,7 +182,7 @@ "No valid plug-ins found." = "Không tìm thấy plug-in hợp lệ."; /* TODO */ -"Notice" = "Lưu Ý"; +"Notice" = "Notice"; /* No comment provided by engineer. */ "Only removable system applications are eligible and listed." = "Chỉ các ứng dụng hệ thống có thể gỡ bỏ và đủ điều kiện tiêm mới được liệt kê."; @@ -218,7 +209,7 @@ "Rebuild Icon Cache" = "Làm mới bộ nhớ đệm biểu tượng"; /* TODO */ -"Record your phone calls like never before." = "Ghi âm cuộc gọi điện thoại của Bạn chưa từng có trước đây."; +"Record your phone calls like never before." = "Record your phone calls like never before."; /* No comment provided by engineer. */ "Reveil" = "Reveil"; @@ -230,7 +221,7 @@ "Search…" = "Tìm kiếm…"; /* TODO */ -"Select an application to view details." = "Chọn một ứng dụng để xem chi tiết."; +"Select an application to view details." = "Select an application to view details."; /* No comment provided by engineer. */ "Select Application to Inject" = "Chọn ứng dụng để tiêm"; @@ -254,7 +245,7 @@ "The content of text file “%@” is empty." = "Nội dung của tệp văn bản “%@” trống."; /* TODO */ -"This is an advertisement." = "Đây là một quảng cáo."; +"This is an advertisement." = "This is an advertisement."; /* No comment provided by engineer. */ "TrollFools" = "TrollFools"; @@ -272,7 +263,7 @@ "TrollVNC" = "TrollVNC"; /* TODO */ -"Unable to locate the data archive in the Debian package." = "Không thể tìm thấy kho lưu trữ dữ liệu trong gói Debian."; +"Unable to locate the data archive in the Debian package." = "Unable to locate the data archive in the Debian package."; /* No comment provided by engineer. */ "Unlock Version" = "Mở khóa phiên bản"; @@ -296,4 +287,36 @@ "You need to rebuild the icon cache in TrollStore to apply changes." = "Bạn cần làm mới bộ nhớ đệm biểu tượng trong TrollStore để áp dụng các thay đổi."; /* TODO */ -"You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing." = "Bạn đã chọn ít nhất một Gói Debian “%@”. Chúng tôi xin nhắc bạn rằng gói này sẽ không hoạt động như khi sử dụng trong môi trường đã bẻ khóa. Vui lòng đảm bảo bạn biết rõ mình đang làm gì."; +"You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing." = "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing."; + +/* No comment provided by engineer. */ +"Rename" = "Đổi tên"; + +/* No comment provided by engineer. */ +"Replace" = "Thay thế"; + +/* No comment provided by engineer. */ +"Custom Name" = "Tên tùy chỉnh"; + +/* No comment provided by engineer. */ +"Enter new name" = "Nhập tên mới"; + +/* No comment provided by engineer. */ +"Leave it empty to restore the original name." = "Để trống để khôi phục tên gốc."; + +/* No comment provided by engineer. */ +"Cancel" = "Hủy"; + +/* No comment provided by engineer. */ +"Save" = "Lưu"; + +/* For the accessibility label on the Manage button */ +"Manage %d Plug-Ins" = "Quan ly %d plugin"; + +/* For the confirmation alert when ejecting all plugins */ +"Eject All" = "Go bo tat ca"; +"Confirm" = "Xac nhan"; +"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Ban co chac chan muon go bo tat ca cac plugin khong? Hanh dong nay khong the hoan tac."; + +/* For the badge on apps with only disabled plugins */ +"Includes Disabled PlugIns" = "Bao gom cac plugin da bi vo hieu hoa"; diff --git a/TrollFools/zh-Hans.lproj/Localizable.strings b/TrollFools/zh-Hans.lproj/Localizable.strings index d92a41de..2d0a150b 100644 --- a/TrollFools/zh-Hans.lproj/Localizable.strings +++ b/TrollFools/zh-Hans.lproj/Localizable.strings @@ -31,9 +31,6 @@ /* No comment provided by engineer. */ "And %d more unsupported user applications." = "另有 %d 个不支持的用户应用。"; -/* No comment provided by engineer. */ -"Are you sure you want to eject all plug-ins? This action cannot be undone." = "你确定要推出所有插件吗?此操作无法撤销。"; - /* No comment provided by engineer. */ "Bringing back the most advanced system and security analysis tool." = "最强大的系统和安全分析工具,再次归来。"; @@ -119,14 +116,11 @@ "Full-Fledged Automation Framework for TrollStore." = "适用于 TrollStore 的全功能自动化框架。"; /* No comment provided by engineer. */ -"Hide" = "隐藏"; +"Hide" = "Hide"; /* No comment provided by engineer. */ "If you do not know what these options mean, please do not change them." = "如果你不知道这些选项的含义,请不要更改。"; -/* No comment provided by engineer. */ -"Includes Disabled PlugIns" = "包含已禁用的插件"; - /* No comment provided by engineer. */ "Inject" = "注入"; @@ -166,9 +160,6 @@ /* No comment provided by engineer. */ "Manage" = "管理"; -/* No comment provided by engineer. */ -"Manage %d Plug-Ins" = "管理 %d 个插件"; - /* No comment provided by engineer. */ "New version %@ available!" = "有新版本 %@ 可用!"; @@ -297,3 +288,34 @@ /* No comment provided by engineer. */ "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing." = "你至少选择了一个 Debian 软件包 “%@”。请留意,它通常不会像在越狱环境当中那样工作,甚至完全不起作用。请确保你知道自己在做些什么。"; + +/* No comment provided by engineer. */ +"Rename" = "重命名"; + +/* No comment provided by engineer. */ +"Replace" = "替换"; + +/* No comment provided by engineer. */ +"Custom Name" = "自定义名称"; + +/* No comment provided by engineer. */ +"Enter new name" = "输入新名称"; + +/* No comment provided by engineer. */ +"Leave it empty to restore the original name." = "留空以恢复原始名称。"; + +/* No comment provided by engineer. */ +"Cancel" = "取消"; + +/* No comment provided by engineer. */ +"Save" = "保存"; +/* For the accessibility label on the Manage button */ +"Manage %d Plug-Ins" = "管理 %d 个插件"; + +/* For the confirmation alert when ejecting all plugins */ +"Eject All" = "清空所有插件"; +"Confirm" = "确认"; +"Are you sure you want to eject all plug-ins? This action cannot be undone." = "你确定要清空所有插件吗?此操作无法撤销。"; + +/* For the badge on apps with only disabled plugins */ +"Includes Disabled PlugIns" = "包含已禁用的插件"; From fa20b0fddc90605135a031a9ce672d500bbeeaf2 Mon Sep 17 00:00:00 2001 From: mszhangopopop <128134674+mszhangopopop@users.noreply.github.com> Date: Fri, 12 Sep 2025 02:53:12 +0800 Subject: [PATCH 5/6] Add files via upload --- TrollFools.xcodeproj/project.pbxproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/TrollFools.xcodeproj/project.pbxproj b/TrollFools.xcodeproj/project.pbxproj index afb67922..42a5291a 100644 --- a/TrollFools.xcodeproj/project.pbxproj +++ b/TrollFools.xcodeproj/project.pbxproj @@ -7,8 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 192F2BE62E73339A0054FB6C /* UnsupportedAppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192F2BE52E7333960054FB6C /* UnsupportedAppListView.swift */; }; - 199A9C5B2E72C5D900407E7B /* RenameManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199A9C5A2E72C5D600407E7B /* RenameManager.swift */; }; 6124A06D2CD2322400C52253 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6124A06C2CD2322400C52253 /* App.swift */; }; 6124A06F2CD2324200C52253 /* AppListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6124A06E2CD2324200C52253 /* AppListModel.swift */; }; 6124A0712CD2326500C52253 /* FilterOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6124A0702CD2326500C52253 /* FilterOptions.swift */; }; @@ -144,8 +142,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 192F2BE52E7333960054FB6C /* UnsupportedAppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedAppListView.swift; sourceTree = ""; }; - 199A9C5A2E72C5D600407E7B /* RenameManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameManager.swift; sourceTree = ""; }; 6124A06C2CD2322400C52253 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 6124A06E2CD2324200C52253 /* AppListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListModel.swift; sourceTree = ""; }; 6124A0702CD2326500C52253 /* FilterOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterOptions.swift; sourceTree = ""; }; @@ -282,7 +278,6 @@ children = ( 6124A06C2CD2322400C52253 /* App.swift */, CCD3FCA92E7033A300AEF7F0 /* App+Ads.swift */, - 192F2BE52E7333960054FB6C /* UnsupportedAppListView.swift */, 6124A06E2CD2324200C52253 /* AppListModel.swift */, 614C0C672D7C72AC007E9184 /* AppListSearchModel.swift */, 6124A0802CD233DA00C52253 /* EjectListModel.swift */, @@ -441,7 +436,6 @@ isa = PBXGroup; children = ( CC60FBEB2CC50F5A00A6D21A /* BartyCrouch.swift */, - 199A9C5A2E72C5D600407E7B /* RenameManager.swift */, CCF470592C4A4649008D8197 /* TrollFoolsApp.swift */, 6124A0822CD2345300C52253 /* Core */, 6124A0722CD2327E00C52253 /* View */, @@ -663,7 +657,6 @@ 6124A06D2CD2322400C52253 /* App.swift in Sources */, CC15490E2C4B80AF00A4173E /* EjectListView.swift in Sources */, CC60FBEC2CC50F5A00A6D21A /* BartyCrouch.swift in Sources */, - 192F2BE62E73339A0054FB6C /* UnsupportedAppListView.swift in Sources */, 61EFA3772D30403900159442 /* InjectorV3+Metadata.swift in Sources */, CCD3FCAA2E7033A700AEF7F0 /* App+Ads.swift in Sources */, 6124A0762CD232C600C52253 /* Option.swift in Sources */, @@ -686,7 +679,6 @@ CC19E4BB2C561D7300E0F1B5 /* SettingsView.swift in Sources */, 61EFA36F2D30338B00159442 /* InjectorV3+Bundle.swift in Sources */, 61EFA3642D30267E00159442 /* InjectorV3.swift in Sources */, - 199A9C5B2E72C5D900407E7B /* RenameManager.swift in Sources */, 61743CDA2D882CE80032696C /* PlaceholderView.swift in Sources */, 61C7D54C2D82DBAD0064D626 /* DisclaimerView.swift in Sources */, 6124A07F2CD233BA00C52253 /* PlugInCell.swift in Sources */, From 72f4d662b7a6a15d6d3ecbb5fc4abdcfa881fd3d Mon Sep 17 00:00:00 2001 From: mszhangopopop <128134674+mszhangopopop@users.noreply.github.com> Date: Fri, 12 Sep 2025 02:54:32 +0800 Subject: [PATCH 6/6] Add files via upload --- TrollFools/App.swift | 10 +- TrollFools/AppListModel.swift | 31 +-- TrollFools/AppListView.swift | 20 +- TrollFools/EjectListModel.swift | 1 - TrollFools/EjectListView.swift | 204 +++---------------- TrollFools/InjectorV3+Persistent.swift | 6 +- TrollFools/OptionCell.swift | 3 + TrollFools/OptionView.swift | 33 +-- TrollFools/PlugInCell.swift | 79 +------ TrollFools/Version.Debug.xcconfig | 4 +- TrollFools/Version.xcconfig | 4 +- TrollFools/en.lproj/Localizable.strings | 41 +--- TrollFools/it.lproj/Localizable.strings | 41 +--- TrollFools/vi.lproj/Localizable.strings | 41 +--- TrollFools/zh-Hans.lproj/Localizable.strings | 42 +--- 15 files changed, 104 insertions(+), 456 deletions(-) diff --git a/TrollFools/App.swift b/TrollFools/App.swift index a277c4a4..c363c6ee 100644 --- a/TrollFools/App.swift +++ b/TrollFools/App.swift @@ -8,7 +8,7 @@ import Combine import Foundation -final class App: Identifiable, ObservableObject, Hashable { +final class App: Identifiable, ObservableObject { let id: String let name: String let latinName: String @@ -89,12 +89,4 @@ final class App: Identifiable, ObservableObject, Hashable { self.isInjected = InjectorV3.main.checkIsInjectedAppBundle(url) self.hasPersistedAssets = InjectorV3.main.hasPersistedAssets(id: id) } - - static func == (lhs: App, rhs: App) -> Bool { - return lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } } diff --git a/TrollFools/AppListModel.swift b/TrollFools/AppListModel.swift index 8bb62048..1e2b1dd0 100644 --- a/TrollFools/AppListModel.swift +++ b/TrollFools/AppListModel.swift @@ -54,7 +54,6 @@ final class AppListModel: ObservableObject { @Published var activeScopeApps: OrderedDictionary = [:] @Published var unsupportedCount: Int = 0 - @Published var unsupportedApps: [App] = [] lazy var isFilzaInstalled: Bool = { if let filzaURL { @@ -106,10 +105,9 @@ final class AppListModel: ObservableObject { } func reload() { - let (supportedApps, unsupportedApps) = Self.fetchApplications(&unsupportedCount) - supportedApps.forEach { $0.appList = self } - self.unsupportedApps = unsupportedApps - _allApplications = supportedApps + let allApplications = Self.fetchApplications(&unsupportedCount) + allApplications.forEach { $0.appList = self } + _allApplications = allApplications performFilter() } @@ -150,7 +148,7 @@ final class AppListModel: ObservableObject { "xyz.willy.Zebra", ] - private static func fetchApplications(_ unsupportedCount: inout Int) -> (supported: [App], unsupported: [App]) { + private static func fetchApplications(_ unsupportedCount: inout Int) -> [App] { let allApps: [App] = LSApplicationWorkspace.default() .allApplications() .compactMap { proxy in @@ -197,33 +195,22 @@ final class AppListModel: ObservableObject { .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } unsupportedCount = allApps.count - filteredApps.count - - let allAppsSet = Set(allApps) - let filteredAppsSet = Set(filteredApps) - let unsupportedAppsList = allAppsSet.subtracting(filteredAppsSet) - .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } - return (filteredApps, unsupportedAppsList) + return filteredApps } } extension AppListModel { func openInFilza(_ url: URL) { - let rawPath = url.path - - guard let encodedPath = rawPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - UIApplication.shared.open(url) + guard let filzaURL else { return } - - let finalURLString = "filza://view" + encodedPath - - guard let finalURL = URL(string: finalURLString) else { return } - - UIApplication.shared.open(finalURL) + let fileURL = filzaURL.appendingPathComponent(url.path) + UIApplication.shared.open(fileURL) } func rebuildIconCache() { + // Sadly, we can't call `trollstorehelper` directly because only TrollStore can launch it without error. DispatchQueue.global(qos: .userInitiated).async { LSApplicationWorkspace.default().openApplication(withBundleID: "com.opa334.TrollStore") } diff --git a/TrollFools/AppListView.swift b/TrollFools/AppListView.swift index 46e3e3bb..bf6e0c3a 100644 --- a/TrollFools/AppListView.swift +++ b/TrollFools/AppListView.swift @@ -26,7 +26,6 @@ struct AppListView: View { @State var temporaryOpenedURL: URLIdentifiable? = nil @State var latestVersionString: String? - @State private var isUnsupportedSheetPresented = false @AppStorage("isAdvertisementHiddenV2") var isAdvertisementHidden: Bool = false @@ -123,8 +122,7 @@ struct AppListView: View { if Double.random(in: 0 ..< 1) < 0.1 { isAdvertisementHidden = false } - } - /* + CheckUpdateManager.shared.checkUpdateIfNeeded { latestVersion, _ in DispatchQueue.main.async { withAnimation { @@ -132,7 +130,7 @@ struct AppListView: View { } } } - */ + } } var styledNavigationView: some View { @@ -173,10 +171,6 @@ struct AppListView: View { PlaceholderView() } } - - .sheet(isPresented: $isUnsupportedSheetPresented) { - UnsupportedAppListView(unsupportedApps: appList.unsupportedApps, isPresented: $isUnsupportedSheetPresented) - } } var refreshableListView: some View { @@ -305,13 +299,13 @@ struct AppListView: View { else if !appList.filter.isSearching && !appList.filter.showPatchedOnly && !appList.isRebuildNeeded && appList.unsupportedCount > 0 { unsupportedSection } - /* + if #available(iOS 15, *) { if shouldShowAdvertisement { advertisementSection } } - */ + appSections } } @@ -439,11 +433,7 @@ struct AppListView: View { var unsupportedSection: some View { Section { } footer: { - Button { - isUnsupportedSheetPresented = true - } label: { - paddedHeaderFooterText(String(format: NSLocalizedString("And %d more unsupported user applications.", comment: ""), appList.unsupportedCount)) - } + paddedHeaderFooterText(String(format: NSLocalizedString("And %d more unsupported user applications.", comment: ""), appList.unsupportedCount)) } .textCase(.none) .transition(.opacity) diff --git a/TrollFools/EjectListModel.swift b/TrollFools/EjectListModel.swift index 513dc174..b9d3fadf 100644 --- a/TrollFools/EjectListModel.swift +++ b/TrollFools/EjectListModel.swift @@ -19,7 +19,6 @@ final class EjectListModel: ObservableObject { @Published var isOkToDisableAll = false @Published var processingPlugIn: InjectedPlugIn? - @Published var plugInToReplace: InjectedPlugIn? private var cancellables = Set() diff --git a/TrollFools/EjectListView.swift b/TrollFools/EjectListView.swift index fca50a7c..b0c76021 100644 --- a/TrollFools/EjectListView.swift +++ b/TrollFools/EjectListView.swift @@ -19,94 +19,54 @@ struct EjectListView: View { @State var isDisablingAll = false @State var isDeletingAll = false @State var isExportingAll = false - @State var isReplaceImporterPresented = false - @State var isErrorOccurred: Bool = false - @State private var isReplacing: Bool = false + @State var isErrorOccurred = false @State var lastError: Error? + @State var isWarningPresented = false - @Environment(\.colorScheme) var colorScheme @StateObject var viewControllerHost = ViewControllerHost() @AppStorage var useWeakReference: Bool @AppStorage var preferMainExecutable: Bool @AppStorage var injectStrategy: InjectorV3.Strategy - @StateObject private var renameManager: RenameManager init(_ app: App) { _ejectList = StateObject(wrappedValue: EjectListModel(app)) _useWeakReference = AppStorage(wrappedValue: true, "UseWeakReference-\(app.id)") _preferMainExecutable = AppStorage(wrappedValue: false, "PreferMainExecutable-\(app.id)") _injectStrategy = AppStorage(wrappedValue: .lexicographic, "InjectStrategy-\(app.id)") - _renameManager = StateObject(wrappedValue: RenameManager(appId: app.id)) - } - - @ViewBuilder - private var loadingView: some View { - ZStack { - Color.primary.opacity(0.2) - .ignoresSafeArea() - - if #available(iOS 15.0, *) { - VStack(spacing: 15) { - ProgressView() - .scaleEffect(1.5) - - Text(NSLocalizedString("Replacing...", comment: "")) - .font(.headline) - } - .padding(25) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 15)) - .shadow(radius: 10) - } else { - VStack(spacing: 15) { - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .white)) - .scaleEffect(1.5) - - Text(NSLocalizedString("Replacing...", comment: "")) - .font(.headline) - .foregroundColor(.white) - } - .padding(25) - .background(Color.black.opacity(0.75)) - .cornerRadius(15) - .shadow(radius: 10) - } - } - .transition(.opacity) } var body: some View { - ZStack { - if #available(iOS 15, *) { - content - .alert(NSLocalizedString("Eject All", comment: ""), isPresented: $isWarningPresented) { - Button(role: .destructive) { - deleteAll(shouldDesist: true) - } label: { - Text(NSLocalizedString("Confirm", comment: "")) - } - Button(role: .cancel) { - isWarningPresented = false - } label: { - Text(NSLocalizedString("Cancel", comment: "")) - } - } message: { - Text(NSLocalizedString("Are you sure you want to eject all plug-ins? This action cannot be undone.", comment: "")) + if #available(iOS 15, *) { + content + .alert(NSLocalizedString("Eject All", comment: ""), isPresented: $isWarningPresented) { + Button(role: .destructive) { + deleteAll(shouldDesist: true) + } label: { + Text(NSLocalizedString("Confirm", comment: "")) } - } else { - content - } - - if isReplacing { - loadingView - } + Button(role: .cancel) { + isWarningPresented = false + } label: { + Text(NSLocalizedString("Cancel", comment: "")) + } + } message: { + Text(NSLocalizedString("Are you sure you want to eject all plug-ins? This action cannot be undone.", comment: "")) + } + } else { + content } - .animation(.easeOut(duration: 0.2), value: isReplacing) } var content: some View { + refreshableListView + .toolbar { toolbarContent } + .animation(.easeOut, value: isExportingAll) + .quickLookPreview($quickLookExport) + } + + var refreshableListView: some View { Group { if #available(iOS 15, *) { searchableListView @@ -133,7 +93,6 @@ struct EjectListView: View { } } - var searchableListView: some View { Group { if #available(iOS 15, *) { @@ -233,40 +192,6 @@ struct EjectListView: View { togglePlugIn(plugIn) } } - - .fileImporter( - isPresented: $isReplaceImporterPresented, - allowedContentTypes: [ - .init(filenameExtension: "dylib")!, - .init(filenameExtension: "deb")!, - .bundle, - .framework, - .package, - .zip, - ], - allowsMultipleSelection: false - ) { result in - switch result { - case .success(let urls): - if let url = urls.first { - performReplace(with: url) - } - case .failure(let error): - self.lastError = error - self.isErrorOccurred = true - } - } - .onChange(of: ejectList.plugInToReplace) { plugIn in - if plugIn != nil { - isReplaceImporterPresented = true - } - } - - .onChange(of: isReplaceImporterPresented) { isPresented in - if !isPresented { - ejectList.plugInToReplace = nil - } - } } var enableAllButton: some View { @@ -389,11 +314,9 @@ struct EjectListView: View { if #available(iOS 16, *) { PlugInCell(plugin, quickLookExport: $quickLookExport) .environmentObject(ejectList) - .environmentObject(renameManager) } else { PlugInCell(plugin, quickLookExport: $quickLookExport) .environmentObject(ejectList) - .environmentObject(renameManager) .padding(.vertical, 4) } } @@ -688,81 +611,6 @@ struct EjectListView: View { } } - private func performReplace(with newURL: URL) { - guard let plugInToReplace = ejectList.plugInToReplace else { return } - - let wasEnabled = plugInToReplace.isEnabled - var logFileURL: URL? - - self.isReplacing = true - - // let view = viewControllerHost.viewController?.navigationController?.view - //view?.isUserInteractionEnabled = false - - DispatchQueue.global(qos: .userInitiated).async { - defer { - DispatchQueue.main.async { - ejectList.plugInToReplace = nil - ejectList.app.reload() - ejectList.reload() - //view?.isUserInteractionEnabled = true - self.isReplacing = false - } - } - - do { - let injector = try InjectorV3(ejectList.app.url) - logFileURL = injector.latestLogFileURL - if injector.appID.isEmpty { injector.appID = ejectList.app.id } - if injector.teamID.isEmpty { injector.teamID = ejectList.app.teamID } - - injector.useWeakReference = useWeakReference - injector.preferMainExecutable = preferMainExecutable - injector.injectStrategy = injectStrategy - - if wasEnabled { - - try injector.eject([plugInToReplace.url], shouldDesist: true) - DDLogInfo("Successfully ejected old enabled plugin: \(plugInToReplace.url.lastPathComponent)", ddlog: injector.logger) - - try injector.inject([newURL], shouldPersist: true) - DDLogInfo("Successfully injected new plugin: \(newURL.lastPathComponent)", ddlog: injector.logger) - - } else { - - let preparedAssetURLs = try injector.preprocessAssets([newURL]) - guard let newAssetURL = preparedAssetURLs.first else { - throw InjectorV3.Error.generic(NSLocalizedString("No valid plug-ins found in the selected file.", comment: "")) - } - - let oldPersistentURL = injector.persistentPlugInsDirectoryURL.appendingPathComponent(plugInToReplace.url.lastPathComponent) - - if FileManager.default.fileExists(atPath: oldPersistentURL.path) { - try injector.cmdRemove(oldPersistentURL, recursively: injector.checkIsDirectory(oldPersistentURL)) - DDLogInfo("Removed old disabled plugin from persistent storage: \(oldPersistentURL.lastPathComponent)", ddlog: injector.logger) - } - - let newPersistentURL = injector.persistentPlugInsDirectoryURL.appendingPathComponent(newAssetURL.lastPathComponent) - try injector.cmdCopy(from: newAssetURL, to: newPersistentURL, overwrite: true) - try injector.cmdChangeOwner(newPersistentURL, owner: 501, groupOwner: 501, recursively: injector.checkIsDirectory(newPersistentURL)) - DDLogInfo("Copied new plugin to persistent storage: \(newPersistentURL.lastPathComponent)", ddlog: injector.logger) - } - - } catch { - DispatchQueue.main.async { - DDLogError("\(error)", ddlog: InjectorV3.main.logger) - var userInfo: [String: Any] = [NSLocalizedDescriptionKey: error.localizedDescription] - if let logFileURL { - userInfo[NSURLErrorKey] = logFileURL - } - let nsErr = NSError(domain: Constants.gErrorDomain, code: 0, userInfo: userInfo) - lastError = nsErr - isErrorOccurred = true - } - } - } - } - @ViewBuilder private func paddedHeaderFooterText(_ content: String) -> some View { if #available(iOS 15, *) { diff --git a/TrollFools/InjectorV3+Persistent.swift b/TrollFools/InjectorV3+Persistent.swift index 35f4a1d0..6aaca7f3 100644 --- a/TrollFools/InjectorV3+Persistent.swift +++ b/TrollFools/InjectorV3+Persistent.swift @@ -40,11 +40,11 @@ extension InjectorV3 { guard let contents = try? FileManager.default.contentsOfDirectory(atPath: base.path) else { return [] } - return contents + return filteredURLs(contents .sorted { $0.localizedStandardCompare($1) == .orderedAscending } - .map { base.appendingPathComponent($0) } + .map { base.appendingPathComponent($0) }) } - + func hasPersistedAssets(id: String) -> Bool { let base = Self.persistentPlugInsRootURL.appendingPathComponent(id, isDirectory: true) guard let contents = try? FileManager.default.contentsOfDirectory(atPath: base.path) else { diff --git a/TrollFools/OptionCell.swift b/TrollFools/OptionCell.swift index f6b53bf4..30512a57 100644 --- a/TrollFools/OptionCell.swift +++ b/TrollFools/OptionCell.swift @@ -32,6 +32,8 @@ struct OptionCell: View { .frame(width: 32, height: 32) .foregroundColor(tintColor) .padding(.all, 40) + .accessibilityHidden(true) + if option == .detach, detachCount > 0 { Text("\(detachCount)") .font(.footnote) @@ -54,6 +56,7 @@ struct OptionCell: View { : NSLocalizedString("Manage", comment: "")) .font(.headline) .foregroundColor(tintColor) + .accessibilityHidden(true) } } } diff --git a/TrollFools/OptionView.swift b/TrollFools/OptionView.swift index 317d2707..286bf95b 100644 --- a/TrollFools/OptionView.swift +++ b/TrollFools/OptionView.swift @@ -11,13 +11,18 @@ struct OptionView: View { let app: App @Environment(\.verticalSizeClass) var verticalSizeClass + @State var isImporterPresented = false @State var isImporterSelected = false + @State var isWarningPresented = false @State var temporaryResult: Result<[URL], any Error>? + @State var isSettingsPresented = false @State var importerResult: Result<[URL], any Error>? + @State var numberOfPlugIns: Int = 0 + @AppStorage("isWarningHidden") var isWarningHidden: Bool = false @@ -53,30 +58,12 @@ struct OptionView: View { Text(NSLocalizedString("Cancel", comment: "")) } } message: { - if case .success(let urls) = $0 { + if case let .success(urls) = $0 { Text(Self.warningMessage(urls)) } } } else { wrappedContent - .alert(isPresented: $isWarningPresented) { - guard case .success(let urls) = temporaryResult else { - return Alert(title: Text("Error")) - } - - return Alert( - title: Text(NSLocalizedString("Notice", comment: "")), - message: Text(Self.warningMessage(urls)), - primaryButton: .destructive(Text(NSLocalizedString("Continue and Don’t Show Again", comment: ""))) { - importerResult = temporaryResult - isImporterSelected = true - isWarningHidden = true - }, - secondaryButton: .cancel() { - temporaryResult = nil - } - ) - } } } @@ -127,10 +114,10 @@ struct OptionView: View { NavigationLink(isActive: $isImporterSelected) { if let result = importerResult { switch result { - case .success(let urls): + case let .success(urls): InjectView(app, urlList: urls .sorted(by: { $0.lastPathComponent < $1.lastPathComponent })) - case .failure(let error): + case let .failure(error): FailureView( title: NSLocalizedString("Error", comment: ""), error: error @@ -156,7 +143,7 @@ struct OptionView: View { ) { result in switch result { - case .success(let theSuccess): + case let .success(theSuccess): if !isWarningHidden && theSuccess.contains(where: { $0.pathExtension.lowercased() == "deb" }) { temporaryResult = result isWarningPresented = true @@ -199,7 +186,7 @@ struct OptionView: View { } return String(format: NSLocalizedString("You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing.", comment: ""), firstDylibName) } - + private func recalculatePlugInCount() { var urls = [URL]() urls += InjectorV3.main.injectedAssetURLsInBundle(app.url) diff --git a/TrollFools/PlugInCell.swift b/TrollFools/PlugInCell.swift index 10540d64..afff4013 100644 --- a/TrollFools/PlugInCell.swift +++ b/TrollFools/PlugInCell.swift @@ -20,26 +20,18 @@ struct PlugInCell: View { @Environment(\.verticalSizeClass) var verticalSizeClass @Binding var quickLookExport: URL? - - @EnvironmentObject var renameManager: RenameManager - @State private var isRenameSheetPresented = false - @State var isEnabled: Bool = false let plugIn: InjectedPlugIn - init (_ plugIn: InjectedPlugIn, quickLookExport: Binding) { - self.plugIn = plugIn - self._quickLookExport = quickLookExport - } - - private var displayName: String { - renameManager.plugInRenames[plugIn.url.lastPathComponent] ?? plugIn.url.lastPathComponent + init(_ plugIn: InjectedPlugIn, quickLookExport: Binding) { + self.plugIn = plugIn + _quickLookExport = quickLookExport } @available(iOS 15, *) var highlightedName: AttributedString { - let name = displayName + let name = plugIn.url.lastPathComponent var attributedString = AttributedString(name) if let range = attributedString.range(of: ejectList.filter.searchKeyword, options: [.caseInsensitive, .diacriticInsensitive]) { attributedString[range].foregroundColor = .accentColor @@ -78,7 +70,7 @@ struct PlugInCell: View { .font(.headline) .lineLimit(2) } else { - Text(displayName) + Text(plugIn.url.lastPathComponent) .font(.headline) .lineLimit(2) } @@ -96,16 +88,6 @@ struct PlugInCell: View { ejectList.togglePlugIn(plugIn, isEnabled: value) } .contextMenu { - Button { - isRenameSheetPresented = true - } label: { - Label(NSLocalizedString("Rename", comment: ""), systemImage: "pencil") - } - Button { - ejectList.plugInToReplace = plugIn - } label: { - Label(NSLocalizedString("Replace", comment: ""), systemImage: "arrow.triangle.2.circlepath") - } if #available(iOS 16.4, *) { ShareLink(item: plugIn.url) { Label(NSLocalizedString("Export", comment: ""), systemImage: "square.and.arrow.up") @@ -129,12 +111,8 @@ struct PlugInCell: View { } .disabled(!isFilzaInstalled) } - .sheet(isPresented: $isRenameSheetPresented) { - RenameSheetView(isPresented: $isRenameSheetPresented, plugInFilename: plugIn.url.lastPathComponent, currentName: displayName) - .environmentObject(renameManager) - } } - + private func exportPlugIn() { quickLookExport = plugIn.url } @@ -144,49 +122,4 @@ struct PlugInCell: View { private func openInFilza() { ejectList.app.appList?.openInFilza(plugIn.url) } - - private struct RenameSheetView: View { - @Binding var isPresented: Bool - @EnvironmentObject var renameManager: RenameManager - - let plugInFilename: String - let currentName: String - - @State private var newName: String = "" - - var body: some View { - NavigationView { - Form { - Section(header: Text(NSLocalizedString("Custom Name", comment: ""))) { - TextField(NSLocalizedString("Enter new name", comment: ""), text: $newName) - Text(NSLocalizedString("Leave it empty to restore the original name.", comment: "")) - .font(.caption) - .foregroundColor(.secondary) - } - } - .navigationTitle(NSLocalizedString("Rename", comment: "")) - .navigationBarTitleDisplayMode(.inline) - .onAppear { - newName = currentName - } - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button(NSLocalizedString("Cancel", comment: "")) { - isPresented = false - } - } - ToolbarItem(placement: .confirmationAction) { - Button(NSLocalizedString("Save", comment: "")) { - if newName.trimmingCharacters(in: .whitespaces).isEmpty { - renameManager.plugInRenames.removeValue(forKey: plugInFilename) - } else { - renameManager.plugInRenames[plugInFilename] = newName - } - isPresented = false - } - } - } - } - } - } } diff --git a/TrollFools/Version.Debug.xcconfig b/TrollFools/Version.Debug.xcconfig index 8e70489c..67bfb1c9 100644 --- a/TrollFools/Version.Debug.xcconfig +++ b/TrollFools/Version.Debug.xcconfig @@ -8,5 +8,5 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -DEBUG_VERSION = 4.0 -DEBUG_BUILD_NUMBER = 202509102 +DEBUG_VERSION = 4.1 +DEBUG_BUILD_NUMBER = 202509111 diff --git a/TrollFools/Version.xcconfig b/TrollFools/Version.xcconfig index 5d51eb75..3888e059 100644 --- a/TrollFools/Version.xcconfig +++ b/TrollFools/Version.xcconfig @@ -8,5 +8,5 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -VERSION = 4.0 -BUILD_NUMBER = 212 +VERSION = 4.1 +BUILD_NUMBER = 219 diff --git a/TrollFools/en.lproj/Localizable.strings b/TrollFools/en.lproj/Localizable.strings index 3f741c5d..5896bc8f 100644 --- a/TrollFools/en.lproj/Localizable.strings +++ b/TrollFools/en.lproj/Localizable.strings @@ -31,6 +31,9 @@ /* No comment provided by engineer. */ "And %d more unsupported user applications." = "And %d more unsupported user applications."; +/* No comment provided by engineer. */ +"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Are you sure you want to eject all plug-ins? This action cannot be undone."; + /* No comment provided by engineer. */ "Bringing back the most advanced system and security analysis tool." = "Bringing back the most advanced system and security analysis tool."; @@ -121,6 +124,9 @@ /* No comment provided by engineer. */ "If you do not know what these options mean, please do not change them." = "If you do not know what these options mean, please do not change them."; +/* No comment provided by engineer. */ +"Includes Disabled PlugIns" = "Includes Disabled PlugIns"; + /* No comment provided by engineer. */ "Inject" = "Inject"; @@ -160,6 +166,9 @@ /* No comment provided by engineer. */ "Manage" = "Manage"; +/* No comment provided by engineer. */ +"Manage %d Plug-Ins" = "Manage %d Plug-Ins"; + /* No comment provided by engineer. */ "New version %@ available!" = "New version %@ available!"; @@ -288,35 +297,3 @@ /* No comment provided by engineer. */ "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing." = "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing."; - -/* No comment provided by engineer. */ -"Rename" = "Rename"; - -/* No comment provided by engineer. */ -"Replace" = "Replace"; - -/* No comment provided by engineer. */ -"Custom Name" = "Custom Name"; - -/* No comment provided by engineer. */ -"Enter new name" = "Enter new name"; - -/* No comment provided by engineer. */ -"Leave it empty to restore the original name." = "Leave it empty to restore the original name."; - -/* No comment provided by engineer. */ -"Cancel" = "Cancel"; - -/* No comment provided by engineer. */ -"Save" = "Save"; - -/* For the accessibility label on the Manage button */ -"Manage %d Plug-Ins" = "Manage %d Plug-Ins"; - -/* For the confirmation alert when ejecting all plugins */ -"Eject All" = "Eject All"; -"Confirm" = "Confirm"; -"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Are you sure you want to eject all plug-ins? This action cannot be undone."; - -/* For the badge on apps with only disabled plugins */ -"Includes Disabled PlugIns" = "Includes Disabled PlugIns"; diff --git a/TrollFools/it.lproj/Localizable.strings b/TrollFools/it.lproj/Localizable.strings index 9232ce7f..387259a5 100644 --- a/TrollFools/it.lproj/Localizable.strings +++ b/TrollFools/it.lproj/Localizable.strings @@ -31,6 +31,9 @@ /* No comment provided by engineer. */ "And %d more unsupported user applications." = "E %d più non supportate dalle applicazioni."; +/* TODO */ +"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Are you sure you want to eject all plug-ins? This action cannot be undone."; + /* No comment provided by engineer. */ "Bringing back the most advanced system and security analysis tool." = "Portiamo indietro il più avanzato strumento di analisi di sistema e della sicurezza."; @@ -121,6 +124,9 @@ /* No comment provided by engineer. */ "If you do not know what these options mean, please do not change them." = "Se non sai cosa significano queste opzioni, non cambiarle."; +/* TODO */ +"Includes Disabled PlugIns" = "Includes Disabled PlugIns"; + /* No comment provided by engineer. */ "Inject" = "Inietta"; @@ -160,6 +166,9 @@ /* TODO */ "Manage" = "Manage"; +/* TODO */ +"Manage %d Plug-Ins" = "Manage %d Plug-Ins"; + /* TODO */ "New version %@ available!" = "New version %@ available!"; @@ -288,35 +297,3 @@ /* TODO */ "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing." = "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing."; - -/* No comment provided by engineer. */ -"Rename" = "Rinomina"; - -/* No comment provided by engineer. */ -"Replace" = "Sostituisci"; - -/* No comment provided by engineer. */ -"Custom Name" = "Nome personalizzato"; - -/* No comment provided by engineer. */ -"Enter new name" = "Inserisci un nuovo nome"; - -/* No comment provided by engineer. */ -"Leave it empty to restore the original name." = "Lascia vuoto per ripristinare il nome originale."; - -/* No comment provided by engineer. */ -"Cancel" = "Annulla"; - -/* No comment provided by engineer. */ -"Save" = "Salva"; - -/* For the accessibility label on the Manage button */ -"Manage %d Plug-Ins" = "Gestisci %d plugin"; - -/* For the confirmation alert when ejecting all plugins */ -"Eject All" = "Espelli tutto"; -"Confirm" = "Conferma"; -"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Sei sicuro di voler espellere tutti i plugin? Questa azione non può essere annullata."; - -/* For the badge on apps with only disabled plugins */ -"Includes Disabled PlugIns" = "Include plugin disabilitati"; diff --git a/TrollFools/vi.lproj/Localizable.strings b/TrollFools/vi.lproj/Localizable.strings index bb485f4a..6f466bb1 100644 --- a/TrollFools/vi.lproj/Localizable.strings +++ b/TrollFools/vi.lproj/Localizable.strings @@ -31,6 +31,9 @@ /* No comment provided by engineer. */ "And %d more unsupported user applications." = "Có %d ứng dụng người dùng không được hỗ trợ."; +/* TODO */ +"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Are you sure you want to eject all plug-ins? This action cannot be undone."; + /* TODO */ "Bringing back the most advanced system and security analysis tool." = "Bringing back the most advanced system and security analysis tool."; @@ -121,6 +124,9 @@ /* No comment provided by engineer. */ "If you do not know what these options mean, please do not change them." = "Nếu bạn không biết các tùy chọn này có ý nghĩa gì, đừng thay đổi chúng."; +/* TODO */ +"Includes Disabled PlugIns" = "Includes Disabled PlugIns"; + /* No comment provided by engineer. */ "Inject" = "Tiêm"; @@ -160,6 +166,9 @@ /* TODO */ "Manage" = "Manage"; +/* TODO */ +"Manage %d Plug-Ins" = "Manage %d Plug-Ins"; + /* TODO */ "New version %@ available!" = "New version %@ available!"; @@ -288,35 +297,3 @@ /* TODO */ "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing." = "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing."; - -/* No comment provided by engineer. */ -"Rename" = "Đổi tên"; - -/* No comment provided by engineer. */ -"Replace" = "Thay thế"; - -/* No comment provided by engineer. */ -"Custom Name" = "Tên tùy chỉnh"; - -/* No comment provided by engineer. */ -"Enter new name" = "Nhập tên mới"; - -/* No comment provided by engineer. */ -"Leave it empty to restore the original name." = "Để trống để khôi phục tên gốc."; - -/* No comment provided by engineer. */ -"Cancel" = "Hủy"; - -/* No comment provided by engineer. */ -"Save" = "Lưu"; - -/* For the accessibility label on the Manage button */ -"Manage %d Plug-Ins" = "Quan ly %d plugin"; - -/* For the confirmation alert when ejecting all plugins */ -"Eject All" = "Go bo tat ca"; -"Confirm" = "Xac nhan"; -"Are you sure you want to eject all plug-ins? This action cannot be undone." = "Ban co chac chan muon go bo tat ca cac plugin khong? Hanh dong nay khong the hoan tac."; - -/* For the badge on apps with only disabled plugins */ -"Includes Disabled PlugIns" = "Bao gom cac plugin da bi vo hieu hoa"; diff --git a/TrollFools/zh-Hans.lproj/Localizable.strings b/TrollFools/zh-Hans.lproj/Localizable.strings index 2d0a150b..d92a41de 100644 --- a/TrollFools/zh-Hans.lproj/Localizable.strings +++ b/TrollFools/zh-Hans.lproj/Localizable.strings @@ -31,6 +31,9 @@ /* No comment provided by engineer. */ "And %d more unsupported user applications." = "另有 %d 个不支持的用户应用。"; +/* No comment provided by engineer. */ +"Are you sure you want to eject all plug-ins? This action cannot be undone." = "你确定要推出所有插件吗?此操作无法撤销。"; + /* No comment provided by engineer. */ "Bringing back the most advanced system and security analysis tool." = "最强大的系统和安全分析工具,再次归来。"; @@ -116,11 +119,14 @@ "Full-Fledged Automation Framework for TrollStore." = "适用于 TrollStore 的全功能自动化框架。"; /* No comment provided by engineer. */ -"Hide" = "Hide"; +"Hide" = "隐藏"; /* No comment provided by engineer. */ "If you do not know what these options mean, please do not change them." = "如果你不知道这些选项的含义,请不要更改。"; +/* No comment provided by engineer. */ +"Includes Disabled PlugIns" = "包含已禁用的插件"; + /* No comment provided by engineer. */ "Inject" = "注入"; @@ -160,6 +166,9 @@ /* No comment provided by engineer. */ "Manage" = "管理"; +/* No comment provided by engineer. */ +"Manage %d Plug-Ins" = "管理 %d 个插件"; + /* No comment provided by engineer. */ "New version %@ available!" = "有新版本 %@ 可用!"; @@ -288,34 +297,3 @@ /* No comment provided by engineer. */ "You’ve selected at least one Debian Package “%@”. We’re here to remind you that it will not work as it was in a jailbroken environment. Please make sure you know what you’re doing." = "你至少选择了一个 Debian 软件包 “%@”。请留意,它通常不会像在越狱环境当中那样工作,甚至完全不起作用。请确保你知道自己在做些什么。"; - -/* No comment provided by engineer. */ -"Rename" = "重命名"; - -/* No comment provided by engineer. */ -"Replace" = "替换"; - -/* No comment provided by engineer. */ -"Custom Name" = "自定义名称"; - -/* No comment provided by engineer. */ -"Enter new name" = "输入新名称"; - -/* No comment provided by engineer. */ -"Leave it empty to restore the original name." = "留空以恢复原始名称。"; - -/* No comment provided by engineer. */ -"Cancel" = "取消"; - -/* No comment provided by engineer. */ -"Save" = "保存"; -/* For the accessibility label on the Manage button */ -"Manage %d Plug-Ins" = "管理 %d 个插件"; - -/* For the confirmation alert when ejecting all plugins */ -"Eject All" = "清空所有插件"; -"Confirm" = "确认"; -"Are you sure you want to eject all plug-ins? This action cannot be undone." = "你确定要清空所有插件吗?此操作无法撤销。"; - -/* For the badge on apps with only disabled plugins */ -"Includes Disabled PlugIns" = "包含已禁用的插件";