diff --git a/TrollFools.xcodeproj/project.pbxproj b/TrollFools.xcodeproj/project.pbxproj index c51fd11..7cf0d0e 100644 --- a/TrollFools.xcodeproj/project.pbxproj +++ b/TrollFools.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ CC1549022C4B62E300A4173E /* cp-15 in Resources */ = {isa = PBXBuildFile; fileRef = CC1549012C4B62E300A4173E /* cp-15 */; }; CC15490C2C4B79D800A4173E /* optool in Resources */ = {isa = PBXBuildFile; fileRef = CC15490B2C4B79D800A4173E /* optool */; }; CC15490E2C4B80AF00A4173E /* EjectListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC15490D2C4B80AF00A4173E /* EjectListView.swift */; }; + CC19E4BB2C561D7300E0F1B5 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC19E4BA2C561D7300E0F1B5 /* SettingsView.swift */; }; CC5E54BE2C4E12F900FDE4A8 /* install_name_tool in Resources */ = {isa = PBXBuildFile; fileRef = CC5E54BC2C4E12F900FDE4A8 /* install_name_tool */; }; CC5E54BF2C4E131000FDE4A8 /* libxar.1.dylib in Resources */ = {isa = PBXBuildFile; fileRef = CC5E54BB2C4E12F900FDE4A8 /* libxar.1.dylib */; }; CC61A87C2C4A677B003BD9A0 /* CydiaSubstrate.framework.zip in Resources */ = {isa = PBXBuildFile; fileRef = CC61A87B2C4A677B003BD9A0 /* CydiaSubstrate.framework.zip */; }; @@ -64,6 +65,7 @@ CC1549012C4B62E300A4173E /* cp-15 */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = "cp-15"; sourceTree = ""; }; CC15490B2C4B79D800A4173E /* optool */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = optool; sourceTree = ""; }; CC15490D2C4B80AF00A4173E /* EjectListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EjectListView.swift; sourceTree = ""; }; + CC19E4BA2C561D7300E0F1B5 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; CC5E54BB2C4E12F900FDE4A8 /* libxar.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libxar.1.dylib; sourceTree = ""; }; CC5E54BC2C4E12F900FDE4A8 /* install_name_tool */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = install_name_tool; sourceTree = ""; }; CC61A87B2C4A677B003BD9A0 /* CydiaSubstrate.framework.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = CydiaSubstrate.framework.zip; sourceTree = ""; }; @@ -168,6 +170,7 @@ CCF470592C4A4649008D8197 /* TrollFoolsApp.swift */, CCF4706D2C4A4BAB008D8197 /* AppListView.swift */, CCB6A1182C4A58C7000D75B0 /* OptionView.swift */, + CC19E4BA2C561D7300E0F1B5 /* SettingsView.swift */, CCB6A11A2C4A6066000D75B0 /* InjectView.swift */, CC15490D2C4B80AF00A4173E /* EjectListView.swift */, CC1548D22C4A743200A4173E /* SuccessView.swift */, @@ -295,6 +298,7 @@ CC1548DB2C4A7C7000A4173E /* Execute.swift in Sources */, CCF5A0A42C4D45160097D48D /* AuxiliaryExecute.swift in Sources */, CCF4705A2C4A4649008D8197 /* TrollFoolsApp.swift in Sources */, + CC19E4BB2C561D7300E0F1B5 /* SettingsView.swift in Sources */, CC1548D72C4A768700A4173E /* Injector.swift in Sources */, CCB6A1192C4A58C7000D75B0 /* OptionView.swift in Sources */, CCF5A0A22C4D417F0097D48D /* main.swift in Sources */, @@ -467,7 +471,7 @@ "$(inherited)", "$(PROJECT_DIR)/TrollFools", ); - MARKETING_VERSION = 2.4; + MARKETING_VERSION = 2.5; PRODUCT_BUNDLE_IDENTIFIER = wiki.qaq.TrollFools; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -509,7 +513,7 @@ "$(inherited)", "$(PROJECT_DIR)/TrollFools", ); - MARKETING_VERSION = 2.4; + MARKETING_VERSION = 2.5; PRODUCT_BUNDLE_IDENTIFIER = wiki.qaq.TrollFools; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; diff --git a/TrollFools/AppListView.swift b/TrollFools/AppListView.swift index 126d54d..dbcca84 100644 --- a/TrollFools/AppListView.swift +++ b/TrollFools/AppListView.swift @@ -277,7 +277,7 @@ struct AppListCell: View { if app.isDetached { Button { do { - let injector = try Injector(bundleURL: app.url, teamID: app.teamID) + let injector = try Injector(app.url, appID: app.id, teamID: app.teamID) try injector.setDetached(false) withAnimation { app.reload() @@ -290,7 +290,7 @@ struct AppListCell: View { } else { Button { do { - let injector = try Injector(bundleURL: app.url, teamID: app.teamID) + let injector = try Injector(app.url, appID: app.id, teamID: app.teamID) try injector.setDetached(true) withAnimation { app.reload() diff --git a/TrollFools/EjectListView.swift b/TrollFools/EjectListView.swift index 960627a..42d7c59 100644 --- a/TrollFools/EjectListView.swift +++ b/TrollFools/EjectListView.swift @@ -250,7 +250,7 @@ struct EjectListView: View { do { let plugInsToRemove = offsets.map { vm.filteredPlugIns[$0] } let plugInURLsToRemove = plugInsToRemove.map { $0.url } - let injector = try Injector(bundleURL: vm.app.url, teamID: vm.app.teamID) + let injector = try Injector(vm.app.url, appID: vm.app.id, teamID: vm.app.teamID) try injector.eject(plugInURLsToRemove) vm.app.reload() @@ -265,7 +265,7 @@ struct EjectListView: View { func deleteAll() { do { - let injector = try Injector(bundleURL: vm.app.url, teamID: vm.app.teamID) + let injector = try Injector(vm.app.url, appID: vm.app.id, teamID: vm.app.teamID) let view = viewControllerHost.viewController? .navigationController?.view diff --git a/TrollFools/InjectView.swift b/TrollFools/InjectView.swift index 1c2b44c..62183ce 100644 --- a/TrollFools/InjectView.swift +++ b/TrollFools/InjectView.swift @@ -63,10 +63,7 @@ struct InjectView: View { func inject() -> Result { do { - let injector = try Injector( - bundleURL: app.url, - teamID: app.teamID - ) + let injector = try Injector(app.url, appID: app.id, teamID: app.teamID) try injector.inject(urlList) return .success(()) } catch { diff --git a/TrollFools/Injector.swift b/TrollFools/Injector.swift index 4f4d36f..786cc5e 100644 --- a/TrollFools/Injector.swift +++ b/TrollFools/Injector.swift @@ -8,6 +8,7 @@ import CocoaLumberjackSwift import Foundation import MachOKit +import SwiftUI import ZIPFoundation final class Injector { @@ -104,6 +105,9 @@ final class Injector { private let tempURL: URL private var teamID: String + @AppStorage var useWeakReference: Bool + @AppStorage var preferMainExecutable: Bool + private lazy var infoPlistURL: URL = bundleURL.appendingPathComponent("Info.plist") private lazy var mainExecutableURL: URL = { let infoPlist = NSDictionary(contentsOf: infoPlistURL)! @@ -125,7 +129,7 @@ final class Injector { private init() { fatalError("Not implemented") } - init(bundleURL: URL, teamID: String) throws { + init(_ bundleURL: URL, appID: String, teamID: String) throws { self.bundleURL = bundleURL self.teamID = teamID self.tempURL = try FileManager.default.url( @@ -134,6 +138,8 @@ final class Injector { appropriateFor: URL(fileURLWithPath: NSHomeDirectory()), create: true ) + _useWeakReference = AppStorage(wrappedValue: true, "UseWeakReference-\(appID)") + _preferMainExecutable = AppStorage(wrappedValue: false, "PreferMainExecutable-\(appID)") try updateTeamIdentifier(bundleURL) } @@ -219,10 +225,18 @@ final class Injector { } } - return executableURLs + var fwkURLs = executableURLs .intersection(initialDylibs) .filter { isMachOURL($0) } - .sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) + [target] + .sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) + + if preferMainExecutable { + fwkURLs.insert(target, at: 0) + } else { + fwkURLs.append(target) + } + + return fwkURLs } private func copyTempInjectURLs(_ injectURLs: [URL]) throws -> [URL] { @@ -568,7 +582,7 @@ final class Injector { } try _insertLoadCommandRpath(target, name: "@executable_path/Frameworks") - try _insertLoadCommandDylib(target, name: name, isWeak: true) + try _insertLoadCommandDylib(target, name: name, isWeak: useWeakReference) try applyTargetFixes(target, name: name) } diff --git a/TrollFools/OptionView.swift b/TrollFools/OptionView.swift index 65a5310..6d3fb9a 100644 --- a/TrollFools/OptionView.swift +++ b/TrollFools/OptionView.swift @@ -59,6 +59,8 @@ struct OptionView: View { @State var isImporterPresented = false @State var isImporterSelected = false + @State var isSettingsPresented = false + @State var importerResult: Result<[URL], any Error>? init(_ app: App) { @@ -66,9 +68,39 @@ struct OptionView: View { } var body: some View { - HStack { - Spacer() + VStack(spacing: 80) { + HStack { + Spacer() + + Button { + isImporterPresented = true + } label: { + OptionCell(option: .attach) + } + .accessibilityLabel(NSLocalizedString("Inject", comment: "")) + + Spacer() + + NavigationLink { + EjectListView(app) + } label: { + OptionCell(option: .detach) + } + .accessibilityLabel(NSLocalizedString("Eject", comment: "")) + + Spacer() + } + Button { + isSettingsPresented = true + } label: { + Label(NSLocalizedString("Advanced Settings", comment: ""), + systemImage: "gear") + } + } + .padding() + .navigationTitle(app.name) + .background(Group { NavigationLink(isActive: $isImporterSelected) { if let result = importerResult { switch result { @@ -76,32 +108,12 @@ struct OptionView: View { InjectView(app: app, urlList: urls .sorted(by: { $0.lastPathComponent < $1.lastPathComponent })) case .failure(let message): - FailureView(title: NSLocalizedString("Error", comment: ""), + FailureView(title: NSLocalizedString("Error", comment: ""), message: message.localizedDescription) } } } label: { } - - Button { - isImporterPresented = true - } label: { - OptionCell(option: .attach) - } - .accessibilityLabel(NSLocalizedString("Inject", comment: "")) - - Spacer() - - NavigationLink { - EjectListView(app) - } label: { - OptionCell(option: .detach) - } - .accessibilityLabel(NSLocalizedString("Eject", comment: "")) - - Spacer() - } - .padding() - .navigationTitle(app.name) + }) .fileImporter( isPresented: $isImporterPresented, allowedContentTypes: [ @@ -117,5 +129,13 @@ struct OptionView: View { importerResult = result isImporterSelected = true } + .sheet(isPresented: $isSettingsPresented) { + if #available(iOS 16.0, *) { + SettingsView(app) + .presentationDetents([.medium, .large]) + } else { + SettingsView(app) + } + } } } diff --git a/TrollFools/SettingsView.swift b/TrollFools/SettingsView.swift new file mode 100644 index 0000000..2f3ff15 --- /dev/null +++ b/TrollFools/SettingsView.swift @@ -0,0 +1,38 @@ +// +// SettingsView.swift +// TrollFools +// +// Created by Lessica on 2024/7/28. +// + +import SwiftUI + +struct SettingsView: View { + let app: App + + init(_ app: App) { + self.app = app + self._useWeakReference = AppStorage(wrappedValue: true, "UseWeakReference-\(app.id)") + self._preferMainExecutable = AppStorage(wrappedValue: false, "PreferMainExecutable-\(app.id)") + } + + @AppStorage var useWeakReference: Bool + @AppStorage var preferMainExecutable: Bool + + var body: some View { + NavigationView { + Form { + Section { + Toggle(NSLocalizedString("Use Weak Reference", comment: ""), systemImage: "link", isOn: $useWeakReference) + Toggle(NSLocalizedString("Prefer Main Executable", comment: ""), systemImage: "doc.badge.gearshape", isOn: $preferMainExecutable) + } header: { + Text(NSLocalizedString("Injection", comment: "")) + } footer: { + Text(NSLocalizedString("If you do not know what these options mean, please do not change them.", comment: "")) + } + } + .navigationTitle(NSLocalizedString("Advanced Settings", comment: "")) + .navigationBarTitleDisplayMode(.inline) + } + } +} diff --git a/TrollFools/en.lproj/Localizable.strings b/TrollFools/en.lproj/Localizable.strings index 84a3c02..c397d80 100644 --- a/TrollFools/en.lproj/Localizable.strings +++ b/TrollFools/en.lproj/Localizable.strings @@ -7,6 +7,9 @@ /* No comment provided by engineer. */ "A pure TrollStore software channel!" = "A pure TrollStore software channel!"; +/* No comment provided by engineer. */ +"Advanced Settings" = "Advanced Settings"; + /* No comment provided by engineer. */ "AE86 TrollStore Channel" = "AE86 TrollStore Channel"; @@ -40,6 +43,9 @@ /* No comment provided by engineer. */ "Failed to parse: %@" = "Failed to parse: %@"; +/* 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. */ "Inject" = "Inject"; @@ -52,6 +58,9 @@ /* No comment provided by engineer. */ "Injecting" = "Injecting"; +/* No comment provided by engineer. */ +"Injection" = "Injection"; + /* No comment provided by engineer. */ "Launch" = "Launch"; @@ -85,6 +94,9 @@ /* No comment provided by engineer. */ "Plug-Ins" = "Plug-Ins"; +/* No comment provided by engineer. */ +"Prefer Main Executable" = "Prefer Main Executable"; + /* No comment provided by engineer. */ "Rebuild Icon Cache" = "Rebuild Icon Cache"; @@ -118,6 +130,9 @@ /* No comment provided by engineer. */ "Unlock Version" = "Unlock Version"; +/* No comment provided by engineer. */ +"Use Weak Reference" = "Use Weak Reference"; + /* No comment provided by engineer. */ "User Applications" = "User Applications"; diff --git a/TrollFools/zh-Hans.lproj/Localizable.strings b/TrollFools/zh-Hans.lproj/Localizable.strings index 36daaf9..f020223 100644 --- a/TrollFools/zh-Hans.lproj/Localizable.strings +++ b/TrollFools/zh-Hans.lproj/Localizable.strings @@ -7,6 +7,9 @@ /* No comment provided by engineer. */ "A pure TrollStore software channel!" = "秋名山一路向北,最纯净的巨魔软件体验!"; +/* No comment provided by engineer. */ +"Advanced Settings" = "高级选项"; + /* No comment provided by engineer. */ "AE86 TrollStore Channel" = "秋名山巨魔俱乐部"; @@ -40,6 +43,9 @@ /* No comment provided by engineer. */ "Failed to parse: %@" = "无法解析:%@"; +/* No comment provided by engineer. */ +"If you do not know what these options mean, please do not change them." = "如果你不知道这些选项的含义,请不要更改。"; + /* No comment provided by engineer. */ "Inject" = "注入"; @@ -52,6 +58,9 @@ /* No comment provided by engineer. */ "Injecting" = "注入中"; +/* No comment provided by engineer. */ +"Injection" = "注入"; + /* No comment provided by engineer. */ "Launch" = "启动"; @@ -85,6 +94,9 @@ /* No comment provided by engineer. */ "Plug-Ins" = "插件列表"; +/* No comment provided by engineer. */ +"Prefer Main Executable" = "优先处理主要可执行文件"; + /* No comment provided by engineer. */ "Rebuild Icon Cache" = "重建图标缓存"; @@ -118,6 +130,9 @@ /* No comment provided by engineer. */ "Unlock Version" = "解锁版本"; +/* No comment provided by engineer. */ +"Use Weak Reference" = "创建弱引用"; + /* No comment provided by engineer. */ "User Applications" = "用户应用";