├── .bartycrouch.toml ├── .github ├── ISSUE_TEMPLATE │ └── BugReport.md └── workflows │ ├── compile.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── TrollFools.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── trollfoolscli.xcscheme ├── TrollFools ├── App.swift ├── AppListCell.swift ├── AppListModel.swift ├── AppListSearchModel.swift ├── AppListView.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── AppIcon.png │ │ └── Contents.json │ ├── Contents.json │ ├── badge-ad.imageset │ │ ├── Contents.json │ │ └── badge-ad.svg │ ├── reveil-default.imageset │ │ ├── Contents.json │ │ └── icon_128x128.png │ └── tricon-default.imageset │ │ ├── Contents.json │ │ └── icon_128x128.png ├── AuxiliaryExecute+Spawn.swift ├── AuxiliaryExecute.swift ├── BartyCrouch.swift ├── CLI │ ├── CmdEject.swift │ ├── CmdInject.swift │ ├── CmdList.swift │ ├── CmdView.swift │ └── Entry.swift ├── Constants.swift ├── CydiaSubstrate.framework.zip ├── DisclaimerView.swift ├── EjectListModel.swift ├── EjectListSearchModel.swift ├── EjectListView.swift ├── Execute.swift ├── FailureView.swift ├── FilterOptions.swift ├── Hashable+Combines.swift ├── IndexableScroller.swift ├── Info.plist ├── InjectView.swift ├── InjectedPlugIn.swift ├── InjectorV3+Backup.swift ├── InjectorV3+Bundle.swift ├── InjectorV3+Command.swift ├── InjectorV3+Eject.swift ├── InjectorV3+Error.swift ├── InjectorV3+Inject.swift ├── InjectorV3+MachO.swift ├── InjectorV3+Metadata.swift ├── InjectorV3+Preprocess.swift ├── InjectorV3.swift ├── LSApplicationProxy.h ├── LSApplicationWorkspace.h ├── LogsView.swift ├── Option.swift ├── OptionCell.swift ├── OptionView.swift ├── PlaceholderView.swift ├── PlugInCell.swift ├── SettingsView.swift ├── StripedTextTableViewController.h ├── StripedTextTableViewController.m ├── SuccessView.swift ├── TrollFools-Bridging-Header.h ├── TrollFools.entitlements ├── TrollFoolsApp.swift ├── TrollFoolsStub.m ├── Version.Debug.xcconfig ├── Version.xcconfig ├── ViewControllerHost.swift ├── chown ├── cp ├── cp-15 ├── ct_bypass ├── en.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── insert_dylib ├── install_name_tool ├── it.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── ldid ├── libcrypto.3.dylib ├── libintl.8.dylib ├── libiosexec.1.dylib ├── libxar.1.dylib ├── mkdir ├── mv ├── mv-15 ├── optool ├── rm ├── vi.lproj │ ├── InfoPlist.strings │ └── Localizable.strings └── zh-Hans.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── TrollFoolsTweak ├── Makefile ├── TrollFoolsTweak.plist └── TrollFoolsTweak.x ├── control └── devkit ├── bump-version.sh ├── print-version.sh ├── rootless.sh ├── standardize-entitlements.sh └── tipa.sh /.bartycrouch.toml: -------------------------------------------------------------------------------- 1 | [update] 2 | tasks = ["interfaces", "code", "transform", "normalize"] 3 | 4 | [update.interfaces] 5 | paths = ["TrollFools"] 6 | subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"] 7 | defaultToBase = true 8 | ignoreEmptyStrings = false 9 | unstripped = false 10 | ignoreKeys = ["#bartycrouch-ignore!", "#bc-ignore!", "#i!"] 11 | 12 | [update.code] 13 | codePaths = ["."] 14 | subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"] 15 | localizablePaths = ["TrollFools"] 16 | defaultToKeys = true 17 | additive = false 18 | customFunction = "LocalizedStringResource" 19 | unstripped = false 20 | plistArguments = true 21 | ignoreKeys = ["#bartycrouch-ignore!", "#bc-ignore!", "#i!"] 22 | overrideComments = false 23 | 24 | [update.transform] 25 | codePaths = ["TrollFools"] 26 | subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"] 27 | localizablePaths = ["TrollFools"] 28 | transformer = "foundation" 29 | supportedLanguageEnumPath = "." 30 | typeName = "BartyCrouch" 31 | translateMethodName = "translate" 32 | separateWithEmptyLine = true 33 | 34 | [update.normalize] 35 | paths = ["TrollFools"] 36 | subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"] 37 | sourceLocale = "en" 38 | harmonizeWithSource = true 39 | sortByKeys = true 40 | separateWithEmptyLine = true 41 | 42 | [lint] 43 | paths = ["TrollFools"] 44 | subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"] 45 | duplicateKeys = true 46 | emptyValues = true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BugReport.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: For Tweak Developer to Report Bugs 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 1. 不支持的 App 不会显示在列表里。不要稍微懂点就自作聪明觉得啊那为什么别的 XX 工具能支持,已经说过了,不支持就是不支持,别在这 **浪费时间**。 11 | 2. 普通用户请勿提交「插件无效」、「闪退」,「错误代码」等问题,请找插件开发者适配。 12 | 3. 插件开发者反馈问题请先自行排查原因,**不要上来就丢源代码**。描述问题时请详细且有条理地描述问题,不要语无伦次。 13 | -------------------------------------------------------------------------------- /.github/workflows/compile.yml: -------------------------------------------------------------------------------- 1 | name: Compile 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | env: 10 | THEOS: "" 11 | VERSION: "0.0.0-0" 12 | 13 | jobs: 14 | fetch-and-release: 15 | runs-on: macos-14 16 | steps: 17 | - name: Setup Xcode 18 | uses: maxim-lobanov/setup-xcode@v1 19 | with: 20 | xcode-version: '15.4' 21 | 22 | - name: Checkout Repository 23 | uses: actions/checkout@v4.1.1 24 | 25 | - name: Install Dependencies 26 | run: | 27 | echo /usr/local/opt/make/libexec/gnubin >> $GITHUB_PATH 28 | echo /opt/homebrew/opt/make/libexec/gnubin >> $GITHUB_PATH 29 | HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install dpkg ldid-procursus make xcbeautify openssl@3 30 | VERSION_FILE="TrollFools/Version.xcconfig" 31 | VERSION_MAIN=$(grep "VERSION" $VERSION_FILE | cut -d'=' -f2 | tr -d '[:space:]') 32 | VERSION_BUILD=$(grep "BUILD_NUMBER" $VERSION_FILE | cut -d'=' -f2 | tr -d '[:space:]') 33 | VERSION=$VERSION_MAIN-$VERSION_BUILD 34 | echo "VERSION=$VERSION" >> $GITHUB_ENV 35 | echo "Version: $VERSION" 36 | 37 | - name: Checkout THEOS 38 | uses: actions/checkout@v4.1.1 39 | with: 40 | repository: theos/theos 41 | ref: 942cd0c015f93d8c2d714ba0c69e7f420157c382 42 | path: theos 43 | submodules: recursive 44 | 45 | - name: Add THEOS environment variables 46 | run: | 47 | rm -rf $GITHUB_WORKSPACE/theos/sdks 48 | echo "THEOS=$GITHUB_WORKSPACE/theos" >> $GITHUB_ENV 49 | echo "THEOS_PACKAGE_SCHEME=rootless" >> $GITHUB_ENV 50 | echo "FINALPACKAGE=1" >> $GITHUB_ENV 51 | 52 | - name: Checkout SDKs 53 | uses: actions/checkout@v4.1.1 54 | with: 55 | repository: theos/sdks 56 | ref: master 57 | path: ${{ env.THEOS }}/sdks 58 | 59 | - name: Build Packages 60 | run: | 61 | make package 62 | cp packages/*.tipa . 63 | find .theos/obj -name "*.dSYM" -exec cp -r {} . \; 64 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build & Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - release 8 | 9 | env: 10 | THEOS: "" 11 | VERSION: "0.0.0-0" 12 | 13 | jobs: 14 | fetch-and-release: 15 | runs-on: macos-14 16 | steps: 17 | - name: Setup Xcode 18 | uses: maxim-lobanov/setup-xcode@v1 19 | with: 20 | xcode-version: '15.4' 21 | 22 | - name: Checkout Repository 23 | uses: actions/checkout@v4.1.1 24 | 25 | - name: Install Dependencies 26 | run: | 27 | echo /usr/local/opt/make/libexec/gnubin >> $GITHUB_PATH 28 | echo /opt/homebrew/opt/make/libexec/gnubin >> $GITHUB_PATH 29 | HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install dpkg ldid-procursus make xcbeautify openssl@3 30 | VERSION_FILE="TrollFools/Version.xcconfig" 31 | VERSION_MAIN=$(grep "VERSION" $VERSION_FILE | cut -d'=' -f2 | tr -d '[:space:]') 32 | VERSION_BUILD=$(grep "BUILD_NUMBER" $VERSION_FILE | cut -d'=' -f2 | tr -d '[:space:]') 33 | VERSION=$VERSION_MAIN-$VERSION_BUILD 34 | echo "VERSION=$VERSION" >> $GITHUB_ENV 35 | echo "Version: $VERSION" 36 | 37 | - name: Checkout THEOS 38 | uses: actions/checkout@v4.1.1 39 | with: 40 | repository: theos/theos 41 | ref: 942cd0c015f93d8c2d714ba0c69e7f420157c382 42 | path: theos 43 | submodules: recursive 44 | 45 | - name: Add THEOS environment variables 46 | run: | 47 | rm -rf $GITHUB_WORKSPACE/theos/sdks 48 | echo "THEOS=$GITHUB_WORKSPACE/theos" >> $GITHUB_ENV 49 | echo "THEOS_PACKAGE_SCHEME=rootless" >> $GITHUB_ENV 50 | echo "FINALPACKAGE=1" >> $GITHUB_ENV 51 | 52 | - name: Checkout SDKs 53 | uses: actions/checkout@v4.1.1 54 | with: 55 | repository: theos/sdks 56 | ref: master 57 | path: ${{ env.THEOS }}/sdks 58 | 59 | - name: Build Packages 60 | run: | 61 | make package 62 | cp packages/*.tipa packages/*.deb . 63 | find .theos/obj -name "*.dSYM" -exec cp -r {} . \; 64 | 65 | - name: Upload Packages 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: packages-${{ env.VERSION }} 69 | path: | 70 | *.tipa 71 | *.deb 72 | *.dSYM 73 | 74 | - name: Release 75 | uses: softprops/action-gh-release@v0.1.15 76 | with: 77 | tag_name: v${{ env.VERSION }} 78 | body_path: CHANGELOG.md 79 | draft: false 80 | prerelease: false 81 | files: | 82 | *.tipa 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .theos 2 | packages 3 | compile_commands.json 4 | 5 | # Created by https://www.toptal.com/developers/gitignore/api/swift,macos,xcode 6 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift,macos,xcode 7 | 8 | ### macOS ### 9 | # General 10 | .DS_Store 11 | .AppleDouble 12 | .LSOverride 13 | 14 | # Icon must end with two \r 15 | Icon 16 | 17 | 18 | # Thumbnails 19 | ._* 20 | 21 | # Files that might appear in the root of a volume 22 | .DocumentRevisions-V100 23 | .fseventsd 24 | .Spotlight-V100 25 | .TemporaryItems 26 | .Trashes 27 | .VolumeIcon.icns 28 | .com.apple.timemachine.donotpresent 29 | 30 | # Directories potentially created on remote AFP share 31 | .AppleDB 32 | .AppleDesktop 33 | Network Trash Folder 34 | Temporary Items 35 | .apdisk 36 | 37 | ### macOS Patch ### 38 | # iCloud generated files 39 | *.icloud 40 | 41 | ### Swift ### 42 | # Xcode 43 | # 44 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 45 | 46 | ## User settings 47 | xcuserdata/ 48 | 49 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 50 | *.xcscmblueprint 51 | *.xccheckout 52 | 53 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 54 | build/ 55 | DerivedData/ 56 | *.moved-aside 57 | *.pbxuser 58 | !default.pbxuser 59 | *.mode1v3 60 | !default.mode1v3 61 | *.mode2v3 62 | !default.mode2v3 63 | *.perspectivev3 64 | !default.perspectivev3 65 | 66 | ## Obj-C/Swift specific 67 | *.hmap 68 | 69 | ## App packaging 70 | *.ipa 71 | *.dSYM.zip 72 | *.dSYM 73 | 74 | ## Playgrounds 75 | timeline.xctimeline 76 | playground.xcworkspace 77 | 78 | # Swift Package Manager 79 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 80 | # Packages/ 81 | # Package.pins 82 | # Package.resolved 83 | # *.xcodeproj 84 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 85 | # hence it is not needed unless you have added a package configuration file to your project 86 | # .swiftpm 87 | 88 | .build/ 89 | 90 | # CocoaPods 91 | # We recommend against adding the Pods directory to your .gitignore. However 92 | # you should judge for yourself, the pros and cons are mentioned at: 93 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 94 | # Pods/ 95 | # Add this line if you want to avoid checking in source code from the Xcode workspace 96 | # *.xcworkspace 97 | 98 | # Carthage 99 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 100 | # Carthage/Checkouts 101 | 102 | Carthage/Build/ 103 | 104 | # Accio dependency management 105 | Dependencies/ 106 | .accio/ 107 | 108 | # fastlane 109 | # It is recommended to not store the screenshots in the git repo. 110 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 111 | # For more information about the recommended setup visit: 112 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 113 | 114 | fastlane/report.xml 115 | fastlane/Preview.html 116 | fastlane/screenshots/**/*.png 117 | fastlane/test_output 118 | 119 | # Code Injection 120 | # After new code Injection tools there's a generated folder /iOSInjectionProject 121 | # https://github.com/johnno1962/injectionforxcode 122 | 123 | iOSInjectionProject/ 124 | 125 | ### Xcode ### 126 | 127 | ## Xcode 8 and earlier 128 | 129 | ### Xcode Patch ### 130 | *.xcodeproj/* 131 | !*.xcodeproj/project.pbxproj 132 | !*.xcodeproj/xcshareddata/ 133 | !*.xcodeproj/project.xcworkspace/ 134 | !*.xcworkspace/contents.xcworkspacedata 135 | /*.gcno 136 | **/xcshareddata/WorkspaceSettings.xcsettings 137 | 138 | # End of https://www.toptal.com/developers/gitignore/api/swift,macos,xcode 139 | n -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - Inject `.bundle` additionally if `.deb` file contains both `.dylib` and `.bundle` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-2025 Lessica 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ARCHS := arm64 2 | TARGET := iphone:clang:latest:14.0 3 | 4 | INSTALL_TARGET_PROCESSES += TrollFools 5 | INSTALL_TARGET_PROCESSES += trollfoolscli 6 | 7 | include $(THEOS)/makefiles/common.mk 8 | 9 | XCODEPROJ_NAME = TrollFools 10 | TrollFools_XCODE_SCHEME = TrollFools 11 | 12 | include $(THEOS_MAKE_PATH)/xcodeproj.mk 13 | 14 | SUBPROJECTS += TrollFoolsTweak 15 | 16 | include $(THEOS_MAKE_PATH)/aggregate.mk 17 | 18 | before-all:: 19 | devkit/standardize-entitlements.sh 20 | 21 | before-package:: 22 | $(ECHO_NOTHING)ldid -STrollFools/TrollFools.entitlements $(THEOS_STAGING_DIR)/Applications/TrollFools.app$(ECHO_END) 23 | $(ECHO_NOTHING)ldid -STrollFools/TrollFools.entitlements $(THEOS_STAGING_DIR)/usr/local/bin/trollfoolscli$(ECHO_END) 24 | 25 | export THEOS_PACKAGE_INSTALL_PREFIX 26 | export THEOS_STAGING_DIR 27 | after-package:: 28 | devkit/tipa.sh -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TrollFools 2 | 3 | [now-on-havoc]: https://havoc.app/package/trollfools 4 | 5 | [][now-on-havoc] 6 | 7 | In-place tweak injection with insert_dylib and ChOma. 8 | Proudly written in SwiftUI. 9 | 10 | Expected to work on all iOS versions supported by opa334’s TrollStore (i.e. iOS 14.0 - 17.0). 11 | 12 | ## Limitations 13 | 14 | - [x] Removable system applications 15 | - [x] Decrypted App Store applications (TrollStore applications) 16 | - [x] Encrypted App Store applications with bare dynamic library 17 | 18 | ## Build 19 | 20 | See GitHub Actions for the latest build status. 21 | 22 | PRs are always welcome. 23 | 24 | ## Milestones 25 | 26 | - [x] `optool` is buggy so we need to compile a statically linked `install_name_tool` or `llvm-install-name-tool` on iOS to achieve a smaller package size. 27 | - [x] Support for `.deb` or `.zip`. 28 | 29 | ## Credits 30 | 31 | This project is inspired by [Patched-TS-App](https://github.com/34306/Patched-TS-App) by **[Huy Nguyen](https://x.com/Little_34306) and [Nathan](https://x.com/dedbeddedbed)**. 32 | 33 | - [ChOma](https://github.com/opa334/ChOma) by [@opa334](https://github.com/opa334) and [@alfiecg24](https://github.com/alfiecg24) 34 | - [MachOKit](https://github.com/p-x9/MachOKit) by [@p-x9](https://github.com/p-x9) 35 | - [insert_dylib](https://github.com/tyilo/insert_dylib) by [@tyilo](https://github.com/tyilo) 36 | 37 | ## License 38 | 39 | See [LICENSE](LICENSE). 40 | -------------------------------------------------------------------------------- /TrollFools.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TrollFools.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TrollFools.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "fcdbf6cfa7a3a4ec5d531775250bf50c00698f54fe6ced65e20943f02920334b", 3 | "pins" : [ 4 | { 5 | "identity" : "ararchivekit", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/LebJe/ArArchiveKit.git", 8 | "state" : { 9 | "revision" : "ec33ce8bcb0be483e80ed49c95413e64e71e157b", 10 | "version" : "0.3.0" 11 | } 12 | }, 13 | { 14 | "identity" : "bitbytedata", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/tsolomko/BitByteData", 17 | "state" : { 18 | "revision" : "cdcdc5177ad536cfb11b95c620f926a81014b7fe", 19 | "version" : "2.0.4" 20 | } 21 | }, 22 | { 23 | "identity" : "cocoalumberjack", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/CocoaLumberjack/CocoaLumberjack.git", 26 | "state" : { 27 | "revision" : "4b8714a7fb84d42393314ce897127b3939885ec3", 28 | "version" : "3.8.5" 29 | } 30 | }, 31 | { 32 | "identity" : "machokit", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/Lessica/MachOKit.git", 35 | "state" : { 36 | "branch" : "patch/trollfools", 37 | "revision" : "43c4b5d59ab75c91ad689fb4a39a8a3913229535" 38 | } 39 | }, 40 | { 41 | "identity" : "swcompression", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/tsolomko/SWCompression.git", 44 | "state" : { 45 | "revision" : "390e0b0af8dd19a600005a242a89e570ff482e09", 46 | "version" : "4.8.6" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-argument-parser", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/apple/swift-argument-parser.git", 53 | "state" : { 54 | "revision" : "41982a3656a71c768319979febd796c6fd111d5c", 55 | "version" : "1.5.0" 56 | } 57 | }, 58 | { 59 | "identity" : "swift-collections", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/apple/swift-collections.git", 62 | "state" : { 63 | "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", 64 | "version" : "1.1.4" 65 | } 66 | }, 67 | { 68 | "identity" : "swift-log", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/apple/swift-log", 71 | "state" : { 72 | "revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91", 73 | "version" : "1.6.2" 74 | } 75 | }, 76 | { 77 | "identity" : "swiftui-introspect", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/siteline/swiftui-introspect.git", 80 | "state" : { 81 | "revision" : "668a65735751432b640260c56dfa621cec568368", 82 | "version" : "1.2.0" 83 | } 84 | }, 85 | { 86 | "identity" : "zipfoundation", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/weichsel/ZIPFoundation.git", 89 | "state" : { 90 | "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", 91 | "version" : "0.9.19" 92 | } 93 | } 94 | ], 95 | "version" : 3 96 | } 97 | -------------------------------------------------------------------------------- /TrollFools.xcodeproj/xcshareddata/xcschemes/trollfoolscli.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 61 | 63 | 69 | 70 | 71 | 72 | 74 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /TrollFools/App.swift: -------------------------------------------------------------------------------- 1 | // 2 | // App.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | 11 | final class App: Identifiable, ObservableObject { 12 | let id: String 13 | let name: String 14 | let latinName: String 15 | let type: String 16 | let teamID: String 17 | let url: URL 18 | let version: String? 19 | let isAdvertisement: Bool 20 | 21 | @Published var isDetached: Bool = false 22 | @Published var isAllowedToAttachOrDetach: Bool 23 | @Published var isInjected: Bool = false 24 | 25 | lazy var icon: UIImage? = UIImage._applicationIconImage(forBundleIdentifier: id, format: 0, scale: 3.0) 26 | var alternateIcon: UIImage? 27 | 28 | lazy var isUser: Bool = type == "User" 29 | lazy var isSystem: Bool = !isUser 30 | lazy var isFromApple: Bool = id.hasPrefix("com.apple.") 31 | lazy var isFromTroll: Bool = isSystem && !isFromApple 32 | lazy var isRemovable: Bool = url.path.contains("/var/containers/Bundle/Application/") 33 | 34 | weak var appList: AppListModel? 35 | private var cancellables: Set = [] 36 | private static let reloadSubject = PassthroughSubject() 37 | 38 | init( 39 | id: String, 40 | name: String, 41 | type: String, 42 | teamID: String, 43 | url: URL, 44 | version: String? = nil, 45 | alternateIcon: UIImage? = nil, 46 | isAdvertisement: Bool = false 47 | ) { 48 | self.id = id 49 | self.name = name 50 | self.type = type 51 | self.teamID = teamID 52 | self.url = url 53 | self.version = version 54 | self.isDetached = InjectorV3.main.isMetadataDetachedInBundle(url) 55 | self.isAllowedToAttachOrDetach = type == "User" && InjectorV3.main.isAllowedToAttachOrDetachMetadataInBundle(url) 56 | self.isInjected = InjectorV3.main.checkIsInjectedAppBundle(url) 57 | self.alternateIcon = alternateIcon 58 | self.isAdvertisement = isAdvertisement 59 | self.latinName = name 60 | .applyingTransform(.toLatin, reverse: false)? 61 | .applyingTransform(.stripDiacritics, reverse: false)? 62 | .components(separatedBy: .whitespaces) 63 | .joined() ?? "" 64 | Self.reloadSubject 65 | .filter { $0 == id } 66 | .sink { [weak self] _ in 67 | self?._reload() 68 | } 69 | .store(in: &cancellables) 70 | } 71 | 72 | func reload() { 73 | Self.reloadSubject.send(id) 74 | } 75 | 76 | private func _reload() { 77 | reloadDetachedStatus() 78 | reloadInjectedStatus() 79 | } 80 | 81 | private func reloadDetachedStatus() { 82 | self.isDetached = InjectorV3.main.isMetadataDetachedInBundle(url) 83 | self.isAllowedToAttachOrDetach = isUser && InjectorV3.main.isAllowedToAttachOrDetachMetadataInBundle(url) 84 | } 85 | 86 | private func reloadInjectedStatus() { 87 | self.isInjected = InjectorV3.main.checkIsInjectedAppBundle(url) 88 | } 89 | } 90 | 91 | extension App { 92 | static let advertisementApp: App = { 93 | [ 94 | App( 95 | id: NSLocalizedString("Record your phone calls like never before.", comment: ""), 96 | name: NSLocalizedString("TrollRecorder", comment: ""), 97 | type: "System", 98 | teamID: "GXZ23M5TP2", 99 | url: URL(string: "https://havoc.app/package/trollrecorder")!, 100 | alternateIcon: .init(named: "tricon-default"), 101 | isAdvertisement: true 102 | ), 103 | App( 104 | id: NSLocalizedString("Bringing back the most advanced system and security analysis tool.", comment: ""), 105 | name: NSLocalizedString("Reveil", comment: ""), 106 | type: "System", 107 | teamID: "GXZ23M5TP2", 108 | url: URL(string: "https://havoc.app/package/reveil")!, 109 | alternateIcon: .init(named: "reveil-default"), 110 | isAdvertisement: true 111 | ), 112 | ].randomElement()! 113 | }() 114 | } 115 | -------------------------------------------------------------------------------- /TrollFools/AppListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppListCell.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | import CocoaLumberjackSwift 9 | import SwiftUI 10 | 11 | struct AppListCell: View { 12 | @EnvironmentObject var appList: AppListModel 13 | 14 | @StateObject var app: App 15 | 16 | @available(iOS 15, *) 17 | var highlightedName: AttributedString { 18 | let name = app.name 19 | var attributedString = AttributedString(name) 20 | if let range = attributedString.range(of: appList.filter.searchKeyword, options: [.caseInsensitive, .diacriticInsensitive]) { 21 | attributedString[range].foregroundColor = .accentColor 22 | } 23 | return attributedString 24 | } 25 | 26 | @available(iOS 15, *) 27 | var highlightedId: AttributedString { 28 | let id = app.id 29 | var attributedString = AttributedString(id) 30 | if let range = attributedString.range(of: appList.filter.searchKeyword, options: [.caseInsensitive, .diacriticInsensitive]) { 31 | attributedString[range].foregroundColor = .accentColor 32 | } 33 | return attributedString 34 | } 35 | 36 | var body: some View { 37 | HStack(spacing: 12) { 38 | if #available(iOS 15, *) { 39 | Image(uiImage: app.alternateIcon ?? app.icon ?? UIImage()) 40 | .resizable() 41 | .frame(width: 32, height: 32) 42 | } else { 43 | Image(uiImage: app.alternateIcon ?? app.icon ?? UIImage()) 44 | .resizable() 45 | .frame(width: 32, height: 32) 46 | .clipShape(RoundedRectangle(cornerRadius: 8)) 47 | } 48 | 49 | VStack(alignment: .leading, spacing: 2) { 50 | HStack(spacing: 4) { 51 | if #available(iOS 15, *) { 52 | Text(highlightedName) 53 | .font(.headline) 54 | .lineLimit(1) 55 | } else { 56 | Text(app.name) 57 | .font(.headline) 58 | .lineLimit(1) 59 | } 60 | 61 | if app.isInjected { 62 | Image(systemName: "bandage") 63 | .font(.subheadline) 64 | .foregroundColor(.orange) 65 | .accessibilityLabel(NSLocalizedString("Patched", comment: "")) 66 | .transition(.opacity) 67 | } 68 | } 69 | .animation(.easeOut, value: app.isInjected) 70 | 71 | if #available(iOS 15, *) { 72 | Text(highlightedId) 73 | .font(.subheadline) 74 | .lineLimit(app.isAdvertisement ? 2 : 1) 75 | } else { 76 | Text(app.id) 77 | .font(.subheadline) 78 | .lineLimit(app.isAdvertisement ? 2 : 1) 79 | } 80 | } 81 | 82 | Spacer() 83 | 84 | if let version = app.version { 85 | if app.isUser && app.isDetached { 86 | HStack(spacing: 4) { 87 | Image(systemName: "lock") 88 | .font(.subheadline) 89 | .foregroundColor(.red) 90 | .accessibilityLabel(NSLocalizedString("Pinned Version", comment: "")) 91 | 92 | Text(version) 93 | .font(.subheadline) 94 | .foregroundColor(.secondary) 95 | .lineLimit(1) 96 | } 97 | } else { 98 | Text(version) 99 | .font(.subheadline) 100 | .foregroundColor(.secondary) 101 | .lineLimit(1) 102 | } 103 | } else if app.isAdvertisement { 104 | Image("badge-ad") 105 | .scaleEffect(1.2) 106 | } 107 | } 108 | .contextMenu { 109 | if !appList.isSelectorMode && !app.isAdvertisement { 110 | cellContextMenuWrapper 111 | } 112 | } 113 | .background(cellBackground) 114 | } 115 | 116 | @ViewBuilder 117 | var cellContextMenu: some View { 118 | Button { 119 | launch() 120 | } label: { 121 | Label(NSLocalizedString("Launch", comment: ""), systemImage: "command") 122 | } 123 | 124 | if AppListModel.hasTrollStore && app.isAllowedToAttachOrDetach { 125 | if app.isDetached { 126 | Button { 127 | do { 128 | try InjectorV3(app.url).setMetadataDetached(false) 129 | app.reload() 130 | appList.isRebuildNeeded = true 131 | } catch { DDLogError("\(error)", ddlog: InjectorV3.main.logger) } 132 | } label: { 133 | Label(NSLocalizedString("Unlock Version", comment: ""), systemImage: "lock.open") 134 | } 135 | } else { 136 | Button { 137 | do { 138 | try InjectorV3(app.url).setMetadataDetached(true) 139 | app.reload() 140 | appList.isRebuildNeeded = true 141 | } catch { DDLogError("\(error)", ddlog: InjectorV3.main.logger) } 142 | } label: { 143 | Label(NSLocalizedString("Lock Version", comment: ""), systemImage: "lock") 144 | } 145 | } 146 | } 147 | 148 | Button { 149 | openInFilza() 150 | } label: { 151 | if isFilzaInstalled { 152 | Label(NSLocalizedString("Show in Filza", comment: ""), systemImage: "scope") 153 | } else { 154 | Label(NSLocalizedString("Filza (URL Scheme) Not Installed", comment: ""), systemImage: "xmark.octagon") 155 | } 156 | } 157 | .disabled(!isFilzaInstalled) 158 | } 159 | 160 | @ViewBuilder 161 | var cellContextMenuWrapper: some View { 162 | if #available(iOS 16, *) { 163 | // iOS 16 164 | cellContextMenu 165 | } else { 166 | if #available(iOS 15, *) { } 167 | else { 168 | // iOS 14 169 | cellContextMenu 170 | } 171 | } 172 | } 173 | 174 | @ViewBuilder 175 | var cellBackground: some View { 176 | if #available(iOS 15, *) { 177 | if #available(iOS 16, *) { } 178 | else { 179 | // iOS 15 180 | Color.clear 181 | .contextMenu { 182 | if !appList.isSelectorMode { 183 | cellContextMenu 184 | } 185 | } 186 | .id(app.isDetached) 187 | } 188 | } 189 | } 190 | 191 | private func launch() { 192 | LSApplicationWorkspace.default().openApplication(withBundleID: app.id) 193 | } 194 | 195 | var isFilzaInstalled: Bool { appList.isFilzaInstalled } 196 | 197 | private func openInFilza() { 198 | appList.openInFilza(app.url) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /TrollFools/AppListModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppListModel.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | import Combine 9 | import OrderedCollections 10 | import SwiftUI 11 | 12 | final class AppListModel: ObservableObject { 13 | enum Scope: Int, CaseIterable { 14 | case all 15 | case user 16 | case troll 17 | case system 18 | 19 | var localizedShortName: String { 20 | switch self { 21 | case .all: 22 | NSLocalizedString("All", comment: "") 23 | case .user: 24 | NSLocalizedString("User", comment: "") 25 | case .troll: 26 | NSLocalizedString("TrollStore", comment: "") 27 | case .system: 28 | NSLocalizedString("System", comment: "") 29 | } 30 | } 31 | 32 | var localizedName: String { 33 | switch self { 34 | case .all: 35 | NSLocalizedString("All Applications", comment: "") 36 | case .user: 37 | NSLocalizedString("User Applications", comment: "") 38 | case .troll: 39 | NSLocalizedString("TrollStore Applications", comment: "") 40 | case .system: 41 | NSLocalizedString("Injectable System Applications", comment: "") 42 | } 43 | } 44 | } 45 | 46 | static let hasTrollStore: Bool = { LSApplicationProxy(forIdentifier: "com.opa334.TrollStore") != nil }() 47 | private var _allApplications: [App] = [] 48 | 49 | let selectorURL: URL? 50 | var isSelectorMode: Bool { selectorURL != nil } 51 | 52 | @Published var filter = FilterOptions() 53 | @Published var activeScope: Scope = .all 54 | @Published var activeScopeApps: OrderedDictionary = [:] 55 | 56 | @Published var isPaidProductInstalled: Bool = false 57 | @Published var unsupportedCount: Int = 0 58 | 59 | @Published var isFilzaInstalled: Bool = false 60 | private let filzaURL = URL(string: "filza://") 61 | 62 | @Published var isRebuildNeeded: Bool = false 63 | 64 | private let applicationChanged = PassthroughSubject() 65 | private var cancellables = Set() 66 | 67 | init(selectorURL: URL? = nil) { 68 | self.selectorURL = selectorURL 69 | reload() 70 | 71 | Publishers.CombineLatest( 72 | $filter, 73 | $activeScope 74 | ) 75 | .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true) 76 | .sink { [weak self] _ in 77 | self?.performFilter() 78 | } 79 | .store(in: &cancellables) 80 | 81 | applicationChanged 82 | .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true) 83 | .sink { [weak self] _ in 84 | self?.reload() 85 | } 86 | .store(in: &cancellables) 87 | 88 | let darwinCenter = CFNotificationCenterGetDarwinNotifyCenter() 89 | CFNotificationCenterAddObserver(darwinCenter, Unmanaged.passRetained(self).toOpaque(), { _, observer, _, _, _ in 90 | guard let observer = Unmanaged.fromOpaque(observer!).takeUnretainedValue() as AppListModel? else { 91 | return 92 | } 93 | observer.applicationChanged.send() 94 | }, "com.apple.LaunchServices.ApplicationsChanged" as CFString, nil, .coalesce) 95 | } 96 | 97 | deinit { 98 | let darwinCenter = CFNotificationCenterGetDarwinNotifyCenter() 99 | CFNotificationCenterRemoveObserver(darwinCenter, Unmanaged.passUnretained(self).toOpaque(), nil, nil) 100 | } 101 | 102 | func reload() { 103 | let allApplications = Self.fetchApplications(&isPaidProductInstalled, &unsupportedCount) 104 | allApplications.forEach { $0.appList = self } 105 | _allApplications = allApplications 106 | if let filzaURL { 107 | isFilzaInstalled = UIApplication.shared.canOpenURL(filzaURL) 108 | } else { 109 | isFilzaInstalled = false 110 | } 111 | performFilter() 112 | } 113 | 114 | func performFilter() { 115 | var filteredApplications = _allApplications 116 | 117 | if !filter.searchKeyword.isEmpty { 118 | filteredApplications = filteredApplications.filter { 119 | $0.name.localizedCaseInsensitiveContains(filter.searchKeyword) || $0.id.localizedCaseInsensitiveContains(filter.searchKeyword) || 120 | ( 121 | $0.latinName.localizedCaseInsensitiveContains( 122 | filter.searchKeyword 123 | .components(separatedBy: .whitespaces).joined() 124 | ) 125 | ) 126 | } 127 | } 128 | 129 | if filter.showPatchedOnly { 130 | filteredApplications = filteredApplications.filter { $0.isInjected } 131 | } 132 | 133 | switch activeScope { 134 | case .all: 135 | activeScopeApps = Self.groupedAppList(filteredApplications) 136 | case .user: 137 | activeScopeApps = Self.groupedAppList(filteredApplications.filter { $0.isUser }) 138 | case .troll: 139 | activeScopeApps = Self.groupedAppList(filteredApplications.filter { $0.isFromTroll }) 140 | case .system: 141 | activeScopeApps = Self.groupedAppList(filteredApplications.filter { $0.isFromApple }) 142 | } 143 | } 144 | 145 | private static let excludedIdentifiers: Set = [ 146 | "com.opa334.Dopamine", 147 | "org.coolstar.SileoStore", 148 | "xyz.willy.Zebra", 149 | ] 150 | 151 | private static func fetchApplications(_ isPaidProductInstalled: inout Bool, _ unsupportedCount: inout Int) -> [App] { 152 | let allApps: [App] = LSApplicationWorkspace.default() 153 | .allApplications() 154 | .compactMap { proxy in 155 | guard let id = proxy.applicationIdentifier(), 156 | let url = proxy.bundleURL(), 157 | let teamID = proxy.teamID(), 158 | let appType = proxy.applicationType(), 159 | let localizedName = proxy.localizedName() 160 | else { 161 | return nil 162 | } 163 | 164 | if id == "wiki.qaq.trapp" || id == "com.82flex.reveil" { 165 | isPaidProductInstalled = true 166 | } 167 | 168 | guard !id.hasPrefix("wiki.qaq.") && !id.hasPrefix("com.82flex.") else { 169 | return nil 170 | } 171 | 172 | guard !excludedIdentifiers.contains(id) else { 173 | return nil 174 | } 175 | 176 | let shortVersionString: String? = proxy.shortVersionString() 177 | let app = App( 178 | id: id, 179 | name: localizedName, 180 | type: appType, 181 | teamID: teamID, 182 | url: url, 183 | version: shortVersionString 184 | ) 185 | 186 | if app.isUser && app.isFromApple { 187 | return nil 188 | } 189 | 190 | guard app.isRemovable else { 191 | return nil 192 | } 193 | 194 | return app 195 | } 196 | 197 | let filteredApps = allApps 198 | .filter { $0.isSystem || InjectorV3.main.checkIsEligibleAppBundle($0.url) } 199 | .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } 200 | 201 | unsupportedCount = allApps.count - filteredApps.count 202 | 203 | return filteredApps 204 | } 205 | } 206 | 207 | extension AppListModel { 208 | func openInFilza(_ url: URL) { 209 | guard let filzaURL else { 210 | return 211 | } 212 | let fileURL = filzaURL.appendingPathComponent(url.path) 213 | UIApplication.shared.open(fileURL) 214 | } 215 | 216 | func rebuildIconCache() { 217 | // Sadly, we can't call `trollstorehelper` directly because only TrollStore can launch it without error. 218 | DispatchQueue.global(qos: .userInitiated).async { 219 | LSApplicationWorkspace.default().openApplication(withBundleID: "com.opa334.TrollStore") 220 | } 221 | } 222 | } 223 | 224 | extension AppListModel { 225 | static let allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ#" 226 | private static let allowedCharacterSet = CharacterSet(charactersIn: allowedCharacters) 227 | 228 | private static func groupedAppList(_ apps: [App]) -> OrderedDictionary { 229 | var groupedApps = OrderedDictionary() 230 | 231 | for app in apps { 232 | var key = app.name 233 | .trimmingCharacters(in: .controlCharacters) 234 | .trimmingCharacters(in: .whitespacesAndNewlines) 235 | .applyingTransform(.stripCombiningMarks, reverse: false)? 236 | .applyingTransform(.toLatin, reverse: false)? 237 | .applyingTransform(.stripDiacritics, reverse: false)? 238 | .prefix(1).uppercased() ?? "#" 239 | 240 | if let scalar = UnicodeScalar(key) { 241 | if !allowedCharacterSet.contains(scalar) { 242 | key = "#" 243 | } 244 | } else { 245 | key = "#" 246 | } 247 | 248 | if groupedApps[key] == nil { 249 | groupedApps[key] = [] 250 | } 251 | 252 | groupedApps[key]?.append(app) 253 | } 254 | 255 | groupedApps.sort { app1, app2 in 256 | if let c1 = app1.key.first, 257 | let c2 = app2.key.first, 258 | let idx1 = allowedCharacters.firstIndex(of: c1), 259 | let idx2 = allowedCharacters.firstIndex(of: c2) 260 | { 261 | return idx1 < idx2 262 | } 263 | return app1.key < app2.key 264 | } 265 | 266 | return groupedApps 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /TrollFools/AppListSearchModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppListSearchModel.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 3/8/25. 6 | // 7 | 8 | import Combine 9 | import UIKit 10 | 11 | final class AppListSearchModel: NSObject, ObservableObject { 12 | @Published var searchKeyword: String = "" 13 | @Published var searchScopeIndex: Int = 0 14 | 15 | weak var searchController: UISearchController? 16 | weak var forwardSearchBarDelegate: (any UISearchBarDelegate)? 17 | } 18 | 19 | extension AppListSearchModel: UISearchBarDelegate, UISearchResultsUpdating { 20 | func updateSearchResults(for searchController: UISearchController) { 21 | searchKeyword = searchController.searchBar.text ?? "" 22 | } 23 | 24 | func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { 25 | forwardSearchBarDelegate?.searchBarShouldBeginEditing?(searchBar) ?? true 26 | } 27 | 28 | func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { 29 | forwardSearchBarDelegate?.searchBarTextDidBeginEditing?(searchBar) 30 | } 31 | 32 | func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool { 33 | forwardSearchBarDelegate?.searchBarShouldEndEditing?(searchBar) ?? true 34 | } 35 | 36 | func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { 37 | forwardSearchBarDelegate?.searchBarTextDidEndEditing?(searchBar) 38 | } 39 | 40 | func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { 41 | forwardSearchBarDelegate?.searchBar?(searchBar, textDidChange: searchText) 42 | } 43 | 44 | func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { 45 | forwardSearchBarDelegate?.searchBar?(searchBar, shouldChangeTextIn: range, replacementText: text) ?? true 46 | } 47 | 48 | func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { 49 | forwardSearchBarDelegate?.searchBarSearchButtonClicked?(searchBar) 50 | } 51 | 52 | func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar) { 53 | forwardSearchBarDelegate?.searchBarBookmarkButtonClicked?(searchBar) 54 | } 55 | 56 | func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { 57 | forwardSearchBarDelegate?.searchBarCancelButtonClicked?(searchBar) 58 | } 59 | 60 | func searchBarResultsListButtonClicked(_ searchBar: UISearchBar) { 61 | forwardSearchBarDelegate?.searchBarResultsListButtonClicked?(searchBar) 62 | } 63 | 64 | func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) { 65 | searchScopeIndex = selectedScope 66 | forwardSearchBarDelegate?.searchBar?(searchBar, selectedScopeButtonIndexDidChange: selectedScope) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/AppIcon.appiconset/AppIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/Assets.xcassets/AppIcon.appiconset/AppIcon.png -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/badge-ad.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "badge-ad.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/badge-ad.imageset/badge-ad.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/reveil-default.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_128x128.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/reveil-default.imageset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/Assets.xcassets/reveil-default.imageset/icon_128x128.png -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/tricon-default.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_128x128.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TrollFools/Assets.xcassets/tricon-default.imageset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/Assets.xcassets/tricon-default.imageset/icon_128x128.png -------------------------------------------------------------------------------- /TrollFools/AuxiliaryExecute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuxiliaryExecute.swift 3 | // TrollFools 4 | // 5 | // Created by Lakr Aream on 2021/11/27. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Execute command or shell with posix, shared with AuxiliaryExecute.local 11 | public class AuxiliaryExecute { 12 | /// we do not recommend you to subclass this singleton 13 | public static let local = AuxiliaryExecute() 14 | 15 | // if binary not found when you call the shell api 16 | // we will take some time to rebuild the bianry table each time 17 | // -->>> this is a time-heavy-task 18 | // so use binaryLocationFor(command:) to cache it if needed 19 | 20 | // system path 21 | internal var currentPath: [String] = [] 22 | // system binary table 23 | internal var binaryTable: [String: String] = [:] 24 | 25 | // for you to put your own search path 26 | internal var extraSearchPath: [String] = [] 27 | // for you to set your own binary table and will be used firstly 28 | // if you set nil here 29 | // -> we will return nil even the binary found in system path 30 | internal var overwriteTable: [String: String?] = [:] 31 | 32 | // this value is used when providing 0 or negative timeout paramete 33 | internal static let maxTimeoutValue: Double = 2147483647 34 | 35 | /// when reading from file pipe, must called from async queue 36 | internal static let pipeControlQueue = DispatchQueue( 37 | label: "wiki.qaq.AuxiliaryExecute.pipeRead", 38 | attributes: .concurrent 39 | ) 40 | 41 | /// when killing process or monitoring events from process, must called from async queue 42 | /// we are making this queue serial queue so won't called at the same time when timeout 43 | internal static let processControlQueue = DispatchQueue( 44 | label: "wiki.qaq.AuxiliaryExecute.processControl", 45 | attributes: [] 46 | ) 47 | 48 | /// used for setting binary table, avoid crash 49 | internal let lock = NSLock() 50 | 51 | /// nope! 52 | private init() { 53 | // no need to setup binary table 54 | // we will make call to it when you call the shell api 55 | // if you only use the spawn api 56 | // we do not need to setup the hole table cause it is a time-heavy-task 57 | } 58 | 59 | /// Execution Error, do the localization your self 60 | public enum ExecuteError: Error, LocalizedError, Codable { 61 | // not found in path 62 | case commandNotFound 63 | // invalid, may be missing, wrong permission or any other reason 64 | case commandInvalid 65 | // fcntl failed 66 | case openFilePipeFailed 67 | // posix failed 68 | case posixSpawnFailed 69 | // waitpid failed 70 | case waitPidFailed 71 | // timeout when execute 72 | case timeout 73 | } 74 | 75 | public enum TerminationReason: Codable { 76 | case exit(Int32) 77 | case uncaughtSignal(Int32) 78 | } 79 | 80 | public struct PersonaOptions: Codable { 81 | let uid: uid_t 82 | let gid: gid_t 83 | } 84 | 85 | /// Execution Receipt 86 | public struct ExecuteReceipt: Codable { 87 | // exit code when process exit, 88 | // or signal code when process terminated by signal 89 | public let terminationReason: TerminationReason 90 | // process pid that was when it is alive 91 | // -1 means spawn failed in some situation 92 | public let pid: Int 93 | // wait result for final waitpid inside block at 94 | // processSource - eventMask.exit, usually is pid 95 | // -1 for other cases 96 | public let wait: Int 97 | // any error from us, not the command it self 98 | // DOES NOT MEAN THAT THE COMMAND DONE WELL 99 | public let error: ExecuteError? 100 | // stdout 101 | public let stdout: String 102 | // stderr 103 | public let stderr: String 104 | 105 | /// General initialization of receipt object 106 | /// - Parameters: 107 | /// - terminationReason: termination reason 108 | /// - pid: pid when process alive 109 | /// - wait: wait result on waitpid 110 | /// - error: error if any 111 | /// - stdout: stdout 112 | /// - stderr: stderr 113 | internal init( 114 | terminationReason: TerminationReason, 115 | pid: Int, 116 | wait: Int, 117 | error: AuxiliaryExecute.ExecuteError?, 118 | stdout: String, 119 | stderr: String 120 | ) { 121 | self.terminationReason = terminationReason 122 | self.pid = pid 123 | self.wait = wait 124 | self.error = error 125 | self.stdout = stdout 126 | self.stderr = stderr 127 | } 128 | 129 | /// Template for making failure receipt 130 | /// - Parameters: 131 | /// - terminationReason: default uncaught signal 0 132 | /// - pid: default -1 133 | /// - wait: default -1 134 | /// - error: error 135 | /// - stdout: default empty 136 | /// - stderr: default empty 137 | internal static func failure( 138 | terminationReason: TerminationReason = .uncaughtSignal(0), 139 | pid: Int = -1, 140 | wait: Int = -1, 141 | error: AuxiliaryExecute.ExecuteError?, 142 | stdout: String = "", 143 | stderr: String = "" 144 | ) -> ExecuteReceipt { 145 | .init( 146 | terminationReason: terminationReason, 147 | pid: pid, 148 | wait: wait, 149 | error: error, 150 | stdout: stdout, 151 | stderr: stderr 152 | ) 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /TrollFools/BartyCrouch.swift: -------------------------------------------------------------------------------- 1 | // This file is required in order for the `transform` task of the translation helper tool BartyCrouch to work. 2 | // See here for more details: https://github.com/FlineDev/BartyCrouch 3 | 4 | import Foundation 5 | 6 | enum BartyCrouch { 7 | enum SupportedLanguage: String { 8 | case english = "en" 9 | case chineseSimplified = "zh-Hans" 10 | case vietnamese = "vi" 11 | case italian = "it" 12 | } 13 | 14 | static func translate(key: String, translations: [SupportedLanguage: String], comment _: String? = nil) -> String { 15 | let typeName = String(describing: BartyCrouch.self) 16 | let methodName = #function 17 | 18 | print( 19 | "Warning: [BartyCrouch]", 20 | "Untransformed \(typeName).\(methodName) method call found with key '\(key)' and base translations '\(translations)'.", 21 | "Please ensure that BartyCrouch is installed and configured correctly." 22 | ) 23 | 24 | // fall back in case something goes wrong with BartyCrouch transformation 25 | return "BC: TRANSFORMATION FAILED!" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TrollFools/CLI/CmdEject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CmdEject.swift 3 | // TrollFools 4 | // 5 | // Created by Rachel on 10/3/2025. 6 | // 7 | 8 | import ArgumentParser 9 | import Foundation 10 | 11 | struct CmdEject: ParsableCommand { 12 | static var configuration = CommandConfiguration( 13 | commandName: "eject", 14 | abstract: "Eject plugins from the specified application." 15 | ) 16 | 17 | @Argument(help: "The bundle identifier of the application.") 18 | var bundleIdentifier: String 19 | 20 | @Option(name: [.customLong("path"), .customShort("p")], help: "The path of the plugin.") 21 | var pluginPath: String? 22 | 23 | @Flag(name: [.customLong("all")], help: "Eject all plugins.") 24 | var ejectAll: Bool = false 25 | 26 | func validate() throws { 27 | if ejectAll && pluginPath != nil { 28 | throw ArgumentParser.ValidationError( 29 | "The --all flag and --path option cannot be used at the same time." 30 | ) 31 | } 32 | if !ejectAll && pluginPath == nil { 33 | throw ArgumentParser.ValidationError( 34 | "Either --all flag or --path option must be specified." 35 | ) 36 | } 37 | } 38 | 39 | func run() throws { 40 | guard let app = LSApplicationProxy(forIdentifier: bundleIdentifier), 41 | let bundleURL = app.bundleURL() 42 | else { 43 | throw ArgumentParser.ValidationError("The specified application does not exist.") 44 | } 45 | if let pluginPath { 46 | if let pluginURL = URL(string: pluginPath), 47 | FileManager.default.fileExists(atPath: pluginPath) { 48 | try InjectorV3(bundleURL, loggerType: .os).eject([pluginURL]) 49 | } else { 50 | throw ArgumentParser.ValidationError("The specified plugin path is invalid.") 51 | } 52 | } else if ejectAll { 53 | try InjectorV3(bundleURL, loggerType: .os).ejectAll() 54 | } else { 55 | throw ArgumentParser.ValidationError("No plugin to eject.") 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /TrollFools/CLI/CmdInject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CmdInject.swift 3 | // TrollFools 4 | // 5 | // Created by Rachel on 10/3/2025. 6 | // 7 | 8 | import ArgumentParser 9 | import Foundation 10 | 11 | struct CmdInject: ParsableCommand { 12 | static var configuration = CommandConfiguration( 13 | commandName: "inject", 14 | abstract: "Inject a persistent payload to a target application" 15 | ) 16 | 17 | @Argument(help: "The bundle identifier of the application.") 18 | var bundleIdentifier: String 19 | 20 | @Option(name: [.customLong("path"), .customShort("p")], parsing: .upToNextOption, help: "The path of the plugin.") 21 | var pluginPaths: [String] 22 | 23 | @Flag(name: [.customLong("fast")], help: "Use fast injection strategy.") 24 | var fastInjection: Bool = false 25 | 26 | @Flag(name: [.customLong("weak")], help: "Use weak reference.") 27 | var weakReference: Bool = false 28 | 29 | func run() throws { 30 | guard let app = LSApplicationProxy(forIdentifier: bundleIdentifier), 31 | let appID = app.applicationIdentifier(), 32 | let bundleURL = app.bundleURL() 33 | else { 34 | throw ArgumentParser.ValidationError("The specified application does not exist.") 35 | } 36 | try pluginPaths.forEach { 37 | guard FileManager.default.fileExists(atPath: $0) else { 38 | throw ArgumentParser.ValidationError("This plugin does not exist: \($0)") 39 | } 40 | } 41 | let pluginURLs = pluginPaths.compactMap { URL(fileURLWithPath: $0) } 42 | let injector = try InjectorV3(bundleURL, loggerType: .os) 43 | if injector.appID.isEmpty { 44 | injector.appID = appID 45 | } 46 | if injector.teamID.isEmpty { 47 | if let teamID = app.teamID() { 48 | injector.teamID = teamID 49 | } else { 50 | injector.teamID = "0000000000" 51 | } 52 | } 53 | injector.useWeakReference = weakReference 54 | injector.injectStrategy = fastInjection ? .fast : .lexicographic 55 | try injector.inject(pluginURLs) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /TrollFools/CLI/CmdList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CmdList.swift 3 | // TrollFools 4 | // 5 | // Created by Rachel on 10/3/2025. 6 | // 7 | 8 | import ArgumentParser 9 | import Foundation 10 | 11 | struct CmdList: ParsableCommand { 12 | static var configuration = CommandConfiguration( 13 | commandName: "list", 14 | abstract: "List all the applications." 15 | ) 16 | 17 | @Flag(name: [.customLong("user")], help: "Print user applications only.") 18 | var userOnly = false 19 | 20 | func run() throws { 21 | struct App { 22 | let identifier: String 23 | let localizedName: String 24 | } 25 | (LSApplicationWorkspace.default().allApplications() ?? []) 26 | .compactMap { app -> App? in 27 | guard let identifier = app.applicationIdentifier(), 28 | let localizedName = app.localizedName() 29 | else { 30 | return nil 31 | } 32 | if userOnly, let type = app.applicationType(), type.lowercased() != "user" { 33 | return nil 34 | } 35 | return App(identifier: identifier, localizedName: localizedName) 36 | } 37 | .sorted { $0.identifier < $1.identifier } 38 | .forEach { app in 39 | print("\(app.identifier) = \(app.localizedName)") 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /TrollFools/CLI/CmdView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CmdView.swift 3 | // TrollFools 4 | // 5 | // Created by Rachel on 10/3/2025. 6 | // 7 | 8 | import ArgumentParser 9 | import Foundation 10 | 11 | struct CmdView: ParsableCommand { 12 | static var configuration = CommandConfiguration( 13 | commandName: "view", 14 | abstract: "View the details of the specified application." 15 | ) 16 | 17 | @Argument(help: "The bundle identifier of the application.") 18 | var bundleIdentifier: String 19 | 20 | func run() throws { 21 | guard let app = LSApplicationProxy(forIdentifier: bundleIdentifier), 22 | let bundleURL = app.bundleURL() 23 | else { 24 | throw ArgumentParser.ValidationError("The specified application does not exist.") 25 | } 26 | var pluginContent = "" 27 | try InjectorV3(bundleURL, loggerType: .os) 28 | .injectedAssetURLsInBundle(bundleURL) 29 | .enumerated() 30 | .forEach { url in 31 | pluginContent += "PLUGIN-\(url.offset) = \(url.element.path)\n" 32 | } 33 | print(""" 34 | ID = \(app.applicationIdentifier() ?? "(null)") 35 | NAME = \(app.localizedName() ?? "(null)") 36 | VERSION = \(app.shortVersionString() ?? "(null)") 37 | TYPE = \(app.applicationType() ?? "(null)") 38 | TEAM = \(app.teamID() ?? "(null)") 39 | BUNDLE = \(app.bundleURL()?.path ?? "(null)") 40 | \(pluginContent) 41 | """) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /TrollFools/CLI/Entry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Entry.swift 3 | // trollfoolscli 4 | // 5 | // Created by 82Flex on 3/8/25. 6 | // 7 | 8 | import ArgumentParser 9 | import Foundation 10 | 11 | @main 12 | struct Entry: ParsableCommand { 13 | static var configuration = CommandConfiguration( 14 | commandName: "trollfoolscli", 15 | abstract: "In-place tweak injection with insert_dylib and ChOma.", 16 | version: TFGetDisplayVersion(), 17 | subcommands: [ 18 | CmdList.self, 19 | CmdView.self, 20 | CmdInject.self, 21 | CmdEject.self, 22 | ] 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /TrollFools/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 3/8/25. 6 | // 7 | 8 | let gTrollFoolsIdentifier = "wiki.qaq.TrollFools" 9 | let gTrollFoolsErrorDomain = "\(gTrollFoolsIdentifier).error" 10 | -------------------------------------------------------------------------------- /TrollFools/CydiaSubstrate.framework.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/CydiaSubstrate.framework.zip -------------------------------------------------------------------------------- /TrollFools/DisclaimerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DisclaimerView.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 3/13/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DisclaimerView: View { 11 | @Binding var isDisclaimerHidden: Bool 12 | 13 | var body: some View { 14 | NavigationView { 15 | ScrollView { 16 | VStack(alignment: .leading, spacing: 20) { 17 | Text(""" 18 | 本开源软件仅用于 iOS 逆向工程技术交流与学习目的,严禁任何非法或商业用途。用户在使用过程中应严格遵守所在地法律法规,因滥用造成的任何法律责任均与开发者/发布者无关。开发者不对软件功能的完整性、适用性及使用后果承担责任,亦不鼓励任何违反服务条款或侵害知识产权的行为。 19 | """) 20 | 21 | Text(""" 22 | This open-source software is provided solely for educational and technical exchange purposes in iOS reverse engineering studies. Any illegal or commercial use is strictly prohibited. Users must comply with all applicable local laws and regulations. The developer/publisher shall not be held liable for any legal consequences arising from misuse. No warranties are provided regarding the software’s completeness, fitness for purpose, or consequences of use. The developer does not endorse any actions that violate service terms or infringe intellectual property rights. 23 | """) 24 | } 25 | .padding() 26 | .font(.body) 27 | } 28 | .navigationTitle("免责声明 / Disclaimer") 29 | .toolbar { 30 | ToolbarItem(placement: .bottomBar) { 31 | Button { 32 | exit(EXIT_SUCCESS) 33 | } label: { 34 | VStack(spacing: 0) { 35 | Text("退出") 36 | .font(.body) 37 | .fontWeight(.bold) 38 | Text("Exit") 39 | .font(.footnote) 40 | } 41 | .foregroundColor(.red) 42 | .padding() 43 | } 44 | } 45 | 46 | ToolbarItem(placement: .bottomBar) { 47 | Button { 48 | isDisclaimerHidden = true 49 | } label: { 50 | VStack(spacing: 0) { 51 | Text("我已阅读并同意") 52 | .font(.body) 53 | .fontWeight(.bold) 54 | Text("I Have Read and Agree") 55 | .font(.footnote) 56 | } 57 | .foregroundColor(.accentColor) 58 | .padding() 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /TrollFools/EjectListModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EjectListModel.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | import Combine 9 | import SwiftUI 10 | 11 | final class EjectListModel: ObservableObject { 12 | let app: App 13 | private(set) var injectedPlugIns: [InjectedPlugIn] = [] 14 | 15 | @Published var filter = FilterOptions() 16 | @Published var filteredPlugIns: [InjectedPlugIn] = [] 17 | 18 | private var cancellables = Set() 19 | 20 | init(_ app: App) { 21 | self.app = app 22 | reload() 23 | 24 | $filter 25 | .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true) 26 | .sink { [weak self] _ in 27 | self?.performFilter() 28 | } 29 | .store(in: &cancellables) 30 | } 31 | 32 | func reload() { 33 | self.injectedPlugIns = InjectorV3.main.injectedAssetURLsInBundle(app.url) 34 | .map { InjectedPlugIn(url: $0) } 35 | performFilter() 36 | } 37 | 38 | func performFilter() { 39 | var filteredPlugIns = injectedPlugIns 40 | 41 | if !filter.searchKeyword.isEmpty { 42 | filteredPlugIns = filteredPlugIns.filter { 43 | $0.url.lastPathComponent.localizedCaseInsensitiveContains(filter.searchKeyword) 44 | } 45 | } 46 | 47 | self.filteredPlugIns = filteredPlugIns 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /TrollFools/EjectListSearchModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EjectListSearchModel.swift 3 | // TrollFools 4 | // 5 | // Created by Rachel on 9/3/2025. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | 11 | final class EjectListSearchViewModel: NSObject, UISearchResultsUpdating, ObservableObject { 12 | @Published var searchKeyword: String = "" 13 | 14 | weak var searchController: UISearchController? 15 | 16 | func updateSearchResults(for searchController: UISearchController) { 17 | searchKeyword = searchController.searchBar.text ?? "" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TrollFools/Execute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Execute.swift 3 | // TrollFools 4 | // 5 | // Created by Lessica on 2024/7/19. 6 | // 7 | 8 | import CocoaLumberjackSwift 9 | import Foundation 10 | 11 | enum Execute { 12 | @discardableResult 13 | static func rootSpawn( 14 | binary: String, 15 | arguments: [String] = [], 16 | environment: [String: String] = [:], 17 | ddlog: DDLog = .sharedInstance 18 | ) throws -> AuxiliaryExecute.TerminationReason { 19 | let receipt = AuxiliaryExecute.spawn( 20 | command: binary, 21 | args: arguments, 22 | environment: environment.merging([ 23 | "DISABLE_TWEAKS": "1", 24 | ], uniquingKeysWith: { $1 }), 25 | personaOptions: .init(uid: 0, gid: 0), 26 | ddlog: ddlog 27 | ) 28 | if !receipt.stdout.isEmpty { 29 | DDLogVerbose("Process \(receipt.pid) output: \(receipt.stdout)", ddlog: ddlog) 30 | } 31 | if !receipt.stderr.isEmpty { 32 | DDLogVerbose("Process \(receipt.pid) error: \(receipt.stderr)", ddlog: ddlog) 33 | } 34 | return receipt.terminationReason 35 | } 36 | 37 | static func rootSpawnWithOutputs( 38 | binary: String, 39 | arguments: [String] = [], 40 | environment: [String: String] = [:], 41 | ddlog: DDLog = .sharedInstance 42 | ) throws -> AuxiliaryExecute.ExecuteReceipt { 43 | let receipt = AuxiliaryExecute.spawn( 44 | command: binary, 45 | args: arguments, 46 | environment: environment.merging([ 47 | "DISABLE_TWEAKS": "1", 48 | ], uniquingKeysWith: { $1 }), 49 | personaOptions: .init(uid: 0, gid: 0), 50 | ddlog: ddlog 51 | ) 52 | if !receipt.stdout.isEmpty { 53 | DDLogVerbose("Process \(receipt.pid) output: \(receipt.stdout)", ddlog: ddlog) 54 | } 55 | if !receipt.stderr.isEmpty { 56 | DDLogVerbose("Process \(receipt.pid) error: \(receipt.stderr)", ddlog: ddlog) 57 | } 58 | return receipt 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /TrollFools/FailureView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FailureView.swift 3 | // TrollFools 4 | // 5 | // Created by Lessica on 2024/7/19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FailureView: View { 11 | 12 | let title: String 13 | let error: Error? 14 | 15 | var logFileURL: URL? { 16 | (error as? NSError)?.userInfo[NSURLErrorKey] as? URL 17 | } 18 | 19 | @State private var isLogsPresented = false 20 | 21 | var body: some View { 22 | VStack(spacing: 20) { 23 | Image(systemName: "xmark.circle.fill") 24 | .font(.system(size: 64)) 25 | .foregroundColor(.red) 26 | 27 | Text(title) 28 | .font(.title) 29 | .bold() 30 | 31 | if let error { 32 | Text(error.localizedDescription) 33 | .font(.title3) 34 | } 35 | 36 | if logFileURL != nil { 37 | Button { 38 | isLogsPresented = true 39 | } label: { 40 | Label(NSLocalizedString("View Logs", comment: ""), 41 | systemImage: "note.text") 42 | } 43 | } 44 | } 45 | .padding() 46 | .multilineTextAlignment(.center) 47 | .sheet(isPresented: $isLogsPresented) { 48 | if let logFileURL { 49 | LogsView(url: logFileURL) 50 | } 51 | } 52 | } 53 | } 54 | 55 | #Preview { 56 | FailureView( 57 | title: "Hello, World!", 58 | error: nil 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /TrollFools/FilterOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterOptions.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FilterOptions: Hashable { 11 | var searchKeyword = "" 12 | var showPatchedOnly = false 13 | 14 | var isSearching: Bool { !searchKeyword.isEmpty } 15 | 16 | mutating func reset() { 17 | searchKeyword = "" 18 | showPatchedOnly = false 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TrollFools/Hashable+Combines.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hashable+Combines.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 3/9/25. 6 | // 7 | 8 | import Foundation 9 | 10 | func combines(_ value: any Hashable ...) -> Int { 11 | var hasher = Hasher() 12 | for v in value { 13 | hasher.combine(v) 14 | } 15 | return hasher.finalize() 16 | } 17 | -------------------------------------------------------------------------------- /TrollFools/IndexableScroller.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndexableScroller.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 3/9/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct IndexableScroller: View { 11 | let indexes: [String] 12 | let feedback = UISelectionFeedbackGenerator() 13 | 14 | @Binding var currentIndex: String? 15 | @GestureState private var dragLocation: CGPoint = .zero 16 | 17 | var body: some View { 18 | HStack { 19 | VStack(spacing: 0) { 20 | ForEach(indexes, id: \.self) { index in 21 | Text(index) 22 | .font(.footnote) 23 | .fontWeight(.semibold) 24 | .foregroundColor(.accentColor) 25 | .padding(.trailing, 12) 26 | .background(dragObserver(index)) 27 | } 28 | } 29 | .gesture( 30 | DragGesture(minimumDistance: 0, coordinateSpace: .global) 31 | .updating($dragLocation) { value, state, _ in 32 | state = value.location 33 | } 34 | ) 35 | 36 | Spacer() 37 | } 38 | } 39 | 40 | func dragObserver(_ index: String) -> some View { 41 | GeometryReader { geometry in 42 | dragObserver(index: index, geometry: geometry) 43 | } 44 | } 45 | 46 | func dragObserver(index: String, geometry: GeometryProxy) -> some View { 47 | if geometry.frame(in: .global).contains(dragLocation) { 48 | DispatchQueue.main.async { 49 | let previousIndex = currentIndex 50 | currentIndex = index 51 | if currentIndex != previousIndex { 52 | feedback.selectionChanged() 53 | } 54 | } 55 | } 56 | return Rectangle().fill(.background).opacity(0.05) 57 | } 58 | } 59 | 60 | // MARK: - Preview 61 | 62 | struct IndexableScroller_Previews: PreviewProvider { 63 | static var previews: some View { 64 | IndexableScroller( 65 | indexes: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"], 66 | currentIndex: .constant(nil) 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /TrollFools/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | TrollFools 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeName 11 | Mach-O Binary 12 | LSHandlerRank 13 | Default 14 | CFBundleTypeRole 15 | Viewer 16 | LSItemContentTypes 17 | 18 | com.apple.mach-o-binary 19 | 20 | 21 | 22 | CFBundleTypeName 23 | ZIP Archive 24 | LSHandlerRank 25 | Default 26 | CFBundleTypeRole 27 | Viewer 28 | LSItemContentTypes 29 | 30 | public.zip-archive 31 | 32 | 33 | 34 | CFBundleTypeName 35 | Sileo Deb Package 36 | LSHandlerRank 37 | Default 38 | CFBundleTypeRole 39 | Viewer 40 | LSItemContentTypes 41 | 42 | org.debian.deb-archive 43 | 44 | 45 | 46 | UTImportedTypeDeclarations 47 | 48 | 49 | UTTypeConformsTo 50 | 51 | public.archive 52 | public.data 53 | 54 | UTTypeIdentifier 55 | org.debian.deb-archive 56 | UTTypeTagSpecification 57 | 58 | public.filename-extension 59 | 60 | deb 61 | 62 | public.mime-type 63 | 64 | application/x-debian-package 65 | application/x-deb 66 | application/vnd.debian.binary-package 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /TrollFools/InjectView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectView.swift 3 | // TrollFools 4 | // 5 | // Created by Lessica on 2024/7/19. 6 | // 7 | 8 | import CocoaLumberjackSwift 9 | import SwiftUI 10 | 11 | struct InjectView: View { 12 | @EnvironmentObject var appList: AppListModel 13 | 14 | let app: App 15 | let urlList: [URL] 16 | 17 | @State var injectResult: Result? 18 | @StateObject fileprivate var viewControllerHost = ViewControllerHost() 19 | 20 | @AppStorage var useWeakReference: Bool 21 | @AppStorage var preferMainExecutable: Bool 22 | @AppStorage var injectStrategy: InjectorV3.Strategy 23 | 24 | init(_ app: App, urlList: [URL]) { 25 | self.app = app 26 | self.urlList = urlList 27 | _useWeakReference = AppStorage(wrappedValue: true, "UseWeakReference-\(app.id)") 28 | _preferMainExecutable = AppStorage(wrappedValue: false, "PreferMainExecutable-\(app.id)") 29 | _injectStrategy = AppStorage(wrappedValue: .lexicographic, "InjectStrategy-\(app.id)") 30 | } 31 | 32 | var body: some View { 33 | if appList.isSelectorMode { 34 | bodyContent 35 | .toolbar { 36 | ToolbarItem(placement: .navigationBarTrailing) { 37 | Button(NSLocalizedString("Done", comment: "")) { 38 | viewControllerHost.viewController?.navigationController? 39 | .dismiss(animated: true) 40 | } 41 | } 42 | } 43 | } else { 44 | bodyContent 45 | } 46 | } 47 | 48 | var bodyContent: some View { 49 | VStack { 50 | if let injectResult { 51 | switch injectResult { 52 | case let .success(url): 53 | SuccessView( 54 | title: NSLocalizedString("Completed", comment: ""), 55 | logFileURL: url 56 | ) 57 | .onAppear { 58 | app.reload() 59 | } 60 | case let .failure(error): 61 | FailureView( 62 | title: NSLocalizedString("Failed", comment: ""), 63 | error: error 64 | ) 65 | .onAppear { 66 | app.reload() 67 | } 68 | } 69 | } else { 70 | if #available(iOS 16, *) { 71 | ProgressView() 72 | .progressViewStyle(CircularProgressViewStyle()) 73 | .padding(.all, 20) 74 | .controlSize(.large) 75 | } else { 76 | // Fallback on earlier versions 77 | ProgressView() 78 | .progressViewStyle(CircularProgressViewStyle()) 79 | .padding(.all, 20) 80 | .scaleEffect(2.0) 81 | } 82 | 83 | Text(NSLocalizedString("Injecting", comment: "")) 84 | .font(.headline) 85 | } 86 | } 87 | .padding() 88 | .animation(.easeOut, value: injectResult == nil) 89 | .navigationTitle(app.name) 90 | .navigationBarTitleDisplayMode(.inline) 91 | .onViewWillAppear { viewController in 92 | viewController.navigationController? 93 | .view.isUserInteractionEnabled = false 94 | viewControllerHost.viewController = viewController 95 | } 96 | .onAppear { 97 | DispatchQueue.global(qos: .userInteractive).async { 98 | let result = inject() 99 | 100 | DispatchQueue.main.async { 101 | injectResult = result 102 | app.reload() 103 | viewControllerHost.viewController?.navigationController? 104 | .view.isUserInteractionEnabled = true 105 | } 106 | } 107 | } 108 | } 109 | 110 | private func inject() -> Result { 111 | var logFileURL: URL? 112 | 113 | do { 114 | let injector = try InjectorV3(app.url) 115 | logFileURL = injector.latestLogFileURL 116 | 117 | if injector.appID.isEmpty { 118 | injector.appID = app.id 119 | } 120 | 121 | if injector.teamID.isEmpty { 122 | injector.teamID = app.teamID 123 | } 124 | 125 | injector.useWeakReference = useWeakReference 126 | injector.preferMainExecutable = preferMainExecutable 127 | injector.injectStrategy = injectStrategy 128 | 129 | try injector.inject(urlList) 130 | return .success(injector.latestLogFileURL) 131 | 132 | } catch { 133 | DDLogError("\(error)", ddlog: InjectorV3.main.logger) 134 | 135 | var userInfo: [String: Any] = [ 136 | NSLocalizedDescriptionKey: error.localizedDescription, 137 | ] 138 | 139 | if let logFileURL { 140 | userInfo[NSURLErrorKey] = logFileURL 141 | } 142 | 143 | let nsErr = NSError(domain: gTrollFoolsErrorDomain, code: 0, userInfo: userInfo) 144 | 145 | return .failure(nsErr) 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /TrollFools/InjectedPlugIn.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectedPlugIn.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | import Foundation 9 | 10 | struct InjectedPlugIn: Identifiable, Hashable { 11 | let id: String 12 | let url: URL 13 | let createdAt: Date 14 | 15 | init(url: URL) { 16 | self.id = url.absoluteString 17 | self.url = url 18 | let attributes = try? FileManager.default.attributesOfItem(atPath: url.path) 19 | self.createdAt = attributes?[.creationDate] as? Date ?? Date() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TrollFools/InjectorV3+Backup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectorV3+Backup.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/10. 6 | // 7 | 8 | import Foundation 9 | 10 | extension InjectorV3 { 11 | // MARK: - Constants 12 | 13 | private static let alternateSuffix = "troll-fools.bak" 14 | 15 | static func alternateURL(for target: URL) -> URL { 16 | target.appendingPathExtension(Self.alternateSuffix) 17 | } 18 | 19 | // MARK: - Shared Methods 20 | 21 | func hasAlternate(_ target: URL) -> Bool { 22 | let alternateURL = Self.alternateURL(for: target) 23 | return FileManager.default.fileExists(atPath: alternateURL.path) 24 | } 25 | 26 | func makeAlternate(_ target: URL) throws { 27 | guard !hasAlternate(target) else { 28 | return 29 | } 30 | let alternateURL = Self.alternateURL(for: target) 31 | try cmdCopy(from: target, to: alternateURL) 32 | } 33 | 34 | func removeAlternate(_ target: URL) throws { 35 | guard hasAlternate(target) else { 36 | return 37 | } 38 | let alternateURL = Self.alternateURL(for: target) 39 | try cmdRemove(alternateURL) 40 | } 41 | 42 | func restoreAlternate(_ target: URL) throws { 43 | guard hasAlternate(target) else { 44 | return 45 | } 46 | let alternateURL = Self.alternateURL(for: target) 47 | try cmdMove(from: alternateURL, to: target, overwrite: true) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /TrollFools/InjectorV3+Bundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectorV3+Bundle.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/10. 6 | // 7 | 8 | import CocoaLumberjackSwift 9 | import Foundation 10 | import OrderedCollections 11 | 12 | extension InjectorV3 { 13 | // MARK: - Constants 14 | 15 | static let ignoredDylibAndFrameworkNames: Set = [ 16 | "cydiasubstrate", 17 | "cydiasubstrate.framework", 18 | "ellekit", 19 | "ellekit.framework", 20 | "libsubstrate.dylib", 21 | "libsubstitute.dylib", 22 | "libellekit.dylib", 23 | ] 24 | 25 | static let substrateName = "CydiaSubstrate" 26 | static let substrateFwkName = "CydiaSubstrate.framework" 27 | 28 | fileprivate static let infoPlistName = "Info.plist" 29 | fileprivate static let injectedMarkerName = ".troll-fools" 30 | 31 | // MARK: - Instance Methods 32 | 33 | var hasInjectedAsset: Bool { 34 | !injectedAssetURLsInBundle(bundleURL).isEmpty 35 | } 36 | 37 | // MARK: - Shared Methods 38 | 39 | func frameworkMachOsInBundle(_ target: URL) throws -> OrderedSet { 40 | precondition(checkIsBundle(target), "Not a bundle: \(target.path)") 41 | 42 | let executableURL = try locateExecutableInBundle(target) 43 | precondition(isMachO(executableURL), "Not a Mach-O: \(executableURL.path)") 44 | 45 | let frameworksURL = target.appendingPathComponent("Frameworks") 46 | let linkedDylibs = try linkedDylibsRecursivelyOfMachO(executableURL) 47 | 48 | var enumeratedURLs = OrderedSet() 49 | if let enumerator = FileManager.default.enumerator( 50 | at: frameworksURL, 51 | includingPropertiesForKeys: [.fileSizeKey], 52 | options: [.skipsHiddenFiles] 53 | ) { 54 | for case let itemURL as URL in enumerator { 55 | if checkIsInjectedBundle(itemURL) || enumerator.level > 2 { 56 | enumerator.skipDescendants() 57 | continue 58 | } 59 | if enumerator.level == 2 { 60 | enumeratedURLs.append(itemURL) 61 | } 62 | } 63 | } 64 | 65 | let machOs = linkedDylibs.intersection(enumeratedURLs) 66 | var sortedMachOs: [URL] = 67 | switch injectStrategy { 68 | case .lexicographic: 69 | machOs.sorted { $0.lastPathComponent.localizedStandardCompare($1.lastPathComponent) == .orderedAscending } 70 | case .fast: 71 | try machOs 72 | .sorted { url1, url2 in 73 | let size1 = (try url1.resourceValues(forKeys: [.fileSizeKey]).fileSize) ?? 0 74 | let size2 = (try url2.resourceValues(forKeys: [.fileSizeKey]).fileSize) ?? 0 75 | return if size1 == size2 { 76 | url1.lastPathComponent.localizedStandardCompare(url2.lastPathComponent) == .orderedAscending 77 | } else { 78 | size1 < size2 79 | } 80 | } 81 | case .preorder: 82 | machOs.elements 83 | case .postorder: 84 | machOs.reversed() 85 | } 86 | 87 | DDLogWarn("Strategy \(injectStrategy.rawValue)", ddlog: logger) 88 | DDLogInfo("Sorted Mach-Os \(sortedMachOs.map { $0.lastPathComponent })", ddlog: logger) 89 | 90 | if preferMainExecutable { 91 | sortedMachOs.insert(executableURL, at: 0) 92 | DDLogWarn("Prefer main executable", ddlog: logger) 93 | } else { 94 | sortedMachOs.append(executableURL) 95 | } 96 | 97 | return OrderedSet(sortedMachOs) 98 | } 99 | 100 | func injectedAssetURLsInBundle(_ target: URL) -> [URL] { 101 | return (injectedBundleURLsInBundle(target) + injectedDylibAndFrameworkURLsInBundle(target)) 102 | .sorted(by: { $0.lastPathComponent.localizedStandardCompare($1.lastPathComponent) == .orderedAscending }) 103 | } 104 | 105 | func injectedBundleURLsInBundle(_ target: URL) -> [URL] { 106 | precondition(checkIsBundle(target), "Not a bundle: \(target.path)") 107 | 108 | guard let bundleContentURLs = try? FileManager.default.contentsOfDirectory(at: target, includingPropertiesForKeys: [.isDirectoryKey]) else { 109 | return [] 110 | } 111 | 112 | let bundleURLs = bundleContentURLs 113 | .filter { 114 | $0.pathExtension.lowercased() == "bundle" && 115 | !Self.ignoredDylibAndFrameworkNames.contains($0.lastPathComponent.lowercased()) 116 | } 117 | .filter { 118 | (try? $0.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false 119 | } 120 | .filter { 121 | checkIsInjectedBundle($0) 122 | } 123 | .sorted(by: { $0.lastPathComponent.localizedStandardCompare($1.lastPathComponent) == .orderedAscending }) 124 | 125 | return bundleURLs 126 | } 127 | 128 | func injectedDylibAndFrameworkURLsInBundle(_ target: URL) -> [URL] { 129 | precondition(checkIsBundle(target), "Not a bundle: \(target.path)") 130 | 131 | let frameworksURL = target.appendingPathComponent("Frameworks") 132 | guard let frameworksContentURLs = try? FileManager.default.contentsOfDirectory(at: frameworksURL, includingPropertiesForKeys: nil) else { 133 | return [] 134 | } 135 | 136 | let dylibURLs = frameworksContentURLs 137 | .filter { 138 | $0.pathExtension.lowercased() == "dylib" && 139 | !$0.lastPathComponent.hasPrefix("libswift") && 140 | !Self.ignoredDylibAndFrameworkNames.contains($0.lastPathComponent.lowercased()) 141 | } 142 | .sorted(by: { $0.lastPathComponent.localizedStandardCompare($1.lastPathComponent) == .orderedAscending }) 143 | 144 | let frameworkURLs = frameworksContentURLs 145 | .filter { 146 | $0.pathExtension.lowercased() == "framework" && 147 | !Self.ignoredDylibAndFrameworkNames.contains($0.lastPathComponent.lowercased()) 148 | } 149 | .filter { 150 | (try? $0.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false 151 | } 152 | .filter { 153 | checkIsInjectedBundle($0) 154 | } 155 | .sorted(by: { $0.lastPathComponent.localizedStandardCompare($1.lastPathComponent) == .orderedAscending }) 156 | 157 | return dylibURLs + frameworkURLs 158 | } 159 | 160 | func markBundlesAsInjected(_ bundleURLs: [URL], privileged: Bool) throws { 161 | let filteredURLs = bundleURLs.filter { checkIsBundle($0) } 162 | precondition(filteredURLs.count == bundleURLs.count, "Not all urls are bundles") 163 | 164 | if privileged { 165 | let markerURL = temporaryDirectoryURL.appendingPathComponent(Self.injectedMarkerName) 166 | try Data().write(to: markerURL, options: .atomic) 167 | try cmdChangeOwnerToInstalld(markerURL, recursively: false) 168 | 169 | try filteredURLs.forEach { 170 | try cmdCopy( 171 | from: markerURL, 172 | to: $0.appendingPathComponent(Self.injectedMarkerName), 173 | clone: true, 174 | overwrite: true 175 | ) 176 | } 177 | } else { 178 | try filteredURLs.forEach { 179 | try Data().write(to: $0.appendingPathComponent(Self.injectedMarkerName), options: .atomic) 180 | } 181 | } 182 | } 183 | 184 | func identifierOfBundle(_ target: URL) throws -> String { 185 | precondition(checkIsBundle(target), "Not a bundle: \(target.path)") 186 | 187 | if let bundleIdentifier = Bundle(url: target)?.bundleIdentifier { 188 | return bundleIdentifier 189 | } 190 | 191 | let infoPlistURL = target.appendingPathComponent(Self.infoPlistName) 192 | let infoPlistData = try Data(contentsOf: infoPlistURL) 193 | 194 | guard let infoPlist = try PropertyListSerialization.propertyList(from: infoPlistData, options: [], format: nil) as? [String: Any] 195 | else { 196 | throw Error.generic(String(format: NSLocalizedString("Failed to parse: %@", comment: ""), infoPlistURL.path)) 197 | } 198 | 199 | guard let bundleIdentifier = infoPlist["CFBundleIdentifier"] as? String else { 200 | throw Error.generic(String(format: NSLocalizedString("Failed to find entry CFBundleIdentifier in: %@", comment: ""), infoPlistURL.path)) 201 | } 202 | 203 | return bundleIdentifier 204 | } 205 | 206 | func locateFrameworksDirectoryInBundle(_ target: URL) throws -> URL { 207 | precondition(checkIsBundle(target), "Not a bundle: \(target.path)") 208 | 209 | let frameworksDirectoryURL = target.appendingPathComponent("Frameworks") 210 | if !FileManager.default.fileExists(atPath: frameworksDirectoryURL.path) { 211 | try? cmdMakeDirectory(at: frameworksDirectoryURL) 212 | } 213 | 214 | return frameworksDirectoryURL 215 | } 216 | 217 | func locateExecutableInBundle(_ target: URL) throws -> URL { 218 | precondition(checkIsBundle(target), "Not a bundle: \(target.path)") 219 | 220 | if let executableURL = Bundle(url: target)?.executableURL { 221 | return executableURL 222 | } 223 | 224 | let infoPlistURL = target.appendingPathComponent(Self.infoPlistName) 225 | let infoPlistData = try Data(contentsOf: infoPlistURL) 226 | 227 | guard let infoPlist = try PropertyListSerialization.propertyList(from: infoPlistData, options: [], format: nil) as? [String: Any] 228 | else { 229 | throw Error.generic(String(format: NSLocalizedString("Failed to parse: %@", comment: ""), infoPlistURL.path)) 230 | } 231 | 232 | guard let executableName = infoPlist["CFBundleExecutable"] as? String else { 233 | throw Error.generic(String(format: NSLocalizedString("Failed to find entry CFBundleExecutable in: %@", comment: ""), infoPlistURL.path)) 234 | } 235 | 236 | let executableURL = target.appendingPathComponent(executableName) 237 | guard FileManager.default.fileExists(atPath: executableURL.path) else { 238 | throw Error.generic(String(format: NSLocalizedString("Failed to locate main executable: %@", comment: ""), executableURL.path)) 239 | } 240 | 241 | return executableURL 242 | } 243 | 244 | func checkIsEligibleAppBundle(_ target: URL) -> Bool { 245 | guard checkIsBundle(target) else { 246 | return false 247 | } 248 | 249 | let frameworksURL = target.appendingPathComponent("Frameworks") 250 | return !((try? FileManager.default.contentsOfDirectory(at: frameworksURL, includingPropertiesForKeys: nil).isEmpty) ?? true) 251 | } 252 | 253 | func checkIsInjectedAppBundle(_ target: URL) -> Bool { 254 | guard checkIsBundle(target) else { 255 | return false 256 | } 257 | 258 | let frameworksURL = target.appendingPathComponent("Frameworks") 259 | let substrateFwkURL = frameworksURL.appendingPathComponent(Self.substrateFwkName) 260 | 261 | return FileManager.default.fileExists(atPath: substrateFwkURL.path) 262 | } 263 | 264 | func checkIsInjectedBundle(_ target: URL) -> Bool { 265 | guard checkIsBundle(target) else { 266 | return false 267 | } 268 | 269 | let markerURL = target.appendingPathComponent(Self.injectedMarkerName) 270 | return FileManager.default.fileExists(atPath: markerURL.path) 271 | } 272 | 273 | func checkIsBundle(_ target: URL) -> Bool { 274 | let values = try? target.resourceValues(forKeys: [.isDirectoryKey, .isPackageKey]) 275 | let isDirectory = values?.isDirectory ?? false 276 | let isPackage = values?.isPackage ?? false 277 | let pathExt = target.pathExtension.lowercased() 278 | return isPackage || (isDirectory && (pathExt == "app" || pathExt == "bundle" || pathExt == "framework")) 279 | } 280 | 281 | func checkIsDirectory(_ target: URL) -> Bool { 282 | let values = try? target.resourceValues(forKeys: [.isDirectoryKey]) 283 | return values?.isDirectory ?? false 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /TrollFools/InjectorV3+Eject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectorV3+Eject.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/10. 6 | // 7 | 8 | import CocoaLumberjackSwift 9 | import Foundation 10 | 11 | extension InjectorV3 { 12 | // MARK: - Instance Methods 13 | 14 | func ejectAll() throws { 15 | try eject(injectedAssetURLsInBundle(bundleURL)) 16 | } 17 | 18 | func eject(_ assetURLs: [URL]) throws { 19 | precondition(!assetURLs.isEmpty, "No asset to eject.") 20 | terminateApp() 21 | 22 | try ejectBundles(assetURLs 23 | .filter { $0.pathExtension.lowercased() == "bundle" }) 24 | 25 | try ejectDylibsAndFrameworks(assetURLs 26 | .filter { $0.pathExtension.lowercased() == "dylib" || $0.pathExtension.lowercased() == "framework" }) 27 | } 28 | 29 | // MARK: - Private Methods 30 | 31 | fileprivate func ejectBundles(_ assetURLs: [URL]) throws { 32 | guard !assetURLs.isEmpty else { 33 | return 34 | } 35 | 36 | for assetURL in assetURLs { 37 | guard checkIsInjectedBundle(assetURL) else { 38 | continue 39 | } 40 | 41 | try? cmdRemove(assetURL, recursively: true) 42 | } 43 | } 44 | 45 | fileprivate func ejectDylibsAndFrameworks(_ assetURLs: [URL]) throws { 46 | guard !assetURLs.isEmpty else { 47 | return 48 | } 49 | 50 | let targetURLs = try collectModifiedMachOs() 51 | guard !targetURLs.isEmpty else { 52 | DDLogError("Unable to find any modified Mach-Os", ddlog: logger) 53 | throw Error.generic(NSLocalizedString("No eligible framework found.", comment: "")) 54 | } 55 | 56 | DDLogInfo("Modified Mach-Os \(targetURLs.map { $0.path })", ddlog: logger) 57 | 58 | for assetURL in assetURLs { 59 | try targetURLs.forEach { 60 | try removeLoadCommandOfAsset(assetURL, from: $0) 61 | } 62 | try? cmdRemove(assetURL, recursively: checkIsDirectory(assetURL)) 63 | } 64 | 65 | try targetURLs.forEach { 66 | try cmdCoreTrustBypass($0, teamID: teamID) 67 | try cmdChangeOwnerToInstalld($0) 68 | } 69 | 70 | if !hasInjectedAsset { 71 | try targetURLs.forEach { try restoreAlternate($0) } 72 | 73 | let substrateFwkURL = bundleURL.appendingPathComponent("Frameworks/\(Self.substrateFwkName)", isDirectory: true) 74 | try? cmdRemove(substrateFwkURL, recursively: true) 75 | } 76 | } 77 | 78 | fileprivate func collectModifiedMachOs() throws -> [URL] { 79 | try frameworkMachOsInBundle(bundleURL) 80 | .filter { hasAlternate($0) }.elements 81 | } 82 | 83 | // MARK: - Load Commands 84 | 85 | fileprivate func removeLoadCommandOfAsset(_ assetURL: URL, from target: URL) throws { 86 | let name = try loadCommandNameOfAsset(assetURL) 87 | try cmdRemoveLoadCommandDylib(target, name: name) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /TrollFools/InjectorV3+Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectorV3+Error.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/10. 6 | // 7 | 8 | import Foundation 9 | 10 | extension InjectorV3 { 11 | enum Error: LocalizedError { 12 | case generic(String) 13 | 14 | var errorDescription: String? { 15 | switch self { 16 | case let .generic(reason): reason 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TrollFools/InjectorV3+Inject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectorV3+Inject.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/10. 6 | // 7 | 8 | import CocoaLumberjackSwift 9 | import Foundation 10 | 11 | extension InjectorV3 { 12 | enum Strategy: String, CaseIterable { 13 | case lexicographic 14 | case fast 15 | case preorder 16 | case postorder 17 | 18 | var localizedDescription: String { 19 | switch self { 20 | case .lexicographic: NSLocalizedString("Lexicographic", comment: "") 21 | case .fast: NSLocalizedString("Fast", comment: "") 22 | case .preorder: NSLocalizedString("Pre-order", comment: "") 23 | case .postorder: NSLocalizedString("Post-order", comment: "") 24 | } 25 | } 26 | } 27 | 28 | // MARK: - Instance Methods 29 | 30 | func inject(_ assetURLs: [URL]) throws { 31 | let preparedAssetURLs = try preprocessAssets(assetURLs) 32 | 33 | precondition(!preparedAssetURLs.isEmpty, "No asset to inject.") 34 | terminateApp() 35 | 36 | try injectBundles(preparedAssetURLs 37 | .filter { $0.pathExtension.lowercased() == "bundle" }) 38 | 39 | try injectDylibsAndFrameworks(preparedAssetURLs 40 | .filter { $0.pathExtension.lowercased() == "dylib" || $0.pathExtension.lowercased() == "framework" }) 41 | } 42 | 43 | // MARK: - Private Methods 44 | 45 | fileprivate func injectBundles(_ assetURLs: [URL]) throws { 46 | guard !assetURLs.isEmpty else { 47 | return 48 | } 49 | 50 | for assetURL in assetURLs { 51 | let targetURL = bundleURL.appendingPathComponent(assetURL.lastPathComponent) 52 | 53 | try cmdCopy(from: assetURL, to: targetURL, clone: true, overwrite: true) 54 | try cmdChangeOwnerToInstalld(targetURL, recursively: true) 55 | } 56 | } 57 | 58 | fileprivate func injectDylibsAndFrameworks(_ assetURLs: [URL]) throws { 59 | guard !assetURLs.isEmpty else { 60 | return 61 | } 62 | 63 | try assetURLs.forEach { 64 | try standardizeLoadCommandDylibToSubstrate($0) 65 | try applyCoreTrustBypass($0) 66 | } 67 | 68 | let substrateFwkURL = try prepareSubstrate() 69 | guard let targetMachO = try locateAvailableMachO() else { 70 | DDLogError("All Mach-Os are protected", ddlog: logger) 71 | 72 | throw Error.generic(NSLocalizedString("No eligible framework found.\n\nIt is usually not a bug with TrollFools itself, but rather with the target app. You may re-install that from App Store. You can’t use TrollFools with apps installed via “Asspp” or tweaks like “NoAppThinning”.", comment: "")) 73 | } 74 | 75 | DDLogInfo("Best matched Mach-O is \(targetMachO.path)", ddlog: logger) 76 | 77 | let resourceURLs: [URL] = [substrateFwkURL] + assetURLs 78 | try makeAlternate(targetMachO) 79 | do { 80 | try copyfiles(resourceURLs) 81 | for assetURL in assetURLs { 82 | try insertLoadCommandOfAsset(assetURL, to: targetMachO) 83 | } 84 | try applyCoreTrustBypass(targetMachO) 85 | } catch { 86 | try? restoreAlternate(targetMachO) 87 | try? batchRemove(resourceURLs) 88 | throw error 89 | } 90 | } 91 | 92 | // MARK: - Core Trust 93 | 94 | fileprivate func applyCoreTrustBypass(_ target: URL) throws { 95 | let isFramework = checkIsBundle(target) 96 | 97 | let machO: URL 98 | if isFramework { 99 | machO = try locateExecutableInBundle(target) 100 | } else { 101 | machO = target 102 | } 103 | 104 | try cmdCoreTrustBypass(machO, teamID: teamID) 105 | try cmdChangeOwnerToInstalld(target, recursively: isFramework) 106 | } 107 | 108 | // MARK: - Cydia Substrate 109 | 110 | fileprivate static let substrateZipURL = findResource(substrateFwkName, fileExtension: "zip") 111 | 112 | fileprivate func prepareSubstrate() throws -> URL { 113 | try FileManager.default.unzipItem(at: Self.substrateZipURL, to: temporaryDirectoryURL) 114 | 115 | let fwkURL = temporaryDirectoryURL.appendingPathComponent(Self.substrateFwkName) 116 | try markBundlesAsInjected([fwkURL], privileged: false) 117 | 118 | let machO = fwkURL.appendingPathComponent(Self.substrateName) 119 | 120 | try cmdCoreTrustBypass(machO, teamID: teamID) 121 | try cmdChangeOwnerToInstalld(fwkURL, recursively: true) 122 | 123 | return fwkURL 124 | } 125 | 126 | fileprivate func standardizeLoadCommandDylibToSubstrate(_ assetURL: URL) throws { 127 | let machO: URL 128 | if checkIsBundle(assetURL) { 129 | machO = try locateExecutableInBundle(assetURL) 130 | } else { 131 | machO = assetURL 132 | } 133 | 134 | let dylibs = try loadedDylibsOfMachO(machO) 135 | for dylib in dylibs { 136 | if Self.ignoredDylibAndFrameworkNames.firstIndex(where: { dylib.lowercased().hasSuffix("/\($0)") }) != nil { 137 | try cmdChangeLoadCommandDylib(machO, from: dylib, to: "@executable_path/Frameworks/\(Self.substrateFwkName)/\(Self.substrateName)") 138 | } 139 | } 140 | } 141 | 142 | // MARK: - Load Commands 143 | 144 | func loadCommandNameOfAsset(_ assetURL: URL) throws -> String { 145 | var name = "@rpath/" 146 | 147 | if checkIsBundle(assetURL) { 148 | precondition(assetURL.pathExtension == "framework", "Invalid framework: \(assetURL.path)") 149 | let machO = try locateExecutableInBundle(assetURL) 150 | name += machO.pathComponents.suffix(2).joined(separator: "/") // @rpath/XXX.framework/XXX 151 | precondition(name.contains(".framework/"), "Invalid framework name: \(name)") 152 | } else { 153 | precondition(assetURL.pathExtension == "dylib", "Invalid dylib: \(assetURL.path)") 154 | name += assetURL.lastPathComponent 155 | precondition(name.hasSuffix(".dylib"), "Invalid dylib name: \(name)") // @rpath/XXX.dylib 156 | } 157 | 158 | return name 159 | } 160 | 161 | fileprivate func insertLoadCommandOfAsset(_ assetURL: URL, to target: URL) throws { 162 | let name = try loadCommandNameOfAsset(assetURL) 163 | 164 | try cmdInsertLoadCommandRuntimePath(target, name: "@executable_path/Frameworks") 165 | try cmdInsertLoadCommandDylib(target, name: name, weak: useWeakReference) 166 | try standardizeLoadCommandDylib(target, to: name) 167 | } 168 | 169 | fileprivate func standardizeLoadCommandDylib(_ target: URL, to name: String) throws { 170 | precondition(name.hasPrefix("@rpath/"), "Invalid dylib name: \(name)") 171 | 172 | let itemName = String(name[name.index(name.startIndex, offsetBy: 7)...]) 173 | let dylibs = try loadedDylibsOfMachO(target) 174 | 175 | for dylib in dylibs { 176 | if dylib.hasSuffix("/" + itemName) { 177 | try cmdChangeLoadCommandDylib(target, from: dylib, to: name) 178 | } 179 | } 180 | } 181 | 182 | // MARK: - Path Clone 183 | 184 | fileprivate func copyfiles(_ assetURLs: [URL]) throws { 185 | let targetURLs = assetURLs.map { 186 | frameworksDirectoryURL.appendingPathComponent($0.lastPathComponent) 187 | } 188 | 189 | for (assetURL, targetURL) in zip(assetURLs, targetURLs) { 190 | try cmdCopy(from: assetURL, to: targetURL, clone: true, overwrite: true) 191 | try cmdChangeOwnerToInstalld(targetURL, recursively: checkIsDirectory(assetURL)) 192 | } 193 | } 194 | 195 | fileprivate func batchRemove(_ assetURLs: [URL]) throws { 196 | try assetURLs.forEach { 197 | try cmdRemove($0, recursively: checkIsDirectory($0)) 198 | } 199 | } 200 | 201 | // MARK: - Path Finder 202 | 203 | fileprivate func locateAvailableMachO() throws -> URL? { 204 | try frameworkMachOsInBundle(bundleURL) 205 | .first { try !isProtectedMachO($0) } 206 | } 207 | 208 | fileprivate static func findResource(_ name: String, fileExtension: String) -> URL { 209 | if let url = Bundle.main.url(forResource: name, withExtension: fileExtension) { 210 | return url 211 | } 212 | if let firstArg = ProcessInfo.processInfo.arguments.first { 213 | let execURL = URL(fileURLWithPath: firstArg) 214 | .deletingLastPathComponent() 215 | .appendingPathComponent(name) 216 | .appendingPathExtension(fileExtension) 217 | if FileManager.default.isReadableFile(atPath: execURL.path) { 218 | return execURL 219 | } 220 | } 221 | if let tfProxy = LSApplicationProxy(forIdentifier: gTrollFoolsIdentifier), 222 | let tfBundleURL = tfProxy.bundleURL() 223 | { 224 | let execURL = tfBundleURL 225 | .appendingPathComponent(name) 226 | .appendingPathExtension(fileExtension) 227 | if FileManager.default.isReadableFile(atPath: execURL.path) { 228 | return execURL 229 | } 230 | } 231 | fatalError("Unable to locate resource \(name)") 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /TrollFools/InjectorV3+MachO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectorV3+MachO.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/10. 6 | // 7 | 8 | import Foundation 9 | import MachOKit 10 | import OrderedCollections 11 | 12 | extension InjectorV3 { 13 | func isMachO(_ target: URL) -> Bool { 14 | if (try? MachOKit.loadFromFile(url: target)) != nil { 15 | true 16 | } else { 17 | false 18 | } 19 | } 20 | 21 | func isProtectedMachO(_ target: URL) throws -> Bool { 22 | let machOFile = try MachOKit.loadFromFile(url: target) 23 | switch machOFile { 24 | case let .machO(machOFile): 25 | for command in machOFile.loadCommands { 26 | switch command { 27 | case let .encryptionInfo(encryptionInfoCommand): 28 | if encryptionInfoCommand.cryptid != 0 { 29 | return true 30 | } 31 | case let .encryptionInfo64(encryptionInfoCommand): 32 | if encryptionInfoCommand.cryptid != 0 { 33 | return true 34 | } 35 | default: 36 | continue 37 | } 38 | } 39 | case let .fat(fatFile): 40 | let machOFiles = try fatFile.machOFiles() 41 | for machOFile in machOFiles { 42 | for command in machOFile.loadCommands { 43 | switch command { 44 | case let .encryptionInfo(encryptionInfoCommand): 45 | if encryptionInfoCommand.cryptid != 0 { 46 | return true 47 | } 48 | case let .encryptionInfo64(encryptionInfoCommand): 49 | if encryptionInfoCommand.cryptid != 0 { 50 | return true 51 | } 52 | default: 53 | continue 54 | } 55 | } 56 | } 57 | } 58 | return false 59 | } 60 | 61 | func linkedDylibsRecursivelyOfMachO(_ target: URL, collected: OrderedSet = []) throws -> OrderedSet { 62 | if collected.contains(target) { 63 | return collected 64 | } 65 | 66 | var newCollected = collected 67 | newCollected.append(target) 68 | 69 | let loadedDylibs = try loadedDylibsOfMachO(target).compactMap({ resolveLoadCommand($0) }) 70 | for dylib in loadedDylibs { 71 | newCollected = try linkedDylibsRecursivelyOfMachO(dylib, collected: newCollected) 72 | } 73 | 74 | return newCollected 75 | } 76 | 77 | func loadedDylibsOfMachO(_ target: URL) throws -> OrderedSet { 78 | var dylibs = OrderedSet() 79 | let machOFile = try MachOKit.loadFromFile(url: target) 80 | switch machOFile { 81 | case let .machO(machOFile): 82 | for command in machOFile.loadCommands { 83 | switch command { 84 | case let .loadDylib(loadDylibCommand): 85 | dylibs.append(loadDylibCommand.dylib(in: machOFile).name) 86 | case let .loadWeakDylib(loadWeakDylibCommand): 87 | dylibs.append(loadWeakDylibCommand.dylib(in: machOFile).name) 88 | default: 89 | continue 90 | } 91 | } 92 | case let .fat(fatFile): 93 | let machOFiles = try fatFile.machOFiles() 94 | for machOFile in machOFiles { 95 | for command in machOFile.loadCommands { 96 | switch command { 97 | case let .loadDylib(loadDylibCommand): 98 | dylibs.append(loadDylibCommand.dylib(in: machOFile).name) 99 | case let .loadWeakDylib(loadWeakDylibCommand): 100 | dylibs.append(loadWeakDylibCommand.dylib(in: machOFile).name) 101 | default: 102 | continue 103 | } 104 | } 105 | } 106 | } 107 | return dylibs 108 | } 109 | 110 | func runtimePathsOfMachO(_ target: URL) throws -> OrderedSet { 111 | var paths = OrderedSet() 112 | let machOFile = try MachOKit.loadFromFile(url: target) 113 | switch machOFile { 114 | case let .machO(machOFile): 115 | for command in machOFile.loadCommands { 116 | switch command { 117 | case let .rpath(rpathCommand): 118 | paths.append(rpathCommand.path(in: machOFile)) 119 | default: 120 | continue 121 | } 122 | } 123 | case let .fat(fatFile): 124 | let machOFiles = try fatFile.machOFiles() 125 | for machOFile in machOFiles { 126 | for command in machOFile.loadCommands { 127 | switch command { 128 | case let .rpath(rpathCommand): 129 | paths.append(rpathCommand.path(in: machOFile)) 130 | default: 131 | continue 132 | } 133 | } 134 | } 135 | } 136 | return paths 137 | } 138 | 139 | func teamIdentifierOfMachO(_ target: URL) throws -> String? { 140 | let machOFile = try MachOKit.loadFromFile(url: target) 141 | switch machOFile { 142 | case let .machO(machOFile): 143 | if let codeSign = machOFile.codeSign, let teamID = codeSign.codeDirectory?.teamId(in: codeSign) { 144 | return teamID 145 | } 146 | case let .fat(fatFile): 147 | let machOFiles = try fatFile.machOFiles() 148 | for machOFile in machOFiles { 149 | if let codeSign = machOFile.codeSign, let teamID = codeSign.codeDirectory?.teamId(in: codeSign) { 150 | return teamID 151 | } 152 | } 153 | } 154 | return nil 155 | } 156 | 157 | fileprivate func resolveLoadCommand(_ name: String) -> URL? { 158 | guard (name.hasPrefix("@rpath/") && !name.hasPrefix("@rpath/libswift")) || name.hasPrefix("@executable_path/") else { 159 | return nil 160 | } 161 | 162 | var resolvedName = name 163 | resolvedName = resolvedName 164 | .replacingOccurrences(of: "@executable_path/", with: executableURL.deletingLastPathComponent().path + "/") 165 | resolvedName = resolvedName 166 | .replacingOccurrences(of: "@rpath/", with: frameworksDirectoryURL.path + "/") 167 | 168 | let resolvedURL = URL(fileURLWithPath: resolvedName) 169 | guard FileManager.default.fileExists(atPath: resolvedURL.path) else { 170 | return nil 171 | } 172 | 173 | return resolvedURL 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /TrollFools/InjectorV3+Metadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectorV3+Metadata.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/10. 6 | // 7 | 8 | import Foundation 9 | 10 | extension InjectorV3 { 11 | fileprivate static let metadataPlistName = "iTunesMetadata.plist" 12 | fileprivate static let metadataPlistBackupName = "\(metadataPlistName).bak" 13 | 14 | // MARK: - Instance Methods 15 | 16 | var isMetadataDetached: Bool { isMetadataDetachedInBundle(bundleURL) } 17 | 18 | func setMetadataDetached(_ detached: Bool) throws { 19 | let containerURL = bundleURL.deletingLastPathComponent() 20 | 21 | let metaURL = containerURL.appendingPathComponent(Self.metadataPlistName) 22 | let metaBackupURL = containerURL.appendingPathComponent(Self.metadataPlistBackupName) 23 | 24 | if detached && !isMetadataDetached { 25 | try? cmdMove(from: metaURL, to: metaBackupURL, overwrite: false) 26 | } 27 | 28 | if !detached && isMetadataDetached { 29 | try? cmdMove(from: metaBackupURL, to: metaURL, overwrite: false) 30 | } 31 | } 32 | 33 | // MARK: - Shared Methods 34 | 35 | func isMetadataDetachedInBundle(_ target: URL) -> Bool { 36 | guard checkIsBundle(target) else { 37 | return false 38 | } 39 | 40 | let containerURL = target.deletingLastPathComponent() 41 | let metaBackupURL = containerURL.appendingPathComponent(Self.metadataPlistBackupName) 42 | 43 | return FileManager.default.fileExists(atPath: metaBackupURL.path) 44 | } 45 | 46 | func isAllowedToAttachOrDetachMetadataInBundle(_ target: URL) -> Bool { 47 | guard checkIsBundle(target) else { 48 | return false 49 | } 50 | 51 | let containerURL = target.deletingLastPathComponent() 52 | 53 | let metaURL = containerURL.appendingPathComponent(Self.metadataPlistName) 54 | let metaBackupURL = containerURL.appendingPathComponent(Self.metadataPlistBackupName) 55 | 56 | return FileManager.default.fileExists(atPath: metaURL.path) || FileManager.default.fileExists(atPath: metaBackupURL.path) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /TrollFools/InjectorV3+Preprocess.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectorV3+Preprocess.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/10. 6 | // 7 | 8 | import ArArchiveKit 9 | import CocoaLumberjackSwift 10 | import Foundation 11 | import SWCompression 12 | import ZIPFoundation 13 | 14 | extension InjectorV3 { 15 | // MARK: - Constants 16 | 17 | fileprivate static let allowedPathExtensions: Set = ["bundle", "dylib", "framework"] 18 | 19 | // MARK: - Shared Methods 20 | 21 | func preprocessAssets(_ assetURLs: [URL]) throws -> [URL] { 22 | DDLogVerbose("Preprocess \(assetURLs.map { $0.path })", ddlog: logger) 23 | 24 | var preparedAssetURLs = [URL]() 25 | var urlsToMarkAsInjected = [URL]() 26 | 27 | for assetURL in assetURLs { 28 | let lowerExt = assetURL.pathExtension.lowercased() 29 | if lowerExt == "zip" || lowerExt == "deb" { 30 | let extractedURL = temporaryDirectoryURL 31 | .appendingPathComponent("\(UUID().uuidString)_\(assetURL.lastPathComponent)") 32 | .appendingPathExtension("extracted") 33 | 34 | try FileManager.default.createDirectory(at: extractedURL, withIntermediateDirectories: true) 35 | if lowerExt == "zip" { 36 | try FileManager.default.unzipItem(at: assetURL, to: extractedURL) 37 | } else { 38 | try extractDebianPackage(at: assetURL, to: extractedURL) 39 | } 40 | 41 | var extractedItems = [URL]() 42 | if let enumerator = FileManager.default.enumerator( 43 | at: extractedURL, 44 | includingPropertiesForKeys: nil, 45 | options: [.skipsHiddenFiles, .skipsPackageDescendants] 46 | ) { 47 | while let item = enumerator.nextObject() as? URL { 48 | let itemExt = item.pathExtension.lowercased() 49 | guard Self.allowedPathExtensions.contains(itemExt) else { 50 | continue 51 | } 52 | extractedItems.append(item) 53 | if itemExt == "bundle" || itemExt == "framework" { 54 | enumerator.skipDescendants() 55 | continue 56 | } 57 | } 58 | } 59 | 60 | for extractedItem in extractedItems { 61 | if checkIsBundle(extractedItem) { 62 | urlsToMarkAsInjected.append(extractedItem) 63 | } 64 | } 65 | 66 | preparedAssetURLs.append(contentsOf: extractedItems) 67 | continue 68 | } else if Self.allowedPathExtensions.contains(lowerExt) { 69 | let copiedURL = temporaryDirectoryURL 70 | .appendingPathComponent(assetURL.lastPathComponent) 71 | try FileManager.default.copyItem(at: assetURL, to: copiedURL) 72 | 73 | if checkIsBundle(copiedURL) { 74 | urlsToMarkAsInjected.append(copiedURL) 75 | } 76 | 77 | preparedAssetURLs.append(copiedURL) 78 | continue 79 | } 80 | } 81 | 82 | try markBundlesAsInjected(urlsToMarkAsInjected, privileged: false) 83 | 84 | preparedAssetURLs.removeAll(where: { Self.ignoredDylibAndFrameworkNames.contains($0.lastPathComponent.lowercased()) }) 85 | guard !preparedAssetURLs.isEmpty else { 86 | throw Error.generic(NSLocalizedString("No valid plug-ins found.", comment: "")) 87 | } 88 | 89 | return preparedAssetURLs 90 | } 91 | } 92 | 93 | fileprivate extension InjectorV3 { 94 | func extractDebianPackage(at debURL: URL, to targetURL: URL) throws { 95 | let fileHandle = try FileHandle(forReadingFrom: debURL) 96 | defer { 97 | try? fileHandle.close() 98 | } 99 | 100 | let archiveData = fileHandle.readDataToEndOfFile() 101 | let archiveReader = try ArArchiveReader(archive: [UInt8](archiveData)) 102 | 103 | var contentData: Data? 104 | for (header, data) in archiveReader { 105 | if header.name == "data.tar.gz" { 106 | DDLogInfo("Extracting \(header.name)", ddlog: logger) 107 | contentData = try GzipArchive.unarchive(archive: Data(data)) 108 | break 109 | } else if header.name == "data.tar.bz2" { 110 | DDLogInfo("Extracting \(header.name)", ddlog: logger) 111 | contentData = try BZip2.decompress(data: Data(data)) 112 | break 113 | } else if header.name == "data.tar.lzma" { 114 | DDLogInfo("Extracting \(header.name)", ddlog: logger) 115 | contentData = try LZMA.decompress(data: Data(data)) 116 | break 117 | } else if header.name == "data.tar.xz" { 118 | DDLogInfo("Extracting \(header.name)", ddlog: logger) 119 | contentData = try XZArchive.unarchive(archive: Data(data)) 120 | break 121 | } else if header.name == "data.tar.lz4" { 122 | DDLogInfo("Extracting \(header.name)", ddlog: logger) 123 | contentData = try LZ4.decompress(data: Data(data)) 124 | break 125 | } else { 126 | continue 127 | } 128 | } 129 | 130 | guard let contentData else { 131 | throw Error.generic(NSLocalizedString("Unable to locate the data archive in the Debian package.", comment: "")) 132 | } 133 | 134 | let tarURL = targetURL.appendingPathComponent("data.tar") 135 | try contentData.write(to: tarURL) 136 | 137 | let tarHandle = try FileHandle(forReadingFrom: tarURL) 138 | defer { 139 | try? tarHandle.close() 140 | } 141 | 142 | var hasAnyDylib = false 143 | var tarReader = TarReader(fileHandle: tarHandle) 144 | var processedBundles = Set() 145 | var bundleContents: [String: [(info: TarEntryInfo, data: Data?)]] = [:] 146 | 147 | while let entry = try tarReader.read() { 148 | if entry.info.type == .regular && entry.info.name.hasSuffix(".dylib") { 149 | guard let entryData = entry.data else { 150 | continue 151 | } 152 | 153 | let dylibName = URL(fileURLWithPath: entry.info.name, relativeTo: targetURL).lastPathComponent 154 | guard !dylibName.hasPrefix(".") else { 155 | continue 156 | } 157 | 158 | DDLogWarn("Found dylib \(entry.info.name) name \(dylibName)", ddlog: logger) 159 | 160 | let entryURL = targetURL.appendingPathComponent(dylibName) 161 | try entryData.write(to: entryURL) 162 | hasAnyDylib = true 163 | } else if entry.info.type == .directory && entry.info.name.hasSuffix(".bundle") { 164 | let bundleName = URL(fileURLWithPath: entry.info.name).lastPathComponent 165 | guard !processedBundles.contains(bundleName) else { 166 | continue 167 | } 168 | 169 | processedBundles.insert(bundleName) 170 | bundleContents[entry.info.name] = [] 171 | 172 | DDLogWarn("Found bundle \(entry.info.name) name \(bundleName)", ddlog: logger) 173 | } else { 174 | for (bundlePath, _) in bundleContents { 175 | if entry.info.name.starts(with: bundlePath + "/") { 176 | bundleContents[bundlePath]?.append((entry.info, entry.data)) 177 | } 178 | } 179 | } 180 | } 181 | 182 | if !hasAnyDylib { 183 | throw Error.generic(NSLocalizedString("No dylib found in the Debian package.", comment: "")) 184 | } 185 | 186 | let fileManager = FileManager.default 187 | for (bundlePath, contents) in bundleContents { 188 | let bundleName = URL(fileURLWithPath: bundlePath).lastPathComponent 189 | DDLogInfo("Preparing to copy bundle \(bundlePath)", ddlog: logger) 190 | 191 | let destinationBundleURL = targetURL.appendingPathComponent(bundleName) 192 | if fileManager.fileExists(atPath: destinationBundleURL.path) { 193 | try fileManager.removeItem(at: destinationBundleURL) 194 | } 195 | 196 | try fileManager.createDirectory(at: destinationBundleURL, withIntermediateDirectories: true) 197 | 198 | for entry in contents { 199 | let relativePath = String(entry.info.name.dropFirst(bundlePath.count + 1)) 200 | let destinationPath = destinationBundleURL.appendingPathComponent(relativePath) 201 | 202 | switch entry.info.type { 203 | case .directory: 204 | try fileManager.createDirectory(at: destinationPath, withIntermediateDirectories: true) 205 | case .regular: 206 | try fileManager.createDirectory(at: destinationPath.deletingLastPathComponent(), withIntermediateDirectories: true) 207 | guard let fileData = entry.data else { 208 | DDLogWarn("Unable to read data for \(entry.info.name)", ddlog: logger) 209 | continue 210 | } 211 | try fileData.write(to: destinationPath) 212 | default: 213 | continue 214 | } 215 | } 216 | 217 | DDLogInfo("Successfully copied bundle \(bundleName)", ddlog: logger) 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /TrollFools/InjectorV3.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectorV3.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/9. 6 | // 7 | 8 | import CocoaLumberjackSwift 9 | import Foundation 10 | 11 | final class InjectorV3 { 12 | enum LoggerType { 13 | case os 14 | case file 15 | } 16 | 17 | static let temporaryRoot: URL = FileManager.default 18 | .urls(for: .cachesDirectory, in: .userDomainMask).first! 19 | .appendingPathComponent(gTrollFoolsIdentifier, isDirectory: true) 20 | .appendingPathComponent("InjectorV3", isDirectory: true) 21 | 22 | static let main = try! InjectorV3(Bundle.main.bundleURL) 23 | 24 | let bundleURL: URL 25 | let temporaryDirectoryURL: URL 26 | let isPrivileged: Bool = geteuid() == 0 27 | 28 | var appID: String! 29 | var teamID: String! 30 | 31 | private(set) var executableURL: URL! 32 | private(set) var frameworksDirectoryURL: URL! 33 | private(set) var logsDirectoryURL: URL! 34 | 35 | var useWeakReference: Bool = false 36 | var preferMainExecutable: Bool = false 37 | var injectStrategy: Strategy = .lexicographic 38 | 39 | let logger: DDLog 40 | let loggerType: LoggerType 41 | 42 | private init() { fatalError("Not implemented") } 43 | 44 | init(_ bundleURL: URL, loggerType: LoggerType = .file) throws { 45 | self.bundleURL = bundleURL 46 | temporaryDirectoryURL = Self.temporaryRoot 47 | .appendingPathComponent(UUID().uuidString, isDirectory: true) 48 | try? FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true) 49 | 50 | logger = DDLog() 51 | self.loggerType = loggerType 52 | 53 | let executableURL = try locateExecutableInBundle(bundleURL) 54 | let frameworksDirectoryURL = try locateFrameworksDirectoryInBundle(bundleURL) 55 | let appID = try identifierOfBundle(bundleURL) 56 | let teamID = try teamIdentifierOfMachO(executableURL) ?? "" 57 | 58 | self.appID = appID 59 | self.teamID = teamID 60 | self.executableURL = executableURL 61 | self.frameworksDirectoryURL = frameworksDirectoryURL 62 | logsDirectoryURL = temporaryDirectoryURL.appendingPathComponent("Logs/\(appID)") 63 | 64 | setupLoggers() 65 | } 66 | 67 | // MARK: - Instance Methods 68 | 69 | func terminateApp() { 70 | TFUtilKillAll(executableURL.lastPathComponent, true) 71 | } 72 | 73 | // MARK: - Logger 74 | 75 | private func setupLoggers() { 76 | if loggerType == .file { 77 | try? FileManager.default.createDirectory(at: logsDirectoryURL, withIntermediateDirectories: true) 78 | 79 | let fileLogger = DDFileLogger(logFileManager: DDLogFileManagerDefault(logsDirectory: logsDirectoryURL.path)) 80 | 81 | fileLogger.rollingFrequency = 60 * 60 * 24 82 | fileLogger.logFileManager.maximumNumberOfLogFiles = 7 83 | fileLogger.doNotReuseLogFiles = true 84 | 85 | logger.add(fileLogger) 86 | } 87 | 88 | logger.add(DDOSLogger.sharedInstance) 89 | 90 | DDLogWarn("Logger setup \(appID!)", asynchronous: false, ddlog: logger) 91 | } 92 | 93 | var latestLogFileURL: URL? { 94 | guard let enumerator = FileManager.default.enumerator( 95 | at: logsDirectoryURL, 96 | includingPropertiesForKeys: [.isRegularFileKey, .creationDateKey] 97 | ) else { 98 | return nil 99 | } 100 | 101 | var latestLogFileURL: URL? 102 | var latestCreationDate: Date? 103 | while let fileURL = enumerator.nextObject() as? URL { 104 | guard let resourceValues = try? fileURL.resourceValues(forKeys: [.isRegularFileKey, .creationDateKey]), 105 | let isRegularFile = resourceValues.isRegularFile, isRegularFile, 106 | let creationDate = resourceValues.creationDate 107 | else { 108 | continue 109 | } 110 | 111 | if latestCreationDate == nil || creationDate > latestCreationDate! { 112 | latestLogFileURL = fileURL 113 | latestCreationDate = creationDate 114 | } 115 | } 116 | 117 | return latestLogFileURL 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /TrollFools/LSApplicationProxy.h: -------------------------------------------------------------------------------- 1 | #ifndef LSApplicationProxy_h 2 | #define LSApplicationProxy_h 3 | 4 | #import 5 | 6 | @class LSPlugInKitProxy; 7 | 8 | @interface LSApplicationProxy : NSObject 9 | 10 | + (LSApplicationProxy *)applicationProxyForIdentifier:(NSString *)bundleIdentifier; 11 | 12 | - (BOOL)installed; 13 | - (BOOL)restricted; 14 | 15 | - (NSString *)applicationIdentifier; 16 | - (NSString *)localizedName; 17 | - (NSString *)shortVersionString; 18 | - (NSString *)applicationType; 19 | - (NSString *)teamID; 20 | 21 | - (NSURL *)bundleURL; 22 | - (NSURL *)dataContainerURL; 23 | - (NSURL *)bundleContainerURL; 24 | 25 | - (NSDictionary *)groupContainerURLs; 26 | - (NSDictionary *)entitlements; 27 | 28 | - (NSArray *)plugInKitPlugins; 29 | 30 | - (BOOL)isRemoveableSystemApp; 31 | - (BOOL)isRemovedSystemApp; 32 | 33 | @end 34 | 35 | #endif /* LSApplicationProxy_h */ 36 | -------------------------------------------------------------------------------- /TrollFools/LSApplicationWorkspace.h: -------------------------------------------------------------------------------- 1 | #ifndef LSApplicationWorkspace_h 2 | #define LSApplicationWorkspace_h 3 | 4 | #import 5 | 6 | @class LSApplicationProxy; 7 | 8 | @interface LSApplicationWorkspace : NSObject 9 | 10 | + (LSApplicationWorkspace *)defaultWorkspace; 11 | - (NSArray *)allApplications; 12 | - (NSArray *)allInstalledApplications; 13 | 14 | - (void)enumerateApplicationsOfType:(NSInteger)type block:(void (^)(id))block; 15 | 16 | - (BOOL)openApplicationWithBundleID:(NSString *)bundleIdentifier; 17 | - (BOOL)installApplication:(NSURL *)ipaPath withOptions:(id)arg2 error:(NSError *__autoreleasing *)error; 18 | - (BOOL)uninstallApplication:(NSString *)bundleIdentifier withOptions:(id)arg2; 19 | - (BOOL)uninstallApplication:(NSString *)arg1 20 | withOptions:(id)arg2 21 | error:(NSError *__autoreleasing *)arg3 22 | usingBlock:(/*^block*/ id)arg4; 23 | - (BOOL)invalidateIconCache:(id)arg1; 24 | - (BOOL)openSensitiveURL:(NSURL *)url withOptions:(id)arg2 error:(NSError *__autoreleasing *)error; 25 | 26 | - (void)removeObserver:(id)arg1; 27 | - (void)addObserver:(id)arg1; 28 | 29 | @end 30 | 31 | #endif /* LSApplicationWorkspace_h */ 32 | -------------------------------------------------------------------------------- /TrollFools/LogsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogsView.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2025/1/14. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct LogsView: UIViewControllerRepresentable { 11 | 12 | let url: URL 13 | 14 | typealias UIViewControllerType = UINavigationController 15 | 16 | func makeUIViewController(context: Context) -> UINavigationController { 17 | 18 | let viewController = StripedTextTableViewController(path: url.path) 19 | 20 | viewController.autoReload = false 21 | viewController.maximumNumberOfRows = 1000 22 | viewController.maximumNumberOfLines = 20 23 | viewController.reversed = true 24 | viewController.allowDismissal = true 25 | viewController.allowTrash = false 26 | viewController.allowSearch = true 27 | viewController.allowShare = true 28 | viewController.allowMultiline = true 29 | viewController.pullToReload = false 30 | viewController.tapToCopy = true 31 | viewController.pressToCopy = true 32 | viewController.preserveEmptyLines = false 33 | viewController.removeDuplicates = true 34 | 35 | if let regex = try? NSRegularExpression(pattern: "^\\d{4}\\/\\d{2}\\/\\d{2} \\d{2}:\\d{2}:\\d{2}:\\d{3} ") { 36 | viewController.rowPrefixRegularExpression = regex 37 | } 38 | 39 | let navController = UINavigationController(rootViewController: viewController) 40 | return navController 41 | } 42 | 43 | func updateUIViewController(_ uiViewController: UINavigationController, context: Context) { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TrollFools/Option.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Option.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | enum Option { 9 | case attach 10 | case detach 11 | } 12 | -------------------------------------------------------------------------------- /TrollFools/OptionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionCell.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct OptionCell: View { 11 | let option: Option 12 | 13 | var iconName: String { 14 | if #available(iOS 16, *) { 15 | option == .attach ? "syringe" : "xmark.bin" 16 | } else { 17 | option == .attach ? "tray.and.arrow.down" : "xmark.bin" 18 | } 19 | } 20 | 21 | var body: some View { 22 | VStack(spacing: 12) { 23 | ZStack { 24 | Image(systemName: iconName) 25 | .resizable() 26 | .aspectRatio(contentMode: .fit) 27 | .frame(width: 32, height: 32) 28 | .foregroundColor(option == .attach 29 | ? .accentColor : .red) 30 | .padding(.all, 40) 31 | } 32 | .background( 33 | (option == .attach ? Color.accentColor : Color.red) 34 | .opacity(0.1) 35 | .clipShape(RoundedRectangle( 36 | cornerRadius: 10, 37 | style: .continuous 38 | )) 39 | ) 40 | 41 | Text(option == .attach 42 | ? NSLocalizedString("Inject", comment: "") 43 | : NSLocalizedString("Eject", comment: "")) 44 | .font(.headline) 45 | .foregroundColor(option == .attach 46 | ? .accentColor : .red) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /TrollFools/OptionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionView.swift 3 | // TrollFools 4 | // 5 | // Created by Lessica on 2024/7/19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct OptionView: View { 11 | let app: App 12 | 13 | @State var isImporterPresented = false 14 | @State var isImporterSelected = false 15 | 16 | @State var isWarningPresented = false 17 | @State var temporaryResult: Result<[URL], any Error>? 18 | 19 | @State var isSettingsPresented = false 20 | 21 | @State var importerResult: Result<[URL], any Error>? 22 | 23 | @AppStorage("isWarningHidden") 24 | var isWarningHidden: Bool = false 25 | 26 | init(_ app: App) { 27 | self.app = app 28 | } 29 | 30 | var body: some View { 31 | if #available(iOS 15, *) { 32 | content 33 | .alert( 34 | NSLocalizedString("Notice", comment: ""), 35 | isPresented: $isWarningPresented, 36 | presenting: temporaryResult 37 | ) { result in 38 | Button { 39 | importerResult = result 40 | isImporterSelected = true 41 | } label: { 42 | Text(NSLocalizedString("Continue", comment: "")) 43 | } 44 | Button(role: .destructive) { 45 | importerResult = result 46 | isImporterSelected = true 47 | isWarningHidden = true 48 | } label: { 49 | Text(NSLocalizedString("Continue and Don’t Show Again", comment: "")) 50 | } 51 | Button(role: .cancel) { 52 | temporaryResult = nil 53 | isWarningPresented = false 54 | } label: { 55 | Text(NSLocalizedString("Cancel", comment: "")) 56 | } 57 | } message: { 58 | if case .success(let urls) = $0 { 59 | Text(Self.warningMessage(urls)) 60 | } 61 | } 62 | } else { 63 | content 64 | } 65 | } 66 | 67 | var content: some View { 68 | VStack(spacing: 80) { 69 | HStack { 70 | Spacer() 71 | 72 | Button { 73 | isImporterPresented = true 74 | } label: { 75 | OptionCell(option: .attach) 76 | } 77 | .accessibilityLabel(NSLocalizedString("Inject", comment: "")) 78 | 79 | Spacer() 80 | 81 | NavigationLink { 82 | EjectListView(app) 83 | } label: { 84 | OptionCell(option: .detach) 85 | } 86 | .accessibilityLabel(NSLocalizedString("Eject", comment: "")) 87 | 88 | Spacer() 89 | } 90 | 91 | Button { 92 | isSettingsPresented = true 93 | } label: { 94 | Label(NSLocalizedString("Advanced Settings", comment: ""), 95 | systemImage: "gear") 96 | } 97 | } 98 | .padding() 99 | .navigationTitle(app.name) 100 | .background(Group { 101 | NavigationLink(isActive: $isImporterSelected) { 102 | if let result = importerResult { 103 | switch result { 104 | case .success(let urls): 105 | InjectView(app, urlList: urls 106 | .sorted(by: { $0.lastPathComponent < $1.lastPathComponent })) 107 | case .failure(let error): 108 | FailureView( 109 | title: NSLocalizedString("Error", comment: ""), 110 | error: error 111 | ) 112 | } 113 | } 114 | } label: { } 115 | }) 116 | .fileImporter( 117 | isPresented: $isImporterPresented, 118 | allowedContentTypes: [ 119 | .init(filenameExtension: "dylib")!, 120 | .init(filenameExtension: "deb")!, 121 | .bundle, 122 | .framework, 123 | .package, 124 | .zip, 125 | ], 126 | allowsMultipleSelection: true 127 | ) { 128 | result in 129 | switch result { 130 | case .success(let theSuccess): 131 | if !isWarningHidden && theSuccess.contains(where: { $0.pathExtension.lowercased() == "deb" }) { 132 | temporaryResult = result 133 | isWarningPresented = true 134 | } else { 135 | importerResult = result 136 | isImporterSelected = true 137 | } 138 | case .failure: 139 | importerResult = result 140 | isImporterSelected = true 141 | } 142 | } 143 | .sheet(isPresented: $isSettingsPresented) { 144 | if #available(iOS 16, *) { 145 | SettingsView(app) 146 | .presentationDetents([.medium, .large]) 147 | } else { 148 | SettingsView(app) 149 | } 150 | } 151 | } 152 | 153 | static func warningMessage(_ urls: [URL]) -> String { 154 | guard let firstDylibName = urls.first(where: { $0.pathExtension.lowercased() == "deb" })?.lastPathComponent else { 155 | fatalError("No debian package found.") 156 | } 157 | 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) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /TrollFools/PlaceholderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaceholderView.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 3/17/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PlaceholderView: View { 11 | var body: some View { 12 | Text(NSLocalizedString("Select an application to view details.", comment: "")) 13 | .font(.headline) 14 | .foregroundColor(.secondary) 15 | .multilineTextAlignment(.center) 16 | .padding() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TrollFools/PlugInCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlugInCell.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | import QuickLook 9 | import SwiftUI 10 | 11 | private let gDateFormatter: DateFormatter = { 12 | let formatter = DateFormatter() 13 | formatter.dateStyle = .medium 14 | formatter.timeStyle = .short 15 | return formatter 16 | }() 17 | 18 | struct PlugInCell: View { 19 | @EnvironmentObject var ejectList: EjectListModel 20 | 21 | @Binding var quickLookExport: URL? 22 | 23 | let plugIn: InjectedPlugIn 24 | 25 | init(_ plugIn: InjectedPlugIn, quickLookExport: Binding) { 26 | self.plugIn = plugIn 27 | _quickLookExport = quickLookExport 28 | } 29 | 30 | @available(iOS 15, *) 31 | var highlightedName: AttributedString { 32 | let name = plugIn.url.lastPathComponent 33 | var attributedString = AttributedString(name) 34 | if let range = attributedString.range(of: ejectList.filter.searchKeyword, options: [.caseInsensitive, .diacriticInsensitive]) { 35 | attributedString[range].foregroundColor = .accentColor 36 | } 37 | return attributedString 38 | } 39 | 40 | var iconName: String { 41 | let pathExt = plugIn.url.pathExtension.lowercased() 42 | if pathExt == "bundle" { 43 | return "archivebox" 44 | } 45 | if pathExt == "dylib" { 46 | return "bandage" 47 | } 48 | if pathExt == "framework" { 49 | return "shippingbox" 50 | } 51 | return "puzzlepiece" 52 | } 53 | 54 | var body: some View { 55 | HStack(spacing: 12) { 56 | Image(systemName: iconName) 57 | .resizable() 58 | .aspectRatio(contentMode: .fit) 59 | .frame(width: 24, height: 24) 60 | .foregroundColor(.accentColor) 61 | 62 | VStack(alignment: .leading) { 63 | if #available(iOS 15, *) { 64 | Text(highlightedName) 65 | .font(.headline) 66 | } else { 67 | Text(plugIn.url.lastPathComponent) 68 | .font(.headline) 69 | } 70 | 71 | Text(gDateFormatter.string(from: plugIn.createdAt)) 72 | .font(.subheadline) 73 | } 74 | } 75 | .contextMenu { 76 | if #available(iOS 16.4, *) { 77 | ShareLink(item: plugIn.url) { 78 | Label(NSLocalizedString("Export", comment: ""), systemImage: "square.and.arrow.up") 79 | } 80 | } else { 81 | Button { 82 | exportPlugIn() 83 | } label: { 84 | Label(NSLocalizedString("Export", comment: ""), systemImage: "square.and.arrow.up") 85 | } 86 | } 87 | 88 | Button { 89 | openInFilza() 90 | } label: { 91 | if isFilzaInstalled { 92 | Label(NSLocalizedString("Show in Filza", comment: ""), systemImage: "scope") 93 | } else { 94 | Label(NSLocalizedString("Filza (URL Scheme) Not Installed", comment: ""), systemImage: "xmark.octagon") 95 | } 96 | } 97 | .disabled(!isFilzaInstalled) 98 | } 99 | } 100 | 101 | private func exportPlugIn() { 102 | quickLookExport = plugIn.url 103 | } 104 | 105 | var isFilzaInstalled: Bool { ejectList.app.appList?.isFilzaInstalled ?? false } 106 | 107 | private func openInFilza() { 108 | ejectList.app.appList?.openInFilza(plugIn.url) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /TrollFools/SettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsView.swift 3 | // TrollFools 4 | // 5 | // Created by Lessica on 2024/7/28. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SettingsView: View { 11 | let app: App 12 | 13 | init(_ app: App) { 14 | self.app = app 15 | _useWeakReference = AppStorage(wrappedValue: true, "UseWeakReference-\(app.id)") 16 | _preferMainExecutable = AppStorage(wrappedValue: false, "PreferMainExecutable-\(app.id)") 17 | _injectStrategy = AppStorage(wrappedValue: .lexicographic, "InjectStrategy-\(app.id)") 18 | } 19 | 20 | @AppStorage var useWeakReference: Bool 21 | @AppStorage var preferMainExecutable: Bool 22 | @AppStorage var injectStrategy: InjectorV3.Strategy 23 | 24 | @StateObject var viewControllerHost = ViewControllerHost() 25 | 26 | var body: some View { 27 | NavigationView { 28 | Form { 29 | Section { 30 | Picker(NSLocalizedString("Injection Strategy", comment: ""), selection: $injectStrategy) { 31 | ForEach(InjectorV3.Strategy.allCases, id: \.self) { strategy in 32 | Text(strategy.localizedDescription).tag(strategy) 33 | } 34 | } 35 | Toggle(NSLocalizedString("Prefer Main Executable", comment: ""), isOn: $preferMainExecutable) 36 | Toggle(NSLocalizedString("Use Weak Reference", comment: ""), isOn: $useWeakReference) 37 | } header: { 38 | paddedHeaderFooterText(NSLocalizedString("Injection", comment: "")) 39 | } footer: { 40 | paddedHeaderFooterText(NSLocalizedString("If you do not know what these options mean, please do not change them.", comment: "")) 41 | } 42 | } 43 | .navigationTitle(NSLocalizedString("Advanced Settings", comment: "")) 44 | .navigationBarTitleDisplayMode(.inline) 45 | .onViewWillAppear { 46 | viewControllerHost.viewController = $0 47 | } 48 | .toolbar { 49 | ToolbarItem(placement: .confirmationAction) { 50 | Button { 51 | viewControllerHost.viewController?.dismiss(animated: true) 52 | } label: { 53 | Text(NSLocalizedString("Done", comment: "")) 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | @ViewBuilder 61 | private func paddedHeaderFooterText(_ content: String) -> some View { 62 | if #available(iOS 15, *) { 63 | Text(content) 64 | .font(.footnote) 65 | } else { 66 | Text(content) 67 | .font(.footnote) 68 | .padding(.horizontal, 16) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TrollFools/StripedTextTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // StripedTextTableViewController.h 3 | // CommonViewControllers 4 | // 5 | // Created by Lessica <82flex@gmail.com> on 2022/1/20. 6 | // Copyright © 2022 Zheng Wu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class StripedTextTableViewController; 14 | 15 | @protocol StripedTextTableViewControllerDelegate 16 | @optional 17 | - (void)stripedTextTableViewRowDidCopy:(StripedTextTableViewController *)controller withText:(NSString *)text; 18 | @end 19 | 20 | @interface StripedTextTableViewController : UITableViewController 21 | 22 | @property (nonatomic, weak) id delegate; 23 | 24 | - (instancetype)initWithPath:(NSString *)path; 25 | @property (nonatomic, copy, readonly) NSString *entryPath; 26 | 27 | @property (nonatomic, assign) BOOL autoReload; 28 | 29 | @property (nonatomic, assign) BOOL reversed; 30 | @property (nonatomic, assign) BOOL removeDuplicates; 31 | @property (nonatomic, assign) BOOL allowTrash; 32 | @property (nonatomic, assign) BOOL allowSearch; 33 | @property (nonatomic, assign) BOOL allowShare; 34 | @property (nonatomic, assign) BOOL allowDismissal; 35 | @property (nonatomic, assign) BOOL pullToReload; 36 | @property (nonatomic, assign) BOOL tapToCopy; 37 | @property (nonatomic, assign) BOOL pressToCopy; 38 | @property (nonatomic, assign) BOOL preserveEmptyLines; 39 | 40 | @property (nonatomic, assign) CGFloat rowHeight; 41 | @property (nonatomic, assign) BOOL allowMultiline; 42 | @property (nonatomic, assign) NSLineBreakMode lineBreakMode; 43 | 44 | @property (nonatomic, assign) NSUInteger maximumNumberOfLines; // default is 0, unlimited 45 | @property (nonatomic, assign) NSUInteger maximumNumberOfRows; // default is 0, unlimited 46 | 47 | @property (nonatomic, copy) NSString *rowSeparator; 48 | @property (nonatomic, copy) NSRegularExpression *rowPrefixRegularExpression; 49 | 50 | @end 51 | 52 | NS_ASSUME_NONNULL_END 53 | -------------------------------------------------------------------------------- /TrollFools/SuccessView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SuccessView.swift 3 | // TrollFools 4 | // 5 | // Created by Lessica on 2024/7/19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SuccessView: View { 11 | 12 | let title: String 13 | let logFileURL: URL? 14 | 15 | @State private var isLogsPresented = false 16 | 17 | var body: some View { 18 | VStack(spacing: 20) { 19 | Image(systemName: "checkmark.circle.fill") 20 | .font(.system(size: 64)) 21 | .foregroundColor(.green) 22 | 23 | Text(title) 24 | .font(.title) 25 | .bold() 26 | 27 | if logFileURL != nil { 28 | Button { 29 | isLogsPresented = true 30 | } label: { 31 | Label(NSLocalizedString("View Logs", comment: ""), 32 | systemImage: "note.text") 33 | } 34 | } 35 | } 36 | .padding() 37 | .multilineTextAlignment(.center) 38 | .sheet(isPresented: $isLogsPresented) { 39 | if let logFileURL { 40 | LogsView(url: logFileURL) 41 | } 42 | } 43 | } 44 | } 45 | 46 | #Preview { 47 | SuccessView( 48 | title: "Hello, World!", 49 | logFileURL: nil 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /TrollFools/TrollFools-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LSApplicationProxy.h" 6 | #import "LSApplicationWorkspace.h" 7 | #import "StripedTextTableViewController.h" 8 | 9 | #import 10 | 11 | FOUNDATION_EXTERN NSString *TFGetDisplayVersion(void); 12 | FOUNDATION_EXTERN void TFUtilKillAll(NSString *processPath, BOOL softly); 13 | 14 | @interface UIImage (Private) 15 | + (instancetype)_applicationIconImageForBundleIdentifier:(NSString *)bundleIdentifier 16 | format:(int)format 17 | scale:(CGFloat)scale; 18 | @end 19 | -------------------------------------------------------------------------------- /TrollFools/TrollFools.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | application-identifier 6 | GXZ23M5TP2.wiki.qaq.TrollFools 7 | checklessPersistentURLTranslation 8 | 9 | com.apple.AutoWake-write-access 10 | 11 | com.apple.CommCenter.fine-grained 12 | 13 | spi 14 | identity 15 | 16 | com.apple.QuartzCore.displayable-context 17 | 18 | com.apple.QuartzCore.secure-mode 19 | 20 | com.apple.SystemConfiguration.SCDynamicStore-write-access 21 | 22 | com.apple.SystemConfiguration.SCPreferences-write-access 23 | 24 | com.apple.assertiond.app-state-monitor 25 | 26 | com.apple.backboard.client 27 | 28 | com.apple.backboardd.launchapplications 29 | 30 | com.apple.developer.icloud-container-identifiers 31 | 32 | GXZ23M5TP2.iCloud.wiki.qaq.TrollFools.icloud-container 33 | 34 | com.apple.developer.icloud-services 35 | 36 | CloudDocuments 37 | 38 | com.apple.developer.notificationcenter-identifiers 39 | 40 | com.apple.developer.team-identifier 41 | GXZ23M5TP2 42 | com.apple.developer.ubiquity-container-identifiers 43 | 44 | GXZ23M5TP2.iCloud.wiki.qaq.TrollFools.icloud-container 45 | 46 | com.apple.frontboard.launchapplications 47 | 48 | com.apple.frontboard.shutdown 49 | 50 | com.apple.mobile.deleted.AllowFreeSpace 51 | 52 | com.apple.multitasking.systemappassertions 53 | 54 | com.apple.multitasking.termination 55 | 56 | com.apple.private.MobileContainerManager.allowed 57 | 58 | com.apple.private.MobileContainerManager.lookup 59 | 60 | com.apple.private.MobileContainerManager.otherIdLookup 61 | 62 | com.apple.private.attribution.usage-reporting-only.implicitly-assumed-identity 63 | 64 | type 65 | bundleID 66 | value 67 | wiki.qaq.TrollFools 68 | 69 | com.apple.private.coreservices.canmaplsdatabase 70 | 71 | com.apple.private.kernel.jetsam 72 | 73 | com.apple.private.memorystatus 74 | 75 | com.apple.private.network.socket-delegate 76 | 77 | com.apple.private.persona-mgmt 78 | 79 | com.apple.private.security.container-manager 80 | 81 | com.apple.private.security.container-required 82 | 83 | com.apple.private.security.disk-device-access 84 | 85 | com.apple.private.security.no-container 86 | 87 | com.apple.private.security.no-sandbox 88 | 89 | com.apple.private.security.storage.AppBundles 90 | 91 | com.apple.private.security.storage.AppDataContainers 92 | 93 | com.apple.private.security.storage.CloudDocsDB 94 | 95 | com.apple.private.security.storage.CloudKit 96 | 97 | com.apple.private.security.storage.DocumentRevisions 98 | 99 | com.apple.private.security.storage.Mail 100 | 101 | com.apple.private.security.storage.MobileDocuments 102 | 103 | com.apple.private.security.storage.Photos 104 | 105 | com.apple.private.security.storage.ciconia 106 | 107 | com.apple.private.security.storage.iCloudDrive 108 | 109 | com.apple.private.tcc.allow 110 | 111 | kTCCServiceUbiquity 112 | kTCCServiceSystemPolicyDesktopFolder 113 | kTCCServiceSystemPolicyDocumentsFolder 114 | kTCCServiceSystemPolicyDownloadsFolder 115 | 116 | com.apple.private.tcc.manager 117 | 118 | com.apple.private.tcc.manager.check-by-audit-token 119 | 120 | kTCCServiceUbiquity 121 | kTCCServiceSystemPolicyDesktopFolder 122 | kTCCServiceSystemPolicyDocumentsFolder 123 | kTCCServiceSystemPolicyDownloadsFolder 124 | 125 | com.apple.private.vfs.allow-low-space-writes 126 | 127 | com.apple.private.vfs.open-by-id 128 | 129 | com.apple.security.exception.files.absolute-path.read-write 130 | 131 | / 132 | 133 | com.apple.security.exception.files.home-relative-path.read-write 134 | 135 | /Library/CloudStorage/ 136 | /Library/Mobile Documents/ 137 | 138 | com.apple.security.exception.mach-lookup.global-name 139 | 140 | com.apple.lsd.modifydb 141 | 142 | com.apple.security.iokit-user-client-class 143 | 144 | IOUserClient 145 | AGXDeviceUserClient 146 | IOHDIXControllerUserClient 147 | IOMobileFramebufferUserClient 148 | IOSurfaceRootUserClient 149 | 150 | com.apple.springboard.CFUserNotification 151 | 152 | com.apple.springboard.appbackgroundstyle 153 | 154 | com.apple.springboard.iconState 155 | 156 | com.apple.springboard.iconState.mutate 157 | 158 | com.apple.springboard.launchapplications 159 | 160 | com.apple.springboard.launchapplicationswithoptions 161 | 162 | com.apple.springboard.opensensitiveurl 163 | 164 | com.apple.springboard.openurlswhenlocked 165 | 166 | com.apple.usernotification.notificationregistrarproxy 167 | 168 | com.apple.usernotification.notificationschedulerproxy 169 | 170 | com.apple.usernotification.proxy 171 | 172 | com.apple.usernotifications.legacy-extension 173 | 174 | file-read-data 175 | 176 | inter-app-audio 177 | 178 | keychain-access-groups 179 | 180 | wiki.qaq.TrollFools 181 | GXZ23M5TP2.wiki.qaq.TrollFools 182 | 183 | platform-application 184 | 185 | uicache.app-data-container-required 186 | 187 | user-preference-read 188 | 189 | user-preference-write 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /TrollFools/TrollFoolsApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrollFoolsApp.swift 3 | // TrollFools 4 | // 5 | // Created by Lessica on 2024/7/19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct TrollFoolsApp: SwiftUI.App { 12 | 13 | @AppStorage("isDisclaimerHidden") 14 | var isDisclaimerHidden: Bool = false 15 | 16 | init() { 17 | try? FileManager.default.removeItem(at: InjectorV3.temporaryRoot) 18 | } 19 | 20 | var body: some Scene { 21 | WindowGroup { 22 | ZStack { 23 | if isDisclaimerHidden { 24 | AppListView() 25 | .environmentObject(AppListModel()) 26 | .transition(.opacity) 27 | } else { 28 | DisclaimerView(isDisclaimerHidden: $isDisclaimerHidden) 29 | .transition(.opacity) 30 | } 31 | } 32 | .animation(.easeInOut, value: isDisclaimerHidden) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TrollFools/TrollFoolsStub.m: -------------------------------------------------------------------------------- 1 | // 2 | // TrollFoolsStub.m 3 | // TrollFools 4 | // 5 | // Created by Lessica on 2024/7/19. 6 | // 7 | 8 | #import 9 | 10 | #import 11 | #import 12 | #import 13 | #import 14 | 15 | FOUNDATION_EXTERN void TFUtilKillAll(NSString *processPath, BOOL softly); 16 | 17 | static void TFUtilEnumerateProcessesUsingBlock(void (^enumerator)(pid_t pid, NSString *executablePath, BOOL *stop)) { 18 | 19 | static int kMaximumArgumentSize = 0; 20 | static dispatch_once_t onceToken; 21 | dispatch_once(&onceToken, ^{ 22 | size_t valSize = sizeof(kMaximumArgumentSize); 23 | if (sysctl((int[]){CTL_KERN, KERN_ARGMAX}, 2, &kMaximumArgumentSize, &valSize, NULL, 0) < 0) { 24 | perror("sysctl argument size"); 25 | kMaximumArgumentSize = 4096; 26 | } 27 | }); 28 | 29 | size_t procInfoLength = 0; 30 | if (sysctl((int[]){CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}, 3, NULL, &procInfoLength, NULL, 0) < 0) { 31 | return; 32 | } 33 | 34 | static struct kinfo_proc *procInfo = NULL; 35 | procInfo = (struct kinfo_proc *)realloc(procInfo, procInfoLength + 1); 36 | if (!procInfo) { 37 | return; 38 | } 39 | 40 | bzero(procInfo, procInfoLength + 1); 41 | if (sysctl((int[]){CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}, 3, procInfo, &procInfoLength, NULL, 0) < 0) { 42 | return; 43 | } 44 | 45 | static char *argBuffer = NULL; 46 | int procInfoCnt = (int)(procInfoLength / sizeof(struct kinfo_proc)); 47 | for (int i = 0; i < procInfoCnt; i++) { 48 | 49 | pid_t pid = procInfo[i].kp_proc.p_pid; 50 | if (pid <= 1) { 51 | continue; 52 | } 53 | 54 | size_t argSize = kMaximumArgumentSize; 55 | if (sysctl((int[]){CTL_KERN, KERN_PROCARGS2, pid, 0}, 3, NULL, &argSize, NULL, 0) < 0) { 56 | continue; 57 | } 58 | 59 | argBuffer = (char *)realloc(argBuffer, argSize + 1); 60 | if (!argBuffer) { 61 | continue; 62 | } 63 | 64 | bzero(argBuffer, argSize + 1); 65 | if (sysctl((int[]){CTL_KERN, KERN_PROCARGS2, pid, 0}, 3, argBuffer, &argSize, NULL, 0) < 0) { 66 | continue; 67 | } 68 | 69 | BOOL stop = NO; 70 | @autoreleasepool { 71 | enumerator(pid, [NSString stringWithUTF8String:(argBuffer + sizeof(int))], &stop); 72 | } 73 | 74 | if (stop) { 75 | break; 76 | } 77 | } 78 | } 79 | 80 | void TFUtilKillAll(NSString *processName, BOOL softly) { 81 | TFUtilEnumerateProcessesUsingBlock(^(pid_t pid, NSString *executablePath, BOOL *stop) { 82 | if ([executablePath containsString:[NSString stringWithFormat:@"/%@.app/%@", processName, processName]]) { 83 | if (softly) { 84 | kill(pid, SIGTERM); 85 | } else { 86 | kill(pid, SIGKILL); 87 | } 88 | } 89 | }); 90 | } 91 | 92 | static NSString *TFGetMarketingVersion(void) { 93 | return @MARKETING_VERSION; 94 | } 95 | 96 | static NSString *TFGetCurrentProjectVersion(void) { 97 | return @CURRENT_PROJECT_VERSION; 98 | } 99 | 100 | NSString *TFGetDisplayVersion(void) { 101 | static NSString *displayVersion = nil; 102 | static dispatch_once_t onceToken; 103 | dispatch_once(&onceToken, ^{ 104 | displayVersion = [NSString stringWithFormat:@"%@ (%@)", TFGetMarketingVersion(), TFGetCurrentProjectVersion()]; 105 | }); 106 | return displayVersion; 107 | } 108 | -------------------------------------------------------------------------------- /TrollFools/Version.Debug.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Version.xcconfig 3 | // TrollFools 4 | // 5 | // Created by Lessica on 2024/8/18. 6 | // 7 | 8 | // Configuration settings file format documentation can be found at: 9 | // https://help.apple.com/xcode/#/dev745c5c974 10 | 11 | DEBUG_VERSION = 3.6 12 | DEBUG_BUILD_NUMBER = 202503171 13 | -------------------------------------------------------------------------------- /TrollFools/Version.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Version.xcconfig 3 | // TRApp 4 | // 5 | // Created by Lessica on 2024/8/18. 6 | // 7 | 8 | // Configuration settings file format documentation can be found at: 9 | // https://help.apple.com/xcode/#/dev745c5c974 10 | 11 | VERSION = 3.7 12 | BUILD_NUMBER = 42 13 | -------------------------------------------------------------------------------- /TrollFools/ViewControllerHost.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerHost.swift 3 | // TrollFools 4 | // 5 | // Created by 82Flex on 2024/10/30. 6 | // 7 | 8 | import SwiftUI 9 | 10 | final class ViewControllerHost: ObservableObject { 11 | weak var viewController: UIViewController? 12 | } 13 | 14 | extension View { 15 | func onViewWillAppear(perform onViewWillAppear: @escaping ((UIViewController) -> Void)) -> some View { 16 | modifier(VCHookViewModifier(onViewWillAppear: onViewWillAppear)) 17 | } 18 | } 19 | 20 | private final class VCHookViewController: UIViewController { 21 | var onViewWillAppear: ((UIViewController) -> Void)? 22 | var didTriggered = false 23 | 24 | override func viewWillAppear(_ animated: Bool) { 25 | super.viewWillAppear(animated) 26 | guard !didTriggered else { 27 | return 28 | } 29 | onViewWillAppear?(self) 30 | didTriggered = true 31 | } 32 | } 33 | 34 | private struct VCHookView: UIViewControllerRepresentable { 35 | typealias UIViewControllerType = VCHookViewController 36 | let onViewWillAppear: ((UIViewController) -> Void) 37 | 38 | func makeUIViewController(context: Context) -> VCHookViewController { 39 | let vc = VCHookViewController() 40 | vc.onViewWillAppear = onViewWillAppear 41 | return vc 42 | } 43 | 44 | func updateUIViewController(_ uiViewController: VCHookViewController, context: Context) { } 45 | } 46 | 47 | private struct VCHookViewModifier: ViewModifier { 48 | let onViewWillAppear: ((UIViewController) -> Void) 49 | 50 | func body(content: Content) -> some View { 51 | content.background(VCHookView(onViewWillAppear: onViewWillAppear)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /TrollFools/chown: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/chown -------------------------------------------------------------------------------- /TrollFools/cp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/cp -------------------------------------------------------------------------------- /TrollFools/cp-15: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/cp-15 -------------------------------------------------------------------------------- /TrollFools/ct_bypass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/ct_bypass -------------------------------------------------------------------------------- /TrollFools/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | "CFBundleDisplayName" = "TrollFools"; 2 | -------------------------------------------------------------------------------- /TrollFools/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "%@ exited with code %d" = "%@ exited with code %d"; 3 | 4 | /* StripedTextTableViewController */ 5 | "%@ rows not loaded" = "%@ rows not loaded"; 6 | 7 | /* No comment provided by engineer. */ 8 | "%@ terminated with signal %d" = "%@ terminated with signal %d"; 9 | 10 | /* No comment provided by engineer. */ 11 | "%ld Plug-Ins of “%@”" = "%1$ld Plug-Ins of “%2$@”"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Advanced Settings" = "Advanced Settings"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Advertisement" = "Advertisement"; 18 | 19 | /* No comment provided by engineer. */ 20 | "All" = "All"; 21 | 22 | /* No comment provided by engineer. */ 23 | "All Applications" = "All Applications"; 24 | 25 | /* No comment provided by engineer. */ 26 | "And %d more unsupported user applications." = "And %d more unsupported user applications."; 27 | 28 | /* No comment provided by engineer. */ 29 | "Bringing back the most advanced system and security analysis tool." = "Bringing back the most advanced system and security analysis tool."; 30 | 31 | /* StripedTextTableViewController */ 32 | "Cancel" = "Cancel"; 33 | 34 | /* No comment provided by engineer. */ 35 | "Cannot parse text with UTF-8 encoding: “%@”." = "Cannot parse text with UTF-8 encoding: “%@”."; 36 | 37 | /* No comment provided by engineer. */ 38 | "Completed" = "Completed"; 39 | 40 | /* StripedTextTableViewController */ 41 | "Confirm" = "Confirm"; 42 | 43 | /* No comment provided by engineer. */ 44 | "Continue" = "Continue"; 45 | 46 | /* No comment provided by engineer. */ 47 | "Continue and Don’t Show Again" = "Continue and Don’t Show Again"; 48 | 49 | /* StripedTextTableViewController */ 50 | "Copy" = "Copy"; 51 | 52 | /* No comment provided by engineer. */ 53 | "Copyright" = "Copyright"; 54 | 55 | /* StripedTextTableViewController */ 56 | "Do you want to clear this log file “%@”?" = "Do you want to clear this log file “%@”?"; 57 | 58 | /* No comment provided by engineer. */ 59 | "Done" = "Done"; 60 | 61 | /* No comment provided by engineer. */ 62 | "Eject" = "Eject"; 63 | 64 | /* No comment provided by engineer. */ 65 | "Eject All" = "Eject All"; 66 | 67 | /* No comment provided by engineer. */ 68 | "Error" = "Error"; 69 | 70 | /* No comment provided by engineer. */ 71 | "Export" = "Export"; 72 | 73 | /* No comment provided by engineer. */ 74 | "Export All" = "Export All"; 75 | 76 | /* No comment provided by engineer. */ 77 | "Failed" = "Failed"; 78 | 79 | /* No comment provided by engineer. */ 80 | "Failed to find entry CFBundleExecutable in: %@" = "Failed to find entry CFBundleExecutable in: %@"; 81 | 82 | /* No comment provided by engineer. */ 83 | "Failed to find entry CFBundleIdentifier in: %@" = "Failed to find entry CFBundleIdentifier in: %@"; 84 | 85 | /* No comment provided by engineer. */ 86 | "Failed to locate main executable: %@" = "Failed to locate main executable: %@"; 87 | 88 | /* No comment provided by engineer. */ 89 | "Failed to parse: %@" = "Failed to parse: %@"; 90 | 91 | /* No comment provided by engineer. */ 92 | "Fast" = "Fast"; 93 | 94 | /* No comment provided by engineer. */ 95 | "Filza (URL Scheme) Not Installed" = "Filza (URL Scheme) Not Installed"; 96 | 97 | /* No comment provided by engineer. */ 98 | "Hide" = "Hide"; 99 | 100 | /* No comment provided by engineer. */ 101 | "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."; 102 | 103 | /* No comment provided by engineer. */ 104 | "Inject" = "Inject"; 105 | 106 | /* No comment provided by engineer. */ 107 | "Injectable System Applications" = "Injectable System Applications"; 108 | 109 | /* No comment provided by engineer. */ 110 | "Injected Plug-Ins" = "Injected Plug-Ins"; 111 | 112 | /* No comment provided by engineer. */ 113 | "Injecting" = "Injecting"; 114 | 115 | /* No comment provided by engineer. */ 116 | "Injection" = "Injection"; 117 | 118 | /* No comment provided by engineer. */ 119 | "Injection Strategy" = "Injection Strategy"; 120 | 121 | /* No comment provided by engineer. */ 122 | "Launch" = "Launch"; 123 | 124 | /* No comment provided by engineer. */ 125 | "Lexicographic" = "Lexicographic"; 126 | 127 | /* No comment provided by engineer. */ 128 | "Lock Version" = "Lock Version"; 129 | 130 | /* StripedTextTableViewController */ 131 | "Log Viewer" = "Log Viewer"; 132 | 133 | /* No comment provided by engineer. */ 134 | "Made with ♥ by OwnGoal Studio" = "Made with ♥ by OwnGoal Studio"; 135 | 136 | /* No comment provided by engineer. */ 137 | "No Applications" = "No Applications"; 138 | 139 | /* No comment provided by engineer. */ 140 | "No dylib found in the Debian package." = "No dylib found in the Debian package."; 141 | 142 | /* No comment provided by engineer. */ 143 | "No eligible framework found." = "No eligible framework found."; 144 | 145 | /* No comment provided by engineer. */ 146 | "No eligible framework found.\n\nIt is usually not a bug with TrollFools itself, but rather with the target app. You may re-install that from App Store. You can’t use TrollFools with apps installed via “Asspp” or tweaks like “NoAppThinning”." = "No eligible framework found.\n\nIt is usually not a bug with TrollFools itself, but rather with the target app. You may re-install that from App Store. You can’t use TrollFools with apps installed via “Asspp” or tweaks like “NoAppThinning”."; 147 | 148 | /* No comment provided by engineer. */ 149 | "No Injected Plug-Ins" = "No Injected Plug-Ins"; 150 | 151 | /* No comment provided by engineer. */ 152 | "No valid plug-ins found." = "No valid plug-ins found."; 153 | 154 | /* No comment provided by engineer. */ 155 | "Notice" = "Notice"; 156 | 157 | /* No comment provided by engineer. */ 158 | "Only removable system applications are eligible and listed." = "Only removable system applications are eligible and listed."; 159 | 160 | /* No comment provided by engineer. */ 161 | "Patched" = "Patched"; 162 | 163 | /* No comment provided by engineer. */ 164 | "Pinned Version" = "Pinned Version"; 165 | 166 | /* No comment provided by engineer. */ 167 | "Plug-Ins" = "Plug-Ins"; 168 | 169 | /* No comment provided by engineer. */ 170 | "Post-order" = "Post-order"; 171 | 172 | /* No comment provided by engineer. */ 173 | "Pre-order" = "Pre-order"; 174 | 175 | /* No comment provided by engineer. */ 176 | "Prefer Main Executable" = "Prefer Main Executable"; 177 | 178 | /* No comment provided by engineer. */ 179 | "Rebuild Icon Cache" = "Rebuild Icon Cache"; 180 | 181 | /* No comment provided by engineer. */ 182 | "Record your phone calls like never before." = "Record your phone calls like never before."; 183 | 184 | /* No comment provided by engineer. */ 185 | "Reveil" = "Reveil"; 186 | 187 | /* No comment provided by engineer. */ 188 | "Search Patched…" = "Search Patched…"; 189 | 190 | /* No comment provided by engineer. */ 191 | "Search…" = "Search…"; 192 | 193 | /* No comment provided by engineer. */ 194 | "Select an application to view details." = "Select an application to view details."; 195 | 196 | /* No comment provided by engineer. */ 197 | "Select Application to Inject" = "Select Application to Inject"; 198 | 199 | /* No comment provided by engineer. */ 200 | "Show in Filza" = "Show in Filza"; 201 | 202 | /* No comment provided by engineer. */ 203 | "Show Patched Only" = "Show Patched Only"; 204 | 205 | /* No comment provided by engineer. */ 206 | "Some plug-ins were not injected by TrollFools, please eject them with caution." = "Some plug-ins were not injected by TrollFools, please eject them with caution."; 207 | 208 | /* No comment provided by engineer. */ 209 | "Source Code" = "Source Code"; 210 | 211 | /* No comment provided by engineer. */ 212 | "System" = "System"; 213 | 214 | /* No comment provided by engineer. */ 215 | "The content of text file “%@” is empty." = "The content of text file “%@” is empty."; 216 | 217 | /* No comment provided by engineer. */ 218 | "TrollFools" = "TrollFools"; 219 | 220 | /* No comment provided by engineer. */ 221 | "TrollRecorder" = "TrollRecorder"; 222 | 223 | /* No comment provided by engineer. */ 224 | "TrollStore" = "TrollStore"; 225 | 226 | /* No comment provided by engineer. */ 227 | "TrollStore Applications" = "TrollStore Applications"; 228 | 229 | /* No comment provided by engineer. */ 230 | "Unable to locate the data archive in the Debian package." = "Unable to locate the data archive in the Debian package."; 231 | 232 | /* No comment provided by engineer. */ 233 | "Unlock Version" = "Unlock Version"; 234 | 235 | /* No comment provided by engineer. */ 236 | "Use Weak Reference" = "Use Weak Reference"; 237 | 238 | /* No comment provided by engineer. */ 239 | "User" = "User"; 240 | 241 | /* No comment provided by engineer. */ 242 | "User Applications" = "User Applications"; 243 | 244 | /* No comment provided by engineer. */ 245 | "View Logs" = "View Logs"; 246 | 247 | /* No comment provided by engineer. */ 248 | "You need to rebuild the icon cache in TrollStore to apply changes." = "You need to rebuild the icon cache in TrollStore to apply changes."; 249 | 250 | /* No comment provided by engineer. */ 251 | "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."; 252 | -------------------------------------------------------------------------------- /TrollFools/insert_dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/insert_dylib -------------------------------------------------------------------------------- /TrollFools/install_name_tool: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/install_name_tool -------------------------------------------------------------------------------- /TrollFools/it.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | "CFBundleDisplayName" = "TrollFools"; 2 | -------------------------------------------------------------------------------- /TrollFools/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "%@ exited with code %d" = "%@ uscito col codice %d"; 3 | 4 | /* StripedTextTableViewController */ 5 | "%@ rows not loaded" = "%@ rows non caricati"; 6 | 7 | /* No comment provided by engineer. */ 8 | "%@ terminated with signal %d" = "%@ terminato col segnale %d"; 9 | 10 | /* TODO */ 11 | "%ld Plug-Ins of “%@”" = "%1$ld Plug-Ins of “%2$@”"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Advanced Settings" = "Impostazioni Avanzate"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Advertisement" = "Advertisement"; 18 | 19 | /* TODO */ 20 | "All" = "All"; 21 | 22 | /* TODO */ 23 | "All Applications" = "All Applications"; 24 | 25 | /* No comment provided by engineer. */ 26 | "And %d more unsupported user applications." = "E %d più non supportate dalle applicazioni."; 27 | 28 | /* No comment provided by engineer. */ 29 | "Bringing back the most advanced system and security analysis tool." = "Portiamo indietro il più avanzato strumento di analisi di sistema e della sicurezza."; 30 | 31 | /* StripedTextTableViewController */ 32 | "Cancel" = "Cancella"; 33 | 34 | /* No comment provided by engineer. */ 35 | "Cannot parse text with UTF-8 encoding: “%@”." = "Impossibile usare il testo con la codifica UTF-8: “%@”."; 36 | 37 | /* No comment provided by engineer. */ 38 | "Completed" = "Completato"; 39 | 40 | /* StripedTextTableViewController */ 41 | "Confirm" = "Conferma"; 42 | 43 | /* TODO */ 44 | "Continue" = "Continue"; 45 | 46 | /* TODO */ 47 | "Continue and Don’t Show Again" = "Continue and Don’t Show Again"; 48 | 49 | /* StripedTextTableViewController */ 50 | "Copy" = "Copia"; 51 | 52 | /* No comment provided by engineer. */ 53 | "Copyright" = "Copyright"; 54 | 55 | /* StripedTextTableViewController */ 56 | "Do you want to clear this log file “%@”?" = "Vuoi cancellare questo log “%@”?"; 57 | 58 | /* No comment provided by engineer. */ 59 | "Done" = "Fatto"; 60 | 61 | /* No comment provided by engineer. */ 62 | "Eject" = "Espelli"; 63 | 64 | /* No comment provided by engineer. */ 65 | "Eject All" = "Espelli Tutti"; 66 | 67 | /* No comment provided by engineer. */ 68 | "Error" = "Errore"; 69 | 70 | /* TODO */ 71 | "Export" = "Export"; 72 | 73 | /* TODO */ 74 | "Export All" = "Export All"; 75 | 76 | /* No comment provided by engineer. */ 77 | "Failed" = "Fallito"; 78 | 79 | /* No comment provided by engineer. */ 80 | "Failed to find entry CFBundleExecutable in: %@" = "Impossibile trovare l'entrata CFBundleExecutable in: %@"; 81 | 82 | /* No comment provided by engineer. */ 83 | "Failed to find entry CFBundleIdentifier in: %@" = "Impossibile trovare l'entrata CFBundleIdentifier in: %@"; 84 | 85 | /* No comment provided by engineer. */ 86 | "Failed to locate main executable: %@" = "Impossibile trovare l'eseguibile principale: %@"; 87 | 88 | /* No comment provided by engineer. */ 89 | "Failed to parse: %@" = "Impossibile scrivere: %@"; 90 | 91 | /* No comment provided by engineer. */ 92 | "Fast" = "Veloce"; 93 | 94 | /* No comment provided by engineer. */ 95 | "Filza (URL Scheme) Not Installed" = "Filza (URL Scheme) Non Installato"; 96 | 97 | /* No comment provided by engineer. */ 98 | "Hide" = "Hide"; 99 | 100 | /* No comment provided by engineer. */ 101 | "If you do not know what these options mean, please do not change them." = "Se non sai cosa significano queste opzioni, non cambiarle."; 102 | 103 | /* No comment provided by engineer. */ 104 | "Inject" = "Inietta"; 105 | 106 | /* No comment provided by engineer. */ 107 | "Injectable System Applications" = "Applicazioni di Sistema Iniettabili"; 108 | 109 | /* No comment provided by engineer. */ 110 | "Injected Plug-Ins" = "Inietta Plug-In"; 111 | 112 | /* No comment provided by engineer. */ 113 | "Injecting" = "Inietto"; 114 | 115 | /* No comment provided by engineer. */ 116 | "Injection" = "Inietazione"; 117 | 118 | /* No comment provided by engineer. */ 119 | "Injection Strategy" = "Strategia Inietazione"; 120 | 121 | /* No comment provided by engineer. */ 122 | "Launch" = "Lancia"; 123 | 124 | /* No comment provided by engineer. */ 125 | "Lexicographic" = "Lessicografico"; 126 | 127 | /* No comment provided by engineer. */ 128 | "Lock Version" = "Blocca Versione"; 129 | 130 | /* StripedTextTableViewController */ 131 | "Log Viewer" = "Visualizzatore Log"; 132 | 133 | /* No comment provided by engineer. */ 134 | "Made with ♥ by OwnGoal Studio" = "Fatto col ♥ da OwnGoal Studio"; 135 | 136 | /* No comment provided by engineer. */ 137 | "No Applications" = "No Applications"; 138 | 139 | /* TODO */ 140 | "No dylib found in the Debian package." = "No dylib found in the Debian package."; 141 | 142 | /* No comment provided by engineer. */ 143 | "No eligible framework found." = "Nessun framework eleggibile trovato."; 144 | 145 | /* No comment provided by engineer. */ 146 | "No eligible framework found.\n\nIt is usually not a bug with TrollFools itself, but rather with the target app. You may re-install that from App Store. You can’t use TrollFools with apps installed via “Asspp” or tweaks like “NoAppThinning”." = "Nessun framework eleggibile trovato.\n\nIt di solito non è un bug di TrollFools, ma con l'app. Dovresti reinstallarla dall'App Store. Non puoi usare TrollFools con app installate via “Asspp” o tweak come “NoAppThinning”."; 147 | 148 | /* No comment provided by engineer. */ 149 | "No Injected Plug-Ins" = "Non ci sono Plug-In iniettati"; 150 | 151 | /* No comment provided by engineer. */ 152 | "No valid plug-ins found." = "Nessun plug-in valido trovato."; 153 | 154 | /* TODO */ 155 | "Notice" = "Notice"; 156 | 157 | /* No comment provided by engineer. */ 158 | "Only removable system applications are eligible and listed." = "Solo le applicazioni di sistema removibili sono eleggibili e nella lista."; 159 | 160 | /* No comment provided by engineer. */ 161 | "Patched" = "Ho applicato la patch"; 162 | 163 | /* No comment provided by engineer. */ 164 | "Pinned Version" = "Versione Fissata"; 165 | 166 | /* No comment provided by engineer. */ 167 | "Plug-Ins" = "Plug-In"; 168 | 169 | /* No comment provided by engineer. */ 170 | "Post-order" = "Post-ordine"; 171 | 172 | /* No comment provided by engineer. */ 173 | "Pre-order" = "Pre-ordine"; 174 | 175 | /* No comment provided by engineer. */ 176 | "Prefer Main Executable" = "Preferisci Eseguibile Principale"; 177 | 178 | /* No comment provided by engineer. */ 179 | "Rebuild Icon Cache" = "Ricrostruisci Cache Icone"; 180 | 181 | /* No comment provided by engineer. */ 182 | "Record your phone calls like never before." = "Registra le tue chiamate come niente prima."; 183 | 184 | /* No comment provided by engineer. */ 185 | "Reveil" = "Rivela"; 186 | 187 | /* No comment provided by engineer. */ 188 | "Search Patched…" = "Cerca con Patch…"; 189 | 190 | /* No comment provided by engineer. */ 191 | "Search…" = "Cerca…"; 192 | 193 | /* TODO */ 194 | "Select an application to view details." = "Select an application to view details."; 195 | 196 | /* No comment provided by engineer. */ 197 | "Select Application to Inject" = "Scegli Applicazione da Iniettare"; 198 | 199 | /* No comment provided by engineer. */ 200 | "Show in Filza" = "Mostra in Filza"; 201 | 202 | /* No comment provided by engineer. */ 203 | "Show Patched Only" = "Mostra solo con patch"; 204 | 205 | /* No comment provided by engineer. */ 206 | "Some plug-ins were not injected by TrollFools, please eject them with caution." = "Alcuni plug-in non sono stati iniettati da TrollFools, espellili con cautela."; 207 | 208 | /* No comment provided by engineer. */ 209 | "Source Code" = "Codice Sorgente"; 210 | 211 | /* TODO */ 212 | "System" = "System"; 213 | 214 | /* No comment provided by engineer. */ 215 | "The content of text file “%@” is empty." = "Il contenuto del file “%@” è vuoto."; 216 | 217 | /* No comment provided by engineer. */ 218 | "TrollFools" = "TrollFools"; 219 | 220 | /* No comment provided by engineer. */ 221 | "TrollRecorder" = "TrollRecorder"; 222 | 223 | /* No comment provided by engineer. */ 224 | "TrollStore" = "TrollStore"; 225 | 226 | /* No comment provided by engineer. */ 227 | "TrollStore Applications" = "Applicazioni TrollStore"; 228 | 229 | /* TODO */ 230 | "Unable to locate the data archive in the Debian package." = "Unable to locate the data archive in the Debian package."; 231 | 232 | /* No comment provided by engineer. */ 233 | "Unlock Version" = "Sblocca Versione"; 234 | 235 | /* No comment provided by engineer. */ 236 | "Use Weak Reference" = "Usa referenza debole"; 237 | 238 | /* TODO */ 239 | "User" = "User"; 240 | 241 | /* No comment provided by engineer. */ 242 | "User Applications" = "Applicazioni"; 243 | 244 | /* No comment provided by engineer. */ 245 | "View Logs" = "Mostra Log"; 246 | 247 | /* No comment provided by engineer. */ 248 | "You need to rebuild the icon cache in TrollStore to apply changes." = "Devi ricostruire la cache delle icone in TrollStore per applicare le modifiche."; 249 | 250 | /* TODO */ 251 | "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."; 252 | -------------------------------------------------------------------------------- /TrollFools/ldid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/ldid -------------------------------------------------------------------------------- /TrollFools/libcrypto.3.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/libcrypto.3.dylib -------------------------------------------------------------------------------- /TrollFools/libintl.8.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/libintl.8.dylib -------------------------------------------------------------------------------- /TrollFools/libiosexec.1.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/libiosexec.1.dylib -------------------------------------------------------------------------------- /TrollFools/libxar.1.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/libxar.1.dylib -------------------------------------------------------------------------------- /TrollFools/mkdir: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/mkdir -------------------------------------------------------------------------------- /TrollFools/mv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/mv -------------------------------------------------------------------------------- /TrollFools/mv-15: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/mv-15 -------------------------------------------------------------------------------- /TrollFools/optool: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/optool -------------------------------------------------------------------------------- /TrollFools/rm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lessica/TrollFools/7589599f973a815151fe250621578022b1e59726/TrollFools/rm -------------------------------------------------------------------------------- /TrollFools/vi.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | "CFBundleDisplayName" = "TrollFools"; 2 | -------------------------------------------------------------------------------- /TrollFools/vi.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "%@ exited with code %d" = "%@ thoát với mã %d"; 3 | 4 | /* No comment provided by engineer. */ 5 | "%@ rows not loaded" = "%@ dòng chưa được tải."; 6 | 7 | /* No comment provided by engineer. */ 8 | "%@ terminated with signal %d" = "%@ đã kết thúc với tín hiệu %d"; 9 | 10 | /* TODO */ 11 | "%ld Plug-Ins of “%@”" = "%1$ld Plug-Ins of “%2$@”"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Advanced Settings" = "Cài đặt nâng cao"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Advertisement" = "Advertisement"; 18 | 19 | /* TODO */ 20 | "All" = "All"; 21 | 22 | /* TODO */ 23 | "All Applications" = "All Applications"; 24 | 25 | /* No comment provided by engineer. */ 26 | "And %d more unsupported user applications." = "Có %d ứng dụng người dùng không được hỗ trợ."; 27 | 28 | /* TODO */ 29 | "Bringing back the most advanced system and security analysis tool." = "Bringing back the most advanced system and security analysis tool."; 30 | 31 | /* No comment provided by engineer. */ 32 | "Cancel" = "Hủy"; 33 | 34 | /* No comment provided by engineer. */ 35 | "Cannot parse text with UTF-8 encoding: “%@”." = "Không thể phân tích văn bản với mã hóa UTF-8: “%@”."; 36 | 37 | /* No comment provided by engineer. */ 38 | "Completed" = "Hoàn thành"; 39 | 40 | /* No comment provided by engineer. */ 41 | "Confirm" = "Xác nhận"; 42 | 43 | /* TODO */ 44 | "Continue" = "Continue"; 45 | 46 | /* TODO */ 47 | "Continue and Don’t Show Again" = "Continue and Don’t Show Again"; 48 | 49 | /* No comment provided by engineer. */ 50 | "Copy" = "Sao chép"; 51 | 52 | /* No comment provided by engineer. */ 53 | "Copyright" = "Bản quyền"; 54 | 55 | /* No comment provided by engineer. */ 56 | "Do you want to clear this log file “%@”?" = "Bạn có muốn xóa tệp nhật ký này “%@”?"; 57 | 58 | /* No comment provided by engineer. */ 59 | "Done" = "Hoàn thành"; 60 | 61 | /* No comment provided by engineer. */ 62 | "Eject" = "Gỡ bỏ"; 63 | 64 | /* No comment provided by engineer. */ 65 | "Eject All" = "Gỡ bỏ tất cả"; 66 | 67 | /* No comment provided by engineer. */ 68 | "Error" = "Lỗi"; 69 | 70 | /* TODO */ 71 | "Export" = "Export"; 72 | 73 | /* TODO */ 74 | "Export All" = "Export All"; 75 | 76 | /* No comment provided by engineer. */ 77 | "Failed" = "Thất bại"; 78 | 79 | /* No comment provided by engineer. */ 80 | "Failed to find entry CFBundleExecutable in: %@" = "Không tìm thấy mục CFBundleExecutable trong: %@"; 81 | 82 | /* No comment provided by engineer. */ 83 | "Failed to find entry CFBundleIdentifier in: %@" = "Không tìm thấy mục CFBundleIdentifier trong: %@"; 84 | 85 | /* No comment provided by engineer. */ 86 | "Failed to locate main executable: %@" = "Không tìm thấy tệp thực thi chính: %@"; 87 | 88 | /* No comment provided by engineer. */ 89 | "Failed to parse: %@" = "Không thể phân tích: %@"; 90 | 91 | /* No comment provided by engineer. */ 92 | "Fast" = "Tiêm nhanh"; 93 | 94 | /* No comment provided by engineer. */ 95 | "Filza (URL Scheme) Not Installed" = "Filza (URL Scheme) chưa được cài đặt."; 96 | 97 | /* No comment provided by engineer. */ 98 | "Hide" = "Hide"; 99 | 100 | /* No comment provided by engineer. */ 101 | "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."; 102 | 103 | /* No comment provided by engineer. */ 104 | "Inject" = "Tiêm"; 105 | 106 | /* No comment provided by engineer. */ 107 | "Injectable System Applications" = "Ứng dụng hệ thống có thể tiêm"; 108 | 109 | /* No comment provided by engineer. */ 110 | "Injected Plug-Ins" = "Plug-in đã tiêm"; 111 | 112 | /* No comment provided by engineer. */ 113 | "Injecting" = "Đang tiêm"; 114 | 115 | /* No comment provided by engineer. */ 116 | "Injection" = "Nâng cao"; 117 | 118 | /* No comment provided by engineer. */ 119 | "Injection Strategy" = "Phương pháp tiêm"; 120 | 121 | /* No comment provided by engineer. */ 122 | "Launch" = "Mở ứng dụng"; 123 | 124 | /* No comment provided by engineer. */ 125 | "Lexicographic" = "Theo thứ tự từ điển"; 126 | 127 | /* No comment provided by engineer. */ 128 | "Lock Version" = "Khóa phiên bản"; 129 | 130 | /* No comment provided by engineer. */ 131 | "Log Viewer" = "Trình xem nhật ký"; 132 | 133 | /* No comment provided by engineer. */ 134 | "Made with ♥ by OwnGoal Studio" = "Tạo với ♥ bởi OwnGoal Studio."; 135 | 136 | /* No comment provided by engineer. */ 137 | "No Applications" = "No Applications"; 138 | 139 | /* TODO */ 140 | "No dylib found in the Debian package." = "No dylib found in the Debian package."; 141 | 142 | /* No comment provided by engineer. */ 143 | "No eligible framework found." = "Không tìm thấy framework đủ điều kiện."; 144 | 145 | /* No comment provided by engineer. */ 146 | "No eligible framework found.\n\nIt is usually not a bug with TrollFools itself, but rather with the target app. You may re-install that from App Store. You can’t use TrollFools with apps installed via “Asspp” or tweaks like “NoAppThinning”." = "Không tìm thấy framework hợp lệ.\n\nĐây thường không phải là lỗi của TrollFools, mà là lỗi của ứng dụng đang tiêm. Bạn có thể cài đặt lại ứng dụng đó từ App Store.\n\nBạn không thể sử dụng TrollFools với các ứng dụng được cài đặt qua “Asspp” hoặc các tinh chỉnh như “NoAppThinning”."; 147 | 148 | /* No comment provided by engineer. */ 149 | "No Injected Plug-Ins" = "Không có plug-in nào được tiêm"; 150 | 151 | /* No comment provided by engineer. */ 152 | "No valid plug-ins found." = "Không tìm thấy plug-in hợp lệ."; 153 | 154 | /* TODO */ 155 | "Notice" = "Notice"; 156 | 157 | /* No comment provided by engineer. */ 158 | "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ê."; 159 | 160 | /* No comment provided by engineer. */ 161 | "Patched" = "Đã sửa đổi"; 162 | 163 | /* No comment provided by engineer. */ 164 | "Pinned Version" = "Phiên bản ghim"; 165 | 166 | /* No comment provided by engineer. */ 167 | "Plug-Ins" = "Plug-Ins"; 168 | 169 | /* No comment provided by engineer. */ 170 | "Post-order" = "Theo thứ tự hậu thứ"; 171 | 172 | /* No comment provided by engineer. */ 173 | "Pre-order" = "Theo thứ tự tiền thứ"; 174 | 175 | /* No comment provided by engineer. */ 176 | "Prefer Main Executable" = "Ưu tiên tệp thực thi chính"; 177 | 178 | /* No comment provided by engineer. */ 179 | "Rebuild Icon Cache" = "Làm mới bộ nhớ đệm biểu tượng"; 180 | 181 | /* TODO */ 182 | "Record your phone calls like never before." = "Record your phone calls like never before."; 183 | 184 | /* No comment provided by engineer. */ 185 | "Reveil" = "Reveil"; 186 | 187 | /* No comment provided by engineer. */ 188 | "Search Patched…" = "Tìm kiếm sửa đổi…"; 189 | 190 | /* No comment provided by engineer. */ 191 | "Search…" = "Tìm kiếm…"; 192 | 193 | /* TODO */ 194 | "Select an application to view details." = "Select an application to view details."; 195 | 196 | /* No comment provided by engineer. */ 197 | "Select Application to Inject" = "Chọn ứng dụng để tiêm"; 198 | 199 | /* No comment provided by engineer. */ 200 | "Show in Filza" = "Hiển thị trong Filza"; 201 | 202 | /* No comment provided by engineer. */ 203 | "Show Patched Only" = "Chỉ hiển thị đã sửa đổi"; 204 | 205 | /* No comment provided by engineer. */ 206 | "Some plug-ins were not injected by TrollFools, please eject them with caution." = "Một số plug-in không được tiêm bởi TrollFools, vui lòng loại bỏ chúng một cách cẩn thận."; 207 | 208 | /* No comment provided by engineer. */ 209 | "Source Code" = "Mã nguồn"; 210 | 211 | /* TODO */ 212 | "System" = "System"; 213 | 214 | /* No comment provided by engineer. */ 215 | "The content of text file “%@” is empty." = "Nội dung của tệp văn bản “%@” trống."; 216 | 217 | /* No comment provided by engineer. */ 218 | "TrollFools" = "TrollFools"; 219 | 220 | /* No comment provided by engineer. */ 221 | "TrollRecorder" = "TrollRecorder"; 222 | 223 | /* No comment provided by engineer. */ 224 | "TrollStore" = "TrollStore"; 225 | 226 | /* No comment provided by engineer. */ 227 | "TrollStore Applications" = "Ứng dụng TrollStore"; 228 | 229 | /* TODO */ 230 | "Unable to locate the data archive in the Debian package." = "Unable to locate the data archive in the Debian package."; 231 | 232 | /* No comment provided by engineer. */ 233 | "Unlock Version" = "Mở khóa phiên bản"; 234 | 235 | /* No comment provided by engineer. */ 236 | "Use Weak Reference" = "Sử dụng tham chiếu yếu"; 237 | 238 | /* TODO */ 239 | "User" = "User"; 240 | 241 | /* No comment provided by engineer. */ 242 | "User Applications" = "Ứng dụng người dùng"; 243 | 244 | /* No comment provided by engineer. */ 245 | "View Logs" = "Xem nhật ký"; 246 | 247 | /* No comment provided by engineer. */ 248 | "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."; 249 | 250 | /* TODO */ 251 | "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."; 252 | -------------------------------------------------------------------------------- /TrollFools/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | "CFBundleDisplayName" = "巨魔注入器"; 2 | -------------------------------------------------------------------------------- /TrollFools/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "%@ exited with code %d" = "%@ 意外退出,返回值 %d"; 3 | 4 | /* StripedTextTableViewController */ 5 | "%@ rows not loaded" = "%@ 行未加载"; 6 | 7 | /* No comment provided by engineer. */ 8 | "%@ terminated with signal %d" = "%@ 意外终止,未捕获的信号 %d"; 9 | 10 | /* No comment provided by engineer. */ 11 | "%ld Plug-Ins of “%@”" = "“%2$@” 的 %1$ld 个插件"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Advanced Settings" = "高级选项"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Advertisement" = "广告"; 18 | 19 | /* No comment provided by engineer. */ 20 | "All" = "所有应用"; 21 | 22 | /* No comment provided by engineer. */ 23 | "All Applications" = "所有应用"; 24 | 25 | /* No comment provided by engineer. */ 26 | "And %d more unsupported user applications." = "另有 %d 个不支持的用户应用。"; 27 | 28 | /* No comment provided by engineer. */ 29 | "Bringing back the most advanced system and security analysis tool." = "最强大的系统和安全分析工具,再次归来。"; 30 | 31 | /* StripedTextTableViewController */ 32 | "Cancel" = "取消"; 33 | 34 | /* No comment provided by engineer. */ 35 | "Cannot parse text with UTF-8 encoding: “%@”." = "无法使用 UTF-8 编码解析文本:“%@”。"; 36 | 37 | /* No comment provided by engineer. */ 38 | "Completed" = "已完成"; 39 | 40 | /* StripedTextTableViewController */ 41 | "Confirm" = "确认"; 42 | 43 | /* No comment provided by engineer. */ 44 | "Continue" = "继续"; 45 | 46 | /* No comment provided by engineer. */ 47 | "Continue and Don’t Show Again" = "继续且不再提示"; 48 | 49 | /* StripedTextTableViewController */ 50 | "Copy" = "拷贝"; 51 | 52 | /* No comment provided by engineer. */ 53 | "Copyright" = "版权所有"; 54 | 55 | /* StripedTextTableViewController */ 56 | "Do you want to clear this log file “%@”?" = "你确定要清空日志文件 “%@” 吗?"; 57 | 58 | /* No comment provided by engineer. */ 59 | "Done" = "完成"; 60 | 61 | /* No comment provided by engineer. */ 62 | "Eject" = "推出"; 63 | 64 | /* No comment provided by engineer. */ 65 | "Eject All" = "全部推出"; 66 | 67 | /* No comment provided by engineer. */ 68 | "Error" = "错误"; 69 | 70 | /* No comment provided by engineer. */ 71 | "Export" = "导出"; 72 | 73 | /* No comment provided by engineer. */ 74 | "Export All" = "全部导出"; 75 | 76 | /* No comment provided by engineer. */ 77 | "Failed" = "失败"; 78 | 79 | /* No comment provided by engineer. */ 80 | "Failed to find entry CFBundleExecutable in: %@" = "找不到 %@ 中的 CFBundleExecutable。"; 81 | 82 | /* No comment provided by engineer. */ 83 | "Failed to find entry CFBundleIdentifier in: %@" = "找不到 %@ 中的 CFBundleIdentifier"; 84 | 85 | /* No comment provided by engineer. */ 86 | "Failed to locate main executable: %@" = "找不到主要可执行文件:%@"; 87 | 88 | /* No comment provided by engineer. */ 89 | "Failed to parse: %@" = "无法解析:%@"; 90 | 91 | /* No comment provided by engineer. */ 92 | "Fast" = "快速"; 93 | 94 | /* No comment provided by engineer. */ 95 | "Filza (URL Scheme) Not Installed" = "未安装支持 URL Scheme 的 Filza"; 96 | 97 | /* No comment provided by engineer. */ 98 | "Hide" = "Hide"; 99 | 100 | /* No comment provided by engineer. */ 101 | "If you do not know what these options mean, please do not change them." = "如果你不知道这些选项的含义,请不要更改。"; 102 | 103 | /* No comment provided by engineer. */ 104 | "Inject" = "注入"; 105 | 106 | /* No comment provided by engineer. */ 107 | "Injectable System Applications" = "可注入的系统应用"; 108 | 109 | /* No comment provided by engineer. */ 110 | "Injected Plug-Ins" = "已注入的插件"; 111 | 112 | /* No comment provided by engineer. */ 113 | "Injecting" = "注入中"; 114 | 115 | /* No comment provided by engineer. */ 116 | "Injection" = "注入"; 117 | 118 | /* No comment provided by engineer. */ 119 | "Injection Strategy" = "注入策略"; 120 | 121 | /* No comment provided by engineer. */ 122 | "Launch" = "启动"; 123 | 124 | /* No comment provided by engineer. */ 125 | "Lexicographic" = "字典序"; 126 | 127 | /* No comment provided by engineer. */ 128 | "Lock Version" = "锁定版本"; 129 | 130 | /* StripedTextTableViewController */ 131 | "Log Viewer" = "日志查看器"; 132 | 133 | /* No comment provided by engineer. */ 134 | "Made with ♥ by OwnGoal Studio" = "「乌龙工作室」倾情献制"; 135 | 136 | /* No comment provided by engineer. */ 137 | "No Applications" = "没有找到符合条件的应用程序"; 138 | 139 | /* No comment provided by engineer. */ 140 | "No dylib found in the Debian package." = "在 Debian 软件包中找不到可以注入的插件。"; 141 | 142 | /* No comment provided by engineer. */ 143 | "No eligible framework found." = "没有找到符合条件的框架。"; 144 | 145 | /* No comment provided by engineer. */ 146 | "No eligible framework found.\n\nIt is usually not a bug with TrollFools itself, but rather with the target app. You may re-install that from App Store. You can’t use TrollFools with apps installed via “Asspp” or tweaks like “NoAppThinning”." = "没有找到符合条件的框架。\n\n通常情况下,这不是 TrollFools 自身的问题,而是目标应用程序的问题。你可能需要从 App Store 重新安装该应用。你不能在通过 “爱啪思道” 或 “NoAppThinning” 等方式安装的应用程序上使用 TrollFools。"; 147 | 148 | /* No comment provided by engineer. */ 149 | "No Injected Plug-Ins" = "没有已注入的插件"; 150 | 151 | /* No comment provided by engineer. */ 152 | "No valid plug-ins found." = "没有找到有效的插件。"; 153 | 154 | /* No comment provided by engineer. */ 155 | "Notice" = "提示"; 156 | 157 | /* No comment provided by engineer. */ 158 | "Only removable system applications are eligible and listed." = "仅列出可移除的系统应用,其他系统应用不支持注入。"; 159 | 160 | /* No comment provided by engineer. */ 161 | "Patched" = "已注入"; 162 | 163 | /* No comment provided by engineer. */ 164 | "Pinned Version" = "已固定版本"; 165 | 166 | /* No comment provided by engineer. */ 167 | "Plug-Ins" = "插件列表"; 168 | 169 | /* No comment provided by engineer. */ 170 | "Post-order" = "后置"; 171 | 172 | /* No comment provided by engineer. */ 173 | "Pre-order" = "前置"; 174 | 175 | /* No comment provided by engineer. */ 176 | "Prefer Main Executable" = "优先处理主要可执行文件"; 177 | 178 | /* No comment provided by engineer. */ 179 | "Rebuild Icon Cache" = "重建图标缓存"; 180 | 181 | /* No comment provided by engineer. */ 182 | "Record your phone calls like never before." = "通话录音,从未如此简单。"; 183 | 184 | /* No comment provided by engineer. */ 185 | "Reveil" = "Reveil"; 186 | 187 | /* No comment provided by engineer. */ 188 | "Search Patched…" = "搜索 已注入…"; 189 | 190 | /* No comment provided by engineer. */ 191 | "Search…" = "搜索…"; 192 | 193 | /* No comment provided by engineer. */ 194 | "Select an application to view details." = "选择一个应用程序以查看详情。"; 195 | 196 | /* No comment provided by engineer. */ 197 | "Select Application to Inject" = "选择应用程序进行注入"; 198 | 199 | /* No comment provided by engineer. */ 200 | "Show in Filza" = "在 Filza 中显示"; 201 | 202 | /* No comment provided by engineer. */ 203 | "Show Patched Only" = "仅显示已注入的"; 204 | 205 | /* No comment provided by engineer. */ 206 | "Some plug-ins were not injected by TrollFools, please eject them with caution." = "部分插件可能并非由 TrollFools 注入,移除它们可能会造成应用程序异常,请谨慎操作。"; 207 | 208 | /* No comment provided by engineer. */ 209 | "Source Code" = "获取源代码"; 210 | 211 | /* No comment provided by engineer. */ 212 | "System" = "系统应用"; 213 | 214 | /* No comment provided by engineer. */ 215 | "The content of text file “%@” is empty." = "文本文件 “%@” 的内容为空。"; 216 | 217 | /* No comment provided by engineer. */ 218 | "TrollFools" = "巨魔注入器"; 219 | 220 | /* No comment provided by engineer. */ 221 | "TrollRecorder" = "巨魔录音机"; 222 | 223 | /* No comment provided by engineer. */ 224 | "TrollStore" = "巨魔应用"; 225 | 226 | /* No comment provided by engineer. */ 227 | "TrollStore Applications" = "巨魔应用"; 228 | 229 | /* No comment provided by engineer. */ 230 | "Unable to locate the data archive in the Debian package." = "无法在 Debian 软件包中找到数据归档。"; 231 | 232 | /* No comment provided by engineer. */ 233 | "Unlock Version" = "解锁版本"; 234 | 235 | /* No comment provided by engineer. */ 236 | "Use Weak Reference" = "创建弱引用"; 237 | 238 | /* No comment provided by engineer. */ 239 | "User" = "用户应用"; 240 | 241 | /* No comment provided by engineer. */ 242 | "User Applications" = "用户应用"; 243 | 244 | /* No comment provided by engineer. */ 245 | "View Logs" = "查看日志"; 246 | 247 | /* No comment provided by engineer. */ 248 | "You need to rebuild the icon cache in TrollStore to apply changes." = "你需要在 TrollStore 中重建图标缓存以应用更改。"; 249 | 250 | /* No comment provided by engineer. */ 251 | "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 软件包 “%@”。请留意,它通常不会像在越狱环境当中那样工作,甚至完全不起作用。请确保你知道自己在做些什么。"; 252 | -------------------------------------------------------------------------------- /TrollFoolsTweak/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS := arm64 arm64e 2 | TARGET := iphone:clang:latest:14.0 3 | 4 | include $(THEOS)/makefiles/common.mk 5 | 6 | TWEAK_NAME = TrollFoolsTweak 7 | 8 | TrollFoolsTweak_FILES += TrollFoolsTweak.x 9 | TrollFoolsTweak_CFLAGS += -fobjc-arc 10 | 11 | include $(THEOS_MAKE_PATH)/tweak.mk -------------------------------------------------------------------------------- /TrollFoolsTweak/TrollFoolsTweak.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "wiki.qaq.TrollFools" ); }; } 2 | -------------------------------------------------------------------------------- /TrollFoolsTweak/TrollFoolsTweak.x: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FLEXManager : NSObject 4 | + (instancetype)sharedManager; 5 | - (void)showExplorer; 6 | @end 7 | 8 | %hook UIViewController 9 | 10 | - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event 11 | { 12 | if (motion == UIEventSubtypeMotionShake) { 13 | [[%c(FLEXManager) sharedManager] showExplorer]; 14 | } 15 | } 16 | 17 | %end 18 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: wiki.qaq.trollfools 2 | Name: TrollFools 3 | Version: 3.7-42 4 | Section: Applications 5 | Depends: firmware (>= 14.0) 6 | Architecture: iphoneos-arm 7 | Author: Lessica <82flex@gmail.com> 8 | Maintainer: Lessica <82flex@gmail.com> 9 | Description: Give me 108 yuan. 10 | -------------------------------------------------------------------------------- /devkit/bump-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is designed to increment the build number consistently across all 4 | # targets. 5 | 6 | # Usage: bump-version.sh 7 | # Example: bump-version.sh 1.0 8 | 9 | # Usage: DEBUG=1 bump-version.sh 10 | # Example: DEBUG=1 bump-version.sh 1.0 11 | 12 | set -e 13 | cd "$(dirname "$0")" 14 | 15 | if [ $# -ne 1 ]; then 16 | echo "Usage: $0 " 17 | exit 1 18 | fi 19 | 20 | VERSION=$1 21 | 22 | if [ ! -z "$DEBUG" ]; then 23 | 24 | # Navigating to the 'carbonwatchuk' directory inside the source root. 25 | XCCONFIG_NAME=../TrollFools/Version.Debug.xcconfig 26 | if [ ! -f $XCCONFIG_NAME ]; then 27 | echo "Versioning configuration not found!" 28 | exit 1 29 | fi 30 | 31 | # Get the current date in the format "YYYYMMDD". 32 | current_date=$(date "+%Y%m%d") 33 | 34 | # Parse the 'Config.xcconfig' file to retrieve the previous build number. 35 | # The 'awk' command is used to find the line containing "BUILD_NUMBER" 36 | # and the 'tr' command is used to remove any spaces. 37 | previous_build_number=$(awk -F "=" '/DEBUG_BUILD_NUMBER/ {print $2}' $XCCONFIG_NAME | tr -d ' ') 38 | 39 | # Extract the date part and the counter part from the previous build number. 40 | previous_date="${previous_build_number:0:8}" 41 | counter="${previous_build_number:8}" 42 | 43 | # If the current date matches the date from the previous build number, 44 | # increment the counter. Otherwise, reset the counter to 1. 45 | new_counter=$((current_date == previous_date ? counter + 1 : 1)) 46 | 47 | # Combine the current date and the new counter to create the new build number. 48 | new_build_number="${current_date}${new_counter}" 49 | 50 | # Use 'sed' command to replace the previous build number with the new build 51 | # number in the 'Config.xcconfig' file. 52 | sed -i -e "/DEBUG_VERSION =/ s/= .*/= $VERSION/" $XCCONFIG_NAME 53 | sed -i -e "/DEBUG_BUILD_NUMBER =/ s/= .*/= $new_build_number/" $XCCONFIG_NAME 54 | 55 | # Remove the backup file created by 'sed' command. 56 | rm -f $XCCONFIG_NAME-e 57 | 58 | else 59 | 60 | XCCONFIG_NAME=../TrollFools/Version.xcconfig 61 | if [ ! -f $XCCONFIG_NAME ]; then 62 | echo "Versioning configuration not found!" 63 | exit 1 64 | fi 65 | 66 | previous_build_number=$(awk -F "=" '/BUILD_NUMBER/ {print $2}' $XCCONFIG_NAME | tr -d ' ') 67 | 68 | new_build_number=$((previous_build_number + 1)) 69 | 70 | sed -i -e "/VERSION =/ s/= .*/= $VERSION/" $XCCONFIG_NAME 71 | sed -i -e "/BUILD_NUMBER =/ s/= .*/= $new_build_number/" $XCCONFIG_NAME 72 | 73 | rm -f $XCCONFIG_NAME-e 74 | 75 | fi 76 | 77 | # Write the control file 78 | cat > ../control << __EOF__ 79 | Package: wiki.qaq.trollfools 80 | Name: TrollFools 81 | Version: $VERSION-$new_build_number 82 | Section: Applications 83 | Depends: firmware (>= 14.0) 84 | Architecture: iphoneos-arm 85 | Author: Lessica <82flex@gmail.com> 86 | Maintainer: Lessica <82flex@gmail.com> 87 | Description: Give me 108 yuan. 88 | __EOF__ 89 | 90 | # Set permissions 91 | chmod 0644 ../control 92 | -------------------------------------------------------------------------------- /devkit/print-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | cd "$(dirname "$0")" 5 | 6 | if [ ! -z "$DEBUG" ]; then 7 | XCCONFIG_NAME=../TrollFools/Version.Debug.xcconfig 8 | if [ ! -f $XCCONFIG_NAME ]; then 9 | echo "Versioning configuration not found!" 10 | exit 1 11 | fi 12 | previous_version=$(awk -F "=" '/DEBUG_VERSION/ {print $2}' $XCCONFIG_NAME | tr -d ' ') 13 | previous_build_number=$(awk -F "=" '/DEBUG_BUILD_NUMBER/ {print $2}' $XCCONFIG_NAME | tr -d ' ') 14 | else 15 | XCCONFIG_NAME=../TrollFools/Version.xcconfig 16 | if [ ! -f $XCCONFIG_NAME ]; then 17 | echo "Versioning configuration not found!" 18 | exit 1 19 | fi 20 | previous_version=$(awk -F "=" '/VERSION/ {print $2}' $XCCONFIG_NAME | tr -d ' ') 21 | previous_build_number=$(awk -F "=" '/BUILD_NUMBER/ {print $2}' $XCCONFIG_NAME | tr -d ' ') 22 | fi 23 | 24 | echo "$previous_version ($previous_build_number)" -------------------------------------------------------------------------------- /devkit/rootless.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export THEOS=$HOME/theos 4 | export THEOS_PACKAGE_SCHEME=rootless 5 | export THEOS_DEVICE_IP=127.0.0.1 6 | export THEOS_DEVICE_PORT=58422 7 | -------------------------------------------------------------------------------- /devkit/standardize-entitlements.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd $(dirname $0)/.. 4 | 5 | plutil -convert xml1 TrollFools/TrollFools.entitlements 6 | -------------------------------------------------------------------------------- /devkit/tipa.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | XCCONFIG_NAME=TrollFools/Version.xcconfig 4 | VERSION=$(awk -F "=" '/VERSION/ {print $2}' $XCCONFIG_NAME | tr -d ' ') 5 | BUILD_NUMBER=$(awk -F "=" '/BUILD_NUMBER/ {print $2}' $XCCONFIG_NAME | tr -d ' ') 6 | 7 | mkdir -p packages $THEOS_STAGING_DIR/Payload 8 | cp -rp $THEOS_STAGING_DIR$THEOS_PACKAGE_INSTALL_PREFIX/Applications/TrollFools.app $THEOS_STAGING_DIR/Payload 9 | chmod 0644 $THEOS_STAGING_DIR/Payload/TrollFools.app/Info.plist 10 | 11 | cd $THEOS_STAGING_DIR 12 | # 7z a -tzip TrollFools_$VERSION-$BUILD_NUMBER.tipa Payload 13 | zip -qr TrollFools_$VERSION-$BUILD_NUMBER.tipa Payload 14 | cd - 15 | 16 | cp -p $THEOS_STAGING_DIR/TrollFools_$VERSION-$BUILD_NUMBER.tipa packages 17 | --------------------------------------------------------------------------------