├── .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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------