├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── dependabot.yml ├── scripts │ └── telegram_url.py └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── apd ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs └── src │ ├── apd.rs │ ├── assets.rs │ ├── banner │ ├── cli.rs │ ├── defs.rs │ ├── event.rs │ ├── installer.sh │ ├── installer_bind.sh │ ├── m_mount.rs │ ├── main.rs │ ├── module.rs │ ├── mount.rs │ ├── package.rs │ ├── pty.rs │ ├── restorecon.rs │ ├── supercall.rs │ └── utils.rs ├── app ├── .gitignore ├── build.gradle.kts ├── libs │ └── arm64-v8a │ │ ├── .gitignore │ │ ├── libbootctl.so │ │ ├── libbusybox.so │ │ ├── libmagiskboot.so │ │ ├── libmagiskpolicy.so │ │ └── libresetprop.so ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── me │ │ └── bmax │ │ └── apatch │ │ └── IAPRootService.aidl │ ├── assets │ ├── .gitignore │ ├── InstallAP.sh │ ├── UninstallAP.sh │ ├── boot_extract.sh │ ├── boot_patch.sh │ ├── boot_unpatch.sh │ └── util_functions.sh │ ├── cpp │ ├── CMakeLists.txt │ ├── apjni.cpp │ ├── apjni.hpp │ ├── supercall.h │ ├── uapi │ │ └── scdefs.h │ └── version │ ├── ic_launcher-playstore.png │ ├── java │ └── me │ │ └── bmax │ │ └── apatch │ │ ├── APatchApp.kt │ │ ├── Natives.kt │ │ ├── services │ │ └── RootServices.java │ │ ├── ui │ │ ├── CrashHandleActivity.kt │ │ ├── MainActivity.kt │ │ ├── WebUIActivity.kt │ │ ├── component │ │ │ ├── Dialog.kt │ │ │ ├── DropdownMenu.kt │ │ │ ├── KeyEventBlocker.kt │ │ │ ├── ModuleCardComponents.kt │ │ │ ├── SearchBar.kt │ │ │ └── SettingsItem.kt │ │ ├── screen │ │ │ ├── APM.kt │ │ │ ├── AboutScreen.kt │ │ │ ├── BottomBarDestination.kt │ │ │ ├── ExecuteAPMAction.kt │ │ │ ├── Home.kt │ │ │ ├── Install.kt │ │ │ ├── InstallModeSelect.kt │ │ │ ├── KPM.kt │ │ │ ├── Patches.kt │ │ │ ├── Settings.kt │ │ │ └── SuperUser.kt │ │ ├── theme │ │ │ ├── AmberTheme.kt │ │ │ ├── BlueGreyTheme.kt │ │ │ ├── BlueTheme.kt │ │ │ ├── BrownTheme.kt │ │ │ ├── CyanTheme.kt │ │ │ ├── DeepOrangeTheme.kt │ │ │ ├── DeepPurpleTheme.kt │ │ │ ├── GreenTheme.kt │ │ │ ├── IndigoTheme.kt │ │ │ ├── LightBlueTheme.kt │ │ │ ├── LightGreenTheme.kt │ │ │ ├── LimeTheme.kt │ │ │ ├── OrangeTheme.kt │ │ │ ├── PinkTheme.kt │ │ │ ├── PurpleTheme.kt │ │ │ ├── RedTheme.kt │ │ │ ├── SakuraTheme.kt │ │ │ ├── TealTheme.kt │ │ │ ├── Theme.kt │ │ │ ├── Type.kt │ │ │ └── YellowTheme.kt │ │ ├── viewmodel │ │ │ ├── APModuleViewModel.kt │ │ │ ├── KPModel.kt │ │ │ ├── KPModuleViewModel.kt │ │ │ ├── PatchesViewModel.kt │ │ │ └── SuperUserViewModel.kt │ │ └── webui │ │ │ ├── MimeUtil.java │ │ │ ├── SuFilePathHandler.java │ │ │ └── WebViewInterface.kt │ │ └── util │ │ ├── APatchCli.kt │ │ ├── APatchKeyHelper.java │ │ ├── DeviceInfoUtils.kt │ │ ├── Downloader.kt │ │ ├── HanziToPinyin.java │ │ ├── IOStreamUtils.kt │ │ ├── LatestVersionInfo.kt │ │ ├── LogEvent.kt │ │ ├── PkgConfig.kt │ │ ├── Version.kt │ │ ├── apksign │ │ ├── ApkSignerV2.java │ │ ├── ByteArrayStream.java │ │ ├── JarMap.java │ │ ├── SignApk.java │ │ └── ZipUtils.java │ │ ├── hideapk │ │ ├── AXML.kt │ │ ├── HideAPK.kt │ │ └── Keygen.kt │ │ └── ui │ │ ├── APDialogBlurBehindUtils.kt │ │ ├── CompositionProvider.kt │ │ ├── HyperlinkText.kt │ │ └── NavigationBarsSpacer.kt │ └── res │ ├── drawable │ ├── device_mobile_down.xml │ ├── github.xml │ ├── ic_launcher_background.xml │ ├── ic_launcher_foreground.xml │ ├── ic_launcher_monochrome.xml │ ├── info_circle_filled.xml │ ├── launcher_splash.xml │ ├── package_import.xml │ ├── settings.xml │ ├── telegram.xml │ ├── trash.xml │ └── weblate.xml │ ├── mipmap-anydpi │ └── ic_launcher.xml │ ├── resources.properties │ ├── values-ar │ └── strings.xml │ ├── values-arq │ └── strings.xml │ ├── values-az │ └── strings.xml │ ├── values-bn │ └── strings.xml │ ├── values-ca │ └── strings.xml │ ├── values-cs │ └── strings.xml │ ├── values-de │ └── strings.xml │ ├── values-el │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fa │ └── strings.xml │ ├── values-fil │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-gl │ └── strings.xml │ ├── values-hr │ └── strings.xml │ ├── values-hu │ └── strings.xml │ ├── values-in │ └── strings.xml │ ├── values-it │ └── strings.xml │ ├── values-ja │ └── strings.xml │ ├── values-jv │ └── strings.xml │ ├── values-ko │ └── strings.xml │ ├── values-lt │ └── strings.xml │ ├── values-ms │ └── strings.xml │ ├── values-nb-rNO │ └── strings.xml │ ├── values-night │ └── themes.xml │ ├── values-nl │ └── strings.xml │ ├── values-pl │ └── strings.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-pt │ └── strings.xml │ ├── values-ro │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-si │ └── strings.xml │ ├── values-sv │ └── strings.xml │ ├── values-th │ └── strings.xml │ ├── values-tr │ └── strings.xml │ ├── values-uk │ └── strings.xml │ ├── values-vi │ └── strings.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ ├── backup_rules.xml │ ├── data_extraction_rules.xml │ ├── file_paths.xml │ └── network_security_config.xml ├── build.gradle.kts ├── docs ├── az │ └── faq_az.md ├── cn │ ├── ap_module.md │ └── faq_cn.md ├── cn_tw │ └── faq_cn_tw.md ├── de │ └── faq_de.md ├── en │ └── faq.md ├── es │ └── faq_es.md ├── fr │ └── faq_fr.md ├── id │ └── faq.md ├── it │ └── faq_it.md ├── kr │ └── faq_kr.md ├── pt_br │ └── faq_pt_br.md ├── ru │ ├── .gitkeep │ └── faq_ru.md ├── tr │ └── faq_tr.md └── uk │ └── faq_uk.md ├── fastlane └── metadata │ └── android │ └── en-US │ ├── full_description.txt │ ├── images │ └── icon.png │ └── short_description.txt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── scripts ├── update_binary.sh └── update_script.sh └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text eol=lf 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | # *.c text 7 | # *.h text 8 | 9 | # Declare files that will always have CRLF line endings on checkout. 10 | *.cmd text eol=crlf 11 | *.bat text eol=crlf 12 | 13 | # Denote all files that are truly binary and should not be modified. 14 | tools/** binary 15 | *.jar binary 16 | *.exe binary 17 | *.apk binary 18 | *.png binary 19 | *.jpg binary 20 | *.ttf binary 21 | *.so binary 22 | 23 | # Help GitHub detect languages 24 | native/jni/external/** linguist-vendored 25 | native/jni/systemproperties/** linguist-language=C++ 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report/反馈 Bug 2 | description: Report errors or unexpected behavior./报告错误或未预料的行为 3 | labels: [bug] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for reporting issues of APatch! 10 | 11 | To make it easier for us to help you please enter detailed information below. 12 | 13 | 感谢给 APatch 汇报问题! 14 | 为了使我们更好地帮助你,请提供以下信息。 15 | 为了防止重复汇报,标题请务必使用英文。 16 | 17 | - type: checkboxes 18 | attributes: 19 | label: Please check before submitting an issue/在提交 issue 前请检查 20 | options: 21 | - label: I have searched the issues and haven't found anything relevant/我已经搜索了 issues 列表,没有发现于本问题相关内容 22 | required: true 23 | 24 | - label: If patch failed, root failed, or device unable to boot after flashing the new boot.img. Please goto KernelPatch/修复失败或刷入修补后镜像不能启动,请前往 KernelPatch 提问 25 | required: true 26 | 27 | - label: I will upload bugreport file in APatch Manager - Settings - Report log/我会上传 bureport 文件从 APatch 管理器 - 设置 - 发送日志 28 | required: true 29 | 30 | - label: I know how to reproduce the issue which may not be specific to my device/我知道如何重新复现这个问题 31 | required: false 32 | 33 | - type: checkboxes 34 | id: latest 35 | attributes: 36 | label: Version requirement/版本要求 37 | options: 38 | - label: I am using latest CI version of APatch/我正在使用最新 CI 版本 39 | required: true 40 | 41 | - type: textarea 42 | attributes: 43 | label: Describe the bug/描述 bug 44 | description: A clear and concise description of what the bug is/对 bug 的清晰简洁的描述 45 | validations: 46 | required: true 47 | 48 | - type: textarea 49 | attributes: 50 | label: Reproduce method/复现方法 51 | description: Steps to reproduce the behaviour/复现的步骤 52 | placeholder: | 53 | - 1. Go to '...' 54 | - 2. Click on '....' 55 | - 3. Scroll down to '....' 56 | - 4. See error 57 | validations: 58 | required: true 59 | 60 | - type: textarea 61 | attributes: 62 | label: Expected behavior/预期行为 63 | description: A clear and concise description of what you expected to happen./对你期望发生的行为进行清晰简洁的描述 64 | validations: 65 | required: true 66 | 67 | - type: textarea 68 | attributes: 69 | label: Actual behaviour /实际行为 70 | placeholder: Tell us what happens instead/告诉我们实际发生了什么 71 | validations: 72 | required: true 73 | 74 | - type: textarea 75 | attributes: 76 | label: Screenshots/截图 77 | description: If applicable, add screenshots to help explain your problem./如果可以的话,添加截图可以帮你解释问题 78 | 79 | - type: textarea 80 | attributes: 81 | label: Logs/日志 82 | description: If applicable, add crash or any other logs to help us figure out the problem./如果可以的话,添加崩溃日志可以帮助我们找到问题 83 | 84 | - type: input 85 | attributes: 86 | label: Device Name/设备名称 87 | validations: 88 | required: true 89 | 90 | - type: input 91 | attributes: 92 | label: OS Version/系统版本 93 | validations: 94 | required: true 95 | 96 | - type: input 97 | attributes: 98 | label: APatch Version/APatch 版本 99 | validations: 100 | required: true 101 | 102 | - type: input 103 | attributes: 104 | label: Kernel Version/内核版本 105 | validations: 106 | required: true 107 | 108 | - type: input 109 | attributes: 110 | label: KernelPatch Version/KernelPatch 版本 111 | validations: 112 | required: true 113 | 114 | - type: textarea 115 | attributes: 116 | label: Additional context/其他信息 117 | description: Add any other context about the problem here./添加关于问题的任何信息。 118 | placeholder: Upload logs zip by clicking the bar on the bottom. Upload logs to other websites or using external links is prohibited./点击文本框底栏上传日志压缩包,禁止上传到其它网站或使用外链提供日志。 119 | validations: 120 | required: true 121 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question/提问 4 | url: https://github.com/bmax121/APatch/discussions/new?category=Q-A 5 | about: Please ask and answer questions here./如果有任何疑问请在这里提问 6 | - name: Official Telegram Channel/官方 Telegram 频道 7 | url: https://t.me/APatchChannel 8 | about: Subscribe for notifications and releases/可以订阅通知和发行版 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request/新特性请求 3 | description: Suggest an idea for this project./提出建议 4 | labels: [enhancement] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Is your feature request related to a problem?/你的请求是否与某个问题相关? 9 | placeholder: A clear and concise description of what the problem is./请清晰准确表述该问题。 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: Describe the solution you'd like/描述你想要的解决方案 15 | placeholder: A clear and concise description of what you want to happen./请清晰准确描述新特性的预期行为 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Describe alternatives you've considered/描述您考虑过的备选方案 21 | placeholder: A clear and concise description of any alternative solutions or features you've considered./对您考虑过的任何替代解决方案或功能的清晰简洁的描述。 22 | validations: 23 | required: true 24 | - type: textarea 25 | attributes: 26 | label: Additional context/其他信息 27 | placeholder: Add any other context or screenshots about the feature request here./其他关于新特性的信息或者截图。 28 | validations: 29 | required: false -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | target-branch: main 8 | registries: 9 | - maven-google 10 | - gradle-plugin 11 | groups: 12 | maven-dependencies: 13 | patterns: 14 | - "*" 15 | 16 | - package-ecosystem: github-actions 17 | target-branch: main 18 | directory: / 19 | schedule: 20 | interval: daily 21 | groups: 22 | action-dependencies: 23 | patterns: 24 | - "*" 25 | 26 | - package-ecosystem: cargo 27 | target-branch: main 28 | directory: apd/ 29 | schedule: 30 | interval: daily 31 | allow: 32 | - dependency-type: "all" 33 | groups: 34 | rust-dependencies: 35 | patterns: 36 | - "*" 37 | 38 | registries: 39 | maven-google: 40 | type: maven-repository 41 | url: "https://dl.google.com/dl/android/maven2/" 42 | gradle-plugin: 43 | type: maven-repository 44 | url: "https://plugins.gradle.org/m2/" 45 | -------------------------------------------------------------------------------- /.github/scripts/telegram_url.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import sys 4 | import urllib.parse 5 | 6 | 7 | url = f'https://api.telegram.org/bot{os.environ["BOT_TOKEN"]}' 8 | url += f'/sendMediaGroup?chat_id={urllib.parse.quote(sys.argv[1])}&media=' 9 | 10 | # https://core.telegram.org/bots/api#markdownv2-style 11 | msg = os.environ["COMMIT_MESSAGE"] 12 | for c in ['\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']: 13 | msg = msg.replace(c, f'\\{c}') 14 | commit_url = os.environ["COMMIT_URL"] 15 | commit_id = os.environ["COMMIT_ID"][:7] 16 | 17 | caption = f"[{commit_id}]({commit_url})\n{msg}"[:1024] 18 | 19 | data = json.dumps([ 20 | {"type": "document", "media": "attach://Release","caption": caption,"parse_mode":"MarkdownV2"} 21 | ]) 22 | 23 | url += urllib.parse.quote(data) 24 | print(url) -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Manager 2 | 3 | on: 4 | push: 5 | tags: [ "*" ] 6 | branches: [ "main" ] 7 | paths: 8 | - '.github/workflows/build.yml' 9 | - 'app/**' 10 | - 'apd/**' 11 | - 'build.gradle.kts' 12 | - 'gradle/libs.versions.toml' 13 | pull_request: 14 | branches: [ "main" ] 15 | paths: 16 | - '.github/workflows/build.yml' 17 | - 'app/**' 18 | - 'apd/**' 19 | - 'build.gradle.kts' 20 | - 'gradle/libs.versions.toml' 21 | workflow_call: 22 | workflow_dispatch: 23 | 24 | jobs: 25 | build-manager: 26 | runs-on: ubuntu-latest 27 | permissions: 28 | contents: write 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | with: 33 | fetch-depth: 0 34 | 35 | - name: Generate version 36 | id: parse_version 37 | run: | 38 | COMMIT_NUM=$(git rev-list --count HEAD) 39 | VERSION=$(echo "$COMMIT_NUM + 198 + 10000" | bc) 40 | echo "Generated Version: $VERSION" 41 | echo "VERSION=$VERSION" >> $GITHUB_OUTPUT 42 | 43 | - name: Setup Java 44 | uses: actions/setup-java@v4 45 | with: 46 | distribution: temurin 47 | java-version: 21 48 | 49 | - name: Setup Gradle 50 | uses: gradle/actions/setup-gradle@v4 51 | 52 | - name: Setup Android SDK 53 | uses: android-actions/setup-android@v3 54 | with: 55 | packages: '' 56 | 57 | - name: Install toolchain 58 | run: | 59 | rustup default stable 60 | rustup update stable 61 | cargo install cargo-ndk 62 | rustup target install aarch64-linux-android 63 | 64 | - name: Cache Rust 65 | uses: Swatinem/rust-cache@v2 66 | with: 67 | workspaces: apd 68 | cache-targets: false 69 | 70 | - name: Build with Gradle 71 | run: | 72 | echo 'org.gradle.parallel=true' >> gradle.properties 73 | echo 'org.gradle.vfs.watch=true' >> gradle.properties 74 | echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties 75 | echo 'android.native.buildOutput=verbose' >> gradle.properties 76 | sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties 77 | ./gradlew clean assembleRelease 78 | echo "BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)" >> $GITHUB_ENV 79 | 80 | - name: Sign Release 81 | env: 82 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 83 | if: ${{ env.SIGNING_KEY != '' }} 84 | continue-on-error: true 85 | uses: noriban/sign-android-release@v5.1 86 | id: sign_app 87 | with: 88 | releaseDirectory: app/build/outputs/apk/release 89 | signingKeyBase64: ${{ secrets.SIGNING_KEY }} 90 | alias: ${{ secrets.ALIAS }} 91 | keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} 92 | keyPassword: ${{ secrets.KEY_PASSWORD }} 93 | 94 | - name: Upload build artifact 95 | env: 96 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 97 | if: ${{ env.SIGNING_KEY != '' }} 98 | uses: actions/upload-artifact@v4 99 | with: 100 | name: APatchLite 101 | path: ${{steps.sign_app.outputs.signedReleaseFile}} 102 | 103 | - name: Post to channel 104 | if: ${{github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && github.ref_type != 'tag'}} 105 | env: 106 | BOT_TOKEN: ${{ secrets.BOT_TOKEN }} 107 | COMMIT_MESSAGE: ${{ github.event.head_commit.message }} 108 | COMMIT_URL: ${{ github.event.head_commit.url }} 109 | COMMIT_ID: ${{ github.event.head_commit.id }} 110 | run: | 111 | if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then 112 | OUTPUT="app/build/outputs/apk/release" 113 | export Release=$(find $OUTPUT -name "*.apk") 114 | URL=$(python3 .github/scripts/telegram_url.py -1002058433411) 115 | curl -v "$URL" -F Release=@${{ steps.sign_app.outputs.signedReleaseFile }} 116 | URL=$(python3 .github/scripts/telegram_url.py -1001910818234) 117 | curl -v "$URL" -F Release=@${{ steps.sign_app.outputs.signedReleaseFile }} 118 | fi 119 | 120 | - name: Release apk 121 | env: 122 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 123 | if: ${{ env.SIGNING_KEY != '' && github.ref_type == 'tag' }} 124 | continue-on-error: true 125 | uses: ncipollo/release-action@v1 126 | with: 127 | token: ${{ github.token }} 128 | tag: ${{ steps.parse_version.outputs.VERSION }} 129 | artifacts: ${{steps.sign_app.outputs.signedReleaseFile}} 130 | generateReleaseNotes: true 131 | makeLatest: true 132 | replacesArtifacts: true 133 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .idea 5 | .DS_Store 6 | build 7 | captures 8 | .cxx 9 | key.jks 10 | key.jks.base64.txt 11 | .vscode 12 | .kotlin 13 | app/src/main/resources/ 14 | private 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > :warning: This is a fork of the original APatch project. 2 | 3 | > :warning: This is a simplified version which uses an **HARDCODED SUPERKEY** for the kernel super calls. You may consider this as non-secure for your use cases so consider yourself warned! 4 | 5 | > :warning: If you intend to use the official version, please go to the [official APatch](https://github.com/bmax121/APatch) project page to download an official release package. 6 | 7 | I made this fork with simplicity and an easy-to-use UI in mind for my daily usage of a simple SuperUser management app with OverlayFS support. 8 | These are the differences compared with the original version: 9 | - Hardcoded superkey for simplicity 10 | - Simplified patching UI 11 | - No KPM management 12 | 13 | --- 14 | 15 |
16 | logo 17 | 18 |

APatch

19 | 20 | [![Latest Release](https://img.shields.io/github/v/release/bmax121/APatch?label=Release&logo=github)](https://github.com/bmax121/APatch/releases/latest) 21 | [![Nightly Release](https://img.shields.io/badge/Nightly%20release-gray?logo=hackthebox&logoColor=fff)](https://nightly.link/bmax121/APatch/workflows/build/main/APatch) 22 | [![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/APatch) 23 | [![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/APatchGroup) 24 | [![GitHub License](https://img.shields.io/github/license/bmax121/APatch?logo=gnu)](/LICENSE) 25 | 26 |
27 | 28 | The patching of Android kernel and Android system. 29 | 30 | - A new kernel-based root solution for Android devices. 31 | - APM: Support for modules similar to Magisk. 32 | - KPM: Support for modules that allow you to inject any code into the kernel (Requires kernel function `inline-hook` and `syscall-table-hook` enabled). 33 | - APatch relies on [KernelPatch](https://github.com/bmax121/KernelPatch/). 34 | - The APatch UI and the APModule source code have been derived and modified from [KernelSU](https://github.com/tiann/KernelSU). 35 | 36 | [Get it on F-Droid](https://f-droid.org/packages/me.bmax.apatch/) 39 | 40 | Or download the latest APK from the [Releases Section](https://github.com/bmax121/APatch/releases/latest). 41 | 42 | ## Supported Versions 43 | 44 | - Only supports the ARM64 architecture. 45 | - Only supports Android kernel versions 3.18 - 6.1 46 | 47 | Support for Samsung devices with security protection: Planned 48 | 49 | ## Requirement 50 | 51 | Kernel configs: 52 | 53 | - `CONFIG_KALLSYMS=y` and `CONFIG_KALLSYMS_ALL=y` 54 | 55 | - `CONFIG_KALLSYMS=y` and `CONFIG_KALLSYMS_ALL=n`: Initial support 56 | 57 | ## Security Alert 58 | 59 | The **SuperKey** has higher privileges than root access. 60 | Weak or compromised keys can lead to unauthorized control of your device. 61 | It is critical to use robust keys and safeguard them from exposure to maintain the security of your device. 62 | 63 | ## Translation 64 | 65 | To help translate APatch or improve existing translations, please use [Weblate](https://hosted.weblate.org/engage/apatch/). PR of APatch translation is no longer accepted, because it will conflict with Weblate. 66 | 67 |
68 | 69 | [![Translation Status](https://hosted.weblate.org/widget/APatch/open-graph.png)](https://hosted.weblate.org/engage/APatch/) 70 | 71 |
72 | 73 | ## Get Help 74 | 75 | ### Usage 76 | 77 | For usage, please refer to [our official documentation](https://apatch.dev). 78 | It's worth noting that the documentation is currently not quite complete, and the content may change at any time. 79 | Furthermore, we need more volunteers to [contribute to the documentation](https://github.com/AndroidPatch/APatchDocs) in other languages. 80 | 81 | ### Updates 82 | 83 | - Telegram Channel: [@APatchUpdates](https://t.me/APatchChannel) 84 | 85 | ### Discussions 86 | 87 | - Telegram Group: [@APatchDiscussions(EN/CN)](https://t.me/Apatch_discuss) 88 | - Telegram Group: [中文](https://t.me/APatch_CN_Group) 89 | 90 | ### More Information 91 | 92 | - [Documents](docs/) 93 | 94 | ## Credits 95 | 96 | - [KernelPatch](https://github.com/bmax121/KernelPatch/): The core. 97 | - [Magisk](https://github.com/topjohnwu/Magisk): magiskboot and magiskpolicy. 98 | - [KernelSU](https://github.com/tiann/KernelSU): App UI, and Magisk module like support. 99 | 100 | ## License 101 | 102 | APatch is licensed under the GNU General Public License v3 [GPL-3](http://www.gnu.org/copyleft/gpl.html). 103 | -------------------------------------------------------------------------------- /apd/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .cargo/ -------------------------------------------------------------------------------- /apd/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "apd" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1" 10 | csv = "1.3.1" 11 | clap = { version = "4", features = ["derive"] } 12 | const_format = "0.2" 13 | zip = { version = "2.2.1", default-features = false } 14 | zip-extensions = { version = "0.8.1", features = [ 15 | "deflate", 16 | "deflate64", 17 | "time", 18 | "lzma", 19 | "xz", 20 | ], default-features = false } 21 | java-properties = { git = "https://github.com/AndroidPatch/java-properties.git", branch = "master", default-features = false } 22 | log = "0.4" 23 | env_logger = "0.11" 24 | serde = { version = "1", features = ["derive"] } 25 | serde_json = "1" 26 | regex-lite = "0.1.6" 27 | encoding_rs = "0.8" 28 | walkdir="2.4" 29 | retry = "2" 30 | humansize = "2" 31 | libc = "0.2" 32 | extattr = "1" 33 | jwalk = "0.8" 34 | is_executable = "1" 35 | nom = "7" 36 | derive-new = "0.7.0" 37 | which = "7" 38 | getopts = "0.2" 39 | sha256 = "1" 40 | tempdir = "0.3" 41 | chrono = "0.4" 42 | errno = "0.3.9" 43 | notify = "7.0" 44 | 45 | [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] 46 | rustix = { git = "https://github.com/AndroidPatch/rustix", branch = "main", features = ["all-apis"] } 47 | # some android specific dependencies which compiles under unix are also listed here for convenience of coding 48 | android-properties = { version = "0.2.2", features = ["bionic-deprecated"] } 49 | procfs = "0.17" 50 | loopdev = { git = "https://github.com/AndroidPatch/loopdev" } 51 | 52 | [target.'cfg(target_os = "android")'.dependencies] 53 | android_logger = { version = "0.14", default-features = false } 54 | 55 | [profile.release] 56 | strip = true 57 | opt-level = 3 58 | codegen-units = 1 59 | panic = "abort" 60 | lto = true 61 | -------------------------------------------------------------------------------- /apd/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::Write; 4 | use std::path::Path; 5 | use std::process::Command; 6 | 7 | fn get_git_version() -> Result<(u32, String), std::io::Error> { 8 | let output = Command::new("git") 9 | .args(["rev-list", "--count", "HEAD"]) 10 | .output()?; 11 | 12 | let output = output.stdout; 13 | let version_code = String::from_utf8(output).expect("Failed to read git count stdout"); 14 | let version_code: u32 = version_code 15 | .trim() 16 | .parse() 17 | .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to parse git count"))?; 18 | let version_code = 10000 + 198 + version_code; // For historical reasons 19 | 20 | let version_name = String::from_utf8( 21 | Command::new("git") 22 | .args(["describe", "--tags", "--always"]) 23 | .output()? 24 | .stdout, 25 | ) 26 | .map_err(|_| { 27 | std::io::Error::new( 28 | std::io::ErrorKind::Other, 29 | "Failed to read git describe stdout", 30 | ) 31 | })?; 32 | let version_name = version_name.trim_start_matches('v').to_string(); 33 | Ok((version_code, version_name)) 34 | } 35 | 36 | fn main() { 37 | let (code, name) = match get_git_version() { 38 | Ok((code, name)) => (code, name), 39 | Err(_) => { 40 | // show warning if git is not installed 41 | println!("cargo:warning=Failed to get git version, using 0.0.0"); 42 | (0, "0.0.0".to_string()) 43 | } 44 | }; 45 | let out_dir = env::var("OUT_DIR").expect("Failed to get $OUT_DIR"); 46 | println!("out_dir: ${out_dir}"); 47 | println!("code: ${code}"); 48 | let out_dir = Path::new(&out_dir); 49 | File::create(Path::new(out_dir).join("VERSION_CODE")) 50 | .expect("Failed to create VERSION_CODE") 51 | .write_all(code.to_string().as_bytes()) 52 | .expect("Failed to write VERSION_CODE"); 53 | 54 | File::create(Path::new(out_dir).join("VERSION_NAME")) 55 | .expect("Failed to create VERSION_NAME") 56 | .write_all(name.trim().as_bytes()) 57 | .expect("Failed to write VERSION_NAME"); 58 | } 59 | -------------------------------------------------------------------------------- /apd/src/assets.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use const_format::concatcp; 3 | 4 | use crate::{defs::BINARY_DIR, utils}; 5 | 6 | pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop"); 7 | pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox"); 8 | pub const MAGISKPOLICY_PATH: &str = concatcp!(BINARY_DIR, "magiskpolicy"); 9 | 10 | pub fn ensure_binaries() -> Result<()> { 11 | utils::ensure_binary(RESETPROP_PATH)?; 12 | utils::ensure_binary(BUSYBOX_PATH)?; 13 | utils::ensure_binary(MAGISKPOLICY_PATH)?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /apd/src/banner: -------------------------------------------------------------------------------- 1 | _ ____ _ _ 2 | / \ | _ \ __ _| |_ ___| |__ 3 | / _ \ | |_) / _` | __/ __| '_ \ 4 | / ___ \| __/ (_| | || (__| | | | 5 | /_/ \_\_| \__,_|\__\___|_| |_| 6 | -------------------------------------------------------------------------------- /apd/src/cli.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | #[cfg(target_os = "android")] 5 | use android_logger::Config; 6 | #[cfg(target_os = "android")] 7 | use log::LevelFilter; 8 | 9 | use crate::{defs, event, module, supercall, utils}; 10 | use std::ffi::{CString, CStr}; 11 | /// APatch cli 12 | #[derive(Parser, Debug)] 13 | #[command(author, version = defs::VERSION_CODE, about, long_about = None)] 14 | struct Args { 15 | #[arg( 16 | short, 17 | long, 18 | value_name = "KEY", 19 | help = "Super key for authentication root" 20 | )] 21 | superkey: Option, 22 | #[command(subcommand)] 23 | command: Commands, 24 | } 25 | 26 | #[derive(clap::Subcommand, Debug)] 27 | enum Commands { 28 | /// Manage APatch modules 29 | Module { 30 | #[command(subcommand)] 31 | command: Module, 32 | }, 33 | /// Manage Kernel Patch modules 34 | Kpm { 35 | #[command(subcommand)] 36 | command: Kpmsub, 37 | }, 38 | 39 | /// Trigger `post-fs-data` event 40 | PostFsData, 41 | 42 | /// Trigger `service` event 43 | Services, 44 | 45 | /// Trigger `boot-complete` event 46 | BootCompleted, 47 | 48 | /// Start uid listener for synchronizing root list 49 | UidListener, 50 | } 51 | 52 | #[derive(clap::Subcommand, Debug)] 53 | enum Module { 54 | /// Install module 55 | Install { 56 | /// module zip file path 57 | zip: String, 58 | }, 59 | 60 | /// Uninstall module 61 | Uninstall { 62 | /// module id 63 | id: String, 64 | }, 65 | 66 | /// enable module 67 | Enable { 68 | /// module id 69 | id: String, 70 | }, 71 | 72 | /// disable module 73 | Disable { 74 | // module id 75 | id: String, 76 | }, 77 | 78 | /// run action for module 79 | Action { 80 | // module id 81 | id: String, 82 | }, 83 | 84 | /// list all modules 85 | List, 86 | } 87 | #[derive(clap::Subcommand, Debug)] 88 | enum Kpmsub { 89 | /// Load Kernelpath module 90 | Load { 91 | // super_key 92 | key: String, 93 | // kpm module path 94 | path: String 95 | }, 96 | 97 | } 98 | 99 | pub fn run() -> Result<()> { 100 | #[cfg(target_os = "android")] 101 | android_logger::init_once( 102 | Config::default() 103 | .with_max_level(LevelFilter::Trace) // limit log level 104 | .with_tag("APatchD") 105 | .with_filter( 106 | android_logger::FilterBuilder::new() 107 | .filter_level(LevelFilter::Trace) 108 | .filter_module("notify", LevelFilter::Warn) 109 | .build(), 110 | ), 111 | ); 112 | 113 | #[cfg(not(target_os = "android"))] 114 | env_logger::init(); 115 | 116 | // the kernel executes su with argv[0] = "/system/bin/kp" or "/system/bin/su" or "su" or "kp" and replace it with us 117 | let arg0 = std::env::args().next().unwrap_or_default(); 118 | if arg0.ends_with("kp") || arg0.ends_with("su") { 119 | return crate::apd::root_shell(); 120 | } 121 | 122 | let cli = Args::parse(); 123 | 124 | log::info!("command: {:?}", cli.command); 125 | 126 | if let Some(ref _superkey) = cli.superkey { 127 | supercall::privilege_apd_profile(&cli.superkey); 128 | } 129 | 130 | let result = match cli.command { 131 | Commands::PostFsData => event::on_post_data_fs(cli.superkey), 132 | 133 | Commands::BootCompleted => event::on_boot_completed(cli.superkey), 134 | 135 | Commands::UidListener => event::start_uid_listener(), 136 | 137 | Commands::Kpm { command } => { 138 | match command { 139 | Kpmsub::Load { key,path } => { 140 | let key_cstr = CString::new(key).map_err(|_| anyhow::anyhow!("Invalid key string"))?; 141 | let path_cstr = CString::new(path).map_err(|_| anyhow::anyhow!("Invalid path string"))?; 142 | let ret = supercall::sc_kpm_load(key_cstr.as_c_str(),path_cstr.as_c_str(),None,std::ptr::null_mut()); 143 | if ret < 0 { 144 | Err(anyhow::anyhow!("System call failed with error code {}", ret)) 145 | } else { 146 | Ok(()) 147 | } 148 | 149 | } 150 | _ => Err(anyhow::anyhow!("Unsupported command")), 151 | 152 | } 153 | 154 | } 155 | 156 | Commands::Module { command } => { 157 | #[cfg(any(target_os = "linux", target_os = "android"))] 158 | { 159 | utils::switch_mnt_ns(1)?; 160 | utils::unshare_mnt_ns()?; 161 | } 162 | match command { 163 | Module::Install { zip } => module::install_module(&zip), 164 | Module::Uninstall { id } => module::uninstall_module(&id), 165 | Module::Action { id } => module::run_action(&id), 166 | Module::Enable { id } => module::enable_module(&id), 167 | Module::Disable { id } => module::disable_module(&id), 168 | Module::List => module::list_modules(), 169 | } 170 | } 171 | 172 | Commands::Services => event::on_services(cli.superkey), 173 | }; 174 | 175 | if let Err(e) = &result { 176 | log::error!("Error: {:?}", e); 177 | } 178 | result 179 | } 180 | -------------------------------------------------------------------------------- /apd/src/defs.rs: -------------------------------------------------------------------------------- 1 | use const_format::concatcp; 2 | 3 | pub const ADB_DIR: &str = "/data/adb/"; 4 | pub const WORKING_DIR: &str = concatcp!(ADB_DIR, "ap/"); 5 | pub const BINARY_DIR: &str = concatcp!(WORKING_DIR, "bin/"); 6 | pub const APATCH_LOG_FOLDER: &str = concatcp!(WORKING_DIR, "log/"); 7 | 8 | pub const AP_RC_PATH: &str = concatcp!(WORKING_DIR, ".aprc"); 9 | pub const GLOBAL_NAMESPACE_FILE: &str = concatcp!(ADB_DIR,".global_namespace_enable"); 10 | pub const BIND_MOUNT_FILE: &str = concatcp!(ADB_DIR,".bind_mount_enable"); 11 | pub const AP_OVERLAY_SOURCE: &str = "APatch"; 12 | pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "apd"); 13 | 14 | pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/"); 15 | pub const MODULE_UPDATE_TMP_IMG: &str = concatcp!(WORKING_DIR, "update_tmp.img"); 16 | 17 | // warning: this directory should not change, or you need to change the code in module_installer.sh!!! 18 | pub const MODULE_UPDATE_TMP_DIR: &str = concatcp!(ADB_DIR, "modules_update/"); 19 | pub const MODULE_MOUNT_DIR: &str = concatcp!(ADB_DIR, "modules_mount/"); 20 | 21 | pub const SYSTEM_RW_DIR: &str = concatcp!(MODULE_DIR, ".rw/"); 22 | 23 | pub const TEMP_DIR: &str = "/debug_ramdisk"; 24 | pub const TEMP_DIR_LEGACY: &str = "/sbin"; 25 | 26 | pub const MODULE_WEB_DIR: &str = "webroot"; 27 | pub const MODULE_ACTION_SH: &str = "action.sh"; 28 | pub const DISABLE_FILE_NAME: &str = "disable"; 29 | pub const UPDATE_FILE_NAME: &str = "update"; 30 | pub const REMOVE_FILE_NAME: &str = "remove"; 31 | pub const SKIP_MOUNT_FILE_NAME: &str = "skip_mount"; 32 | pub const PTS_NAME: &str = "pts"; 33 | 34 | pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE")); 35 | pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME")); 36 | -------------------------------------------------------------------------------- /apd/src/main.rs: -------------------------------------------------------------------------------- 1 | mod apd; 2 | mod assets; 3 | mod cli; 4 | mod defs; 5 | mod event; 6 | mod m_mount; 7 | mod module; 8 | mod mount; 9 | mod package; 10 | #[cfg(any(target_os = "linux", target_os = "android"))] 11 | mod pty; 12 | mod restorecon; 13 | mod supercall; 14 | mod utils; 15 | fn main() -> anyhow::Result<()> { 16 | cli::run() 17 | } 18 | -------------------------------------------------------------------------------- /apd/src/package.rs: -------------------------------------------------------------------------------- 1 | use log::{info, warn}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::fs::File; 4 | use std::io::{self, BufRead}; 5 | use std::path::Path; 6 | 7 | #[derive(Deserialize, Serialize)] 8 | pub struct PackageConfig { 9 | pub pkg: String, 10 | pub exclude: i32, 11 | pub allow: i32, 12 | pub uid: i32, 13 | pub to_uid: i32, 14 | pub sctx: String, 15 | } 16 | 17 | pub fn read_ap_package_config() -> Vec { 18 | let file = match File::open("/data/adb/ap/package_config") { 19 | Ok(file) => file, 20 | Err(e) => { 21 | warn!("Error opening file: {}", e); 22 | return Vec::new(); 23 | } 24 | }; 25 | 26 | let mut reader = csv::Reader::from_reader(file); 27 | let mut package_configs = Vec::new(); 28 | for record in reader.deserialize() { 29 | match record { 30 | Ok(config) => package_configs.push(config), 31 | Err(e) => { 32 | warn!("Error deserializing record: {}", e); 33 | } 34 | } 35 | } 36 | 37 | package_configs 38 | } 39 | 40 | fn write_ap_package_config(package_configs: &[PackageConfig]) { 41 | let file = match File::create("/data/adb/ap/package_config") { 42 | Ok(file) => file, 43 | Err(e) => { 44 | warn!("Error creating file: {}", e); 45 | return; 46 | } 47 | }; 48 | 49 | let mut writer = csv::Writer::from_writer(file); 50 | for config in package_configs { 51 | if let Err(e) = writer.serialize(config) { 52 | warn!("Error serializing record: {}", e); 53 | } 54 | } 55 | } 56 | 57 | fn read_lines

(filename: P) -> io::Result>> 58 | where 59 | P: AsRef, 60 | { 61 | File::open(filename).map(|file| io::BufReader::new(file).lines()) 62 | } 63 | 64 | pub fn synchronize_package_uid() { 65 | info!("[synchronize_package_uid] Start synchronizing root list with system packages..."); 66 | 67 | if let Ok(lines) = read_lines("/data/system/packages.list") { 68 | let mut package_configs = read_ap_package_config(); 69 | 70 | for line in lines.filter_map(|line| line.ok()) { 71 | let words: Vec<&str> = line.split_whitespace().collect(); 72 | if words.len() >= 2 { 73 | if let Ok(uid) = words[1].parse::() { 74 | if let Some(config) = package_configs 75 | .iter_mut() 76 | .find(|config| config.pkg == words[0]) 77 | { 78 | config.uid = uid; 79 | } 80 | } else { 81 | warn!("Error parsing uid: {}", words[1]); 82 | } 83 | } 84 | } 85 | 86 | write_ap_package_config(&package_configs); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /apd/src/restorecon.rs: -------------------------------------------------------------------------------- 1 | use crate::defs; 2 | use anyhow::Result; 3 | use jwalk::{Parallelism::Serial, WalkDir}; 4 | use std::path::Path; 5 | 6 | #[cfg(any(target_os = "linux", target_os = "android"))] 7 | use anyhow::{Context, Ok}; 8 | #[cfg(any(target_os = "linux", target_os = "android"))] 9 | use extattr::{lsetxattr, Flags as XattrFlags}; 10 | 11 | pub const SYSTEM_CON: &str = "u:object_r:system_file:s0"; 12 | pub const ADB_CON: &str = "u:object_r:adb_data_file:s0"; 13 | pub const UNLABEL_CON: &str = "u:object_r:unlabeled:s0"; 14 | 15 | const SELINUX_XATTR: &str = "security.selinux"; 16 | 17 | pub fn lsetfilecon>(path: P, con: &str) -> Result<()> { 18 | #[cfg(any(target_os = "linux", target_os = "android"))] 19 | lsetxattr(&path, SELINUX_XATTR, con, XattrFlags::empty()).with_context(|| { 20 | format!( 21 | "Failed to change SELinux context for {}", 22 | path.as_ref().display() 23 | ) 24 | })?; 25 | Ok(()) 26 | } 27 | 28 | #[cfg(any(target_os = "linux", target_os = "android"))] 29 | pub fn lgetfilecon>(path: P) -> Result { 30 | let con = extattr::lgetxattr(&path, SELINUX_XATTR).with_context(|| { 31 | format!( 32 | "Failed to get SELinux context for {}", 33 | path.as_ref().display() 34 | ) 35 | })?; 36 | let con = String::from_utf8_lossy(&con); 37 | Ok(con.to_string()) 38 | } 39 | 40 | #[cfg(any(target_os = "linux", target_os = "android"))] 41 | pub fn setsyscon>(path: P) -> Result<()> { 42 | lsetfilecon(path, SYSTEM_CON) 43 | } 44 | 45 | #[cfg(not(any(target_os = "linux", target_os = "android")))] 46 | pub fn setsyscon>(path: P) -> Result<()> { 47 | unimplemented!() 48 | } 49 | 50 | #[cfg(not(any(target_os = "linux", target_os = "android")))] 51 | pub fn lgetfilecon>(path: P) -> Result { 52 | unimplemented!() 53 | } 54 | 55 | pub fn restore_syscon>(dir: P) -> Result<()> { 56 | for dir_entry in WalkDir::new(dir).parallelism(Serial) { 57 | if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) { 58 | setsyscon(&path)?; 59 | } 60 | } 61 | Ok(()) 62 | } 63 | 64 | fn restore_syscon_if_unlabeled>(dir: P) -> Result<()> { 65 | for dir_entry in WalkDir::new(dir).parallelism(Serial) { 66 | if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) { 67 | if let anyhow::Result::Ok(con) = lgetfilecon(&path) { 68 | if con == UNLABEL_CON || con.is_empty() { 69 | lsetfilecon(&path, SYSTEM_CON)?; 70 | } 71 | } 72 | } 73 | } 74 | Ok(()) 75 | } 76 | 77 | pub fn restorecon() -> Result<()> { 78 | lsetfilecon(defs::DAEMON_PATH, ADB_CON)?; 79 | restore_syscon_if_unlabeled(defs::MODULE_DIR)?; 80 | Ok(()) 81 | } 82 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release/ 3 | -------------------------------------------------------------------------------- /app/libs/arm64-v8a/.gitignore: -------------------------------------------------------------------------------- 1 | libkptools.so 2 | libapjni.so 3 | libkpatch.so 4 | libapd.so -------------------------------------------------------------------------------- /app/libs/arm64-v8a/libbootctl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponces/APatchLite/98f9cb734d936e3f5716252429dcdb6a026851f0/app/libs/arm64-v8a/libbootctl.so -------------------------------------------------------------------------------- /app/libs/arm64-v8a/libbusybox.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponces/APatchLite/98f9cb734d936e3f5716252429dcdb6a026851f0/app/libs/arm64-v8a/libbusybox.so -------------------------------------------------------------------------------- /app/libs/arm64-v8a/libmagiskboot.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponces/APatchLite/98f9cb734d936e3f5716252429dcdb6a026851f0/app/libs/arm64-v8a/libmagiskboot.so -------------------------------------------------------------------------------- /app/libs/arm64-v8a/libmagiskpolicy.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponces/APatchLite/98f9cb734d936e3f5716252429dcdb6a026851f0/app/libs/arm64-v8a/libmagiskpolicy.so -------------------------------------------------------------------------------- /app/libs/arm64-v8a/libresetprop.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponces/APatchLite/98f9cb734d936e3f5716252429dcdb6a026851f0/app/libs/arm64-v8a/libresetprop.so -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -dontwarn org.bouncycastle.jsse.BCSSLParameters 2 | -dontwarn org.bouncycastle.jsse.BCSSLSocket 3 | -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider 4 | -dontwarn org.conscrypt.Conscrypt$Version 5 | -dontwarn org.conscrypt.Conscrypt 6 | -dontwarn org.conscrypt.ConscryptHostnameVerifier 7 | -dontwarn org.openjsse.javax.net.ssl.SSLParameters 8 | -dontwarn org.openjsse.javax.net.ssl.SSLSocket 9 | -dontwarn org.openjsse.net.ssl.OpenJSSE 10 | -dontwarn java.beans.Introspector 11 | -dontwarn java.beans.VetoableChangeListener 12 | -dontwarn java.beans.VetoableChangeSupport 13 | 14 | # Keep ini4j Service Provider Interface 15 | -keep,allowobfuscation,allowoptimization public class org.ini4j.spi.* 16 | 17 | # Kotlin 18 | -assumenosideeffects class kotlin.jvm.internal.Intrinsics { 19 | public static void check*(...); 20 | public static void throw*(...); 21 | } 22 | 23 | -repackageclasses 24 | -allowaccessmodification 25 | -overloadaggressively 26 | -renamesourcefileattribute SourceFile 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 12 | 15 | 16 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 51 | 52 | 57 | 60 | 61 | 62 | 66 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /app/src/main/aidl/me/bmax/apatch/IAPRootService.aidl: -------------------------------------------------------------------------------- 1 | // IAPRootService.aidl 2 | package me.bmax.apatch; 3 | 4 | import android.content.pm.PackageInfo; 5 | import rikka.parcelablelist.ParcelableListSlice; 6 | 7 | interface IAPRootService { 8 | ParcelableListSlice getPackages(int flags); 9 | } -------------------------------------------------------------------------------- /app/src/main/assets/.gitignore: -------------------------------------------------------------------------------- 1 | kpimg 2 | *.kpm -------------------------------------------------------------------------------- /app/src/main/assets/InstallAP.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # By SakuraKyuo 3 | 4 | OUTFD=/proc/self/fd/$2 5 | 6 | function ui_print() { 7 | echo -e "ui_print $1\nui_print" >> $OUTFD 8 | } 9 | 10 | function ui_printfile() { 11 | while IFS='' read -r line || $BB [[ -n "$line" ]]; do 12 | ui_print "$line"; 13 | done < $1; 14 | } 15 | 16 | function kernelFlagsErr(){ 17 | ui_print "- Installation has Aborted!" 18 | ui_print "- APatch requires CONFIG_KALLSYMS to be Enabled." 19 | ui_print "- But your kernel seems NOT enabled it." 20 | exit 21 | } 22 | 23 | function apatchNote(){ 24 | ui_print "- APatch Patch Done" 25 | ui_print "- APatch Key is $skey" 26 | ui_print "- We do have saved Origin Boot image to /data" 27 | ui_print "- If you encounter bootloop, reboot into Recovery and flash it" 28 | exit 29 | } 30 | 31 | function failed(){ 32 | ui_printfile /dev/tmp/install/log 33 | ui_print "- APatch Patch Failed." 34 | ui_print "- Please feedback to the developer with the screenshots." 35 | exit 36 | } 37 | 38 | function boot_execute_ab(){ 39 | ./lib/arm64-v8a/libmagiskboot.so unpack boot.img 40 | if [[ ! $(./lib/arm64-v8a/libkptools.so -i ./kernel -f | grep CONFIG_KALLSYMS=y) ]]; then 41 | kernelFlagsErr 42 | fi 43 | mv kernel kernel-origin 44 | ./lib/arm64-v8a/libkptools.so -p --image kernel-origin --skey "$skey" --kpimg ./assets/kpimg --out ./kernel 2>&1 | tee /dev/tmp/install/log 45 | if [[ ! $(cat /dev/tmp/install/log | grep "patch done") ]]; then 46 | failed 47 | fi 48 | ui_printfile /dev/tmp/install/log 49 | ./lib/arm64-v8a/libmagiskboot.so repack boot.img 50 | dd if=/dev/tmp/install/new-boot.img of=/dev/block/by-name/boot$slot 51 | mv boot.img /data/boot.img 52 | apatchNote 53 | } 54 | 55 | function boot_execute(){ 56 | ./lib/arm64-v8a/libmagiskboot.so unpack boot.img 57 | if [[ ! $(./lib/arm64-v8a/libkptools.so -i ./kernel -f | grep CONFIG_KALLSYMS=y) ]]; then 58 | kernelFlagsErr 59 | fi 60 | mv kernel kernel-origin 61 | ./lib/arm64-v8a/libkptools.so -p --image kernel-origin --skey "$skey" --kpimg ./assets/kpimg --out ./kernel 2>&1 | tee /dev/tmp/install/log 62 | if [[ ! $(cat /dev/tmp/install/log | grep "patch done") ]]; then 63 | failed 64 | fi 65 | ui_printfile /dev/tmp/install/log 66 | ./lib/arm64-v8a/libmagiskboot.so repack boot.img 67 | dd if=/dev/tmp/install/new-boot.img of=/dev/block/by-name/boot$slot 68 | mv boot.img /data/boot.img 69 | apatchNote 70 | } 71 | 72 | function main(){ 73 | 74 | cd /dev/tmp/install 75 | 76 | chmod a+x ./assets/kpimg 77 | chmod a+x ./lib/arm64-v8a/libkptools.so 78 | chmod a+x ./lib/arm64-v8a/libmagiskboot.so 79 | 80 | slot=$(getprop ro.boot.slot_suffix) 81 | 82 | skey=$(cat /proc/sys/kernel/random/uuid | cut -d \- -f1) 83 | 84 | if [[ ! "$slot" == "" ]]; then 85 | 86 | ui_print "" 87 | ui_print "- You are using A/B device." 88 | 89 | # Script author 90 | ui_print "- Install Script by SakuraKyuo" 91 | 92 | # Get kernel 93 | ui_print "" 94 | dd if=/dev/block/by-name/boot$slot of=/dev/tmp/install/boot.img 95 | if [[ "$?" == 0 ]]; then 96 | ui_print "- Detected boot partition." 97 | boot_execute_ab 98 | fi 99 | 100 | else 101 | 102 | ui_print "You are using A Only device." 103 | 104 | # Get kernel 105 | ui_print "" 106 | dd if=/dev/block/by-name/boot of=/dev/tmp/install/boot.img 107 | if [[ "$?" == 0 ]]; then 108 | ui_print "- Detected boot partition." 109 | boot_execute 110 | fi 111 | 112 | fi 113 | 114 | } 115 | 116 | main -------------------------------------------------------------------------------- /app/src/main/assets/UninstallAP.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # By SakuraKyuo 3 | 4 | OUTFD=/proc/self/fd/$2 5 | 6 | function ui_print() { 7 | echo -e "ui_print $1\nui_print" >> $OUTFD 8 | } 9 | 10 | function ui_printfile() { 11 | while IFS='' read -r line || $BB [[ -n "$line" ]]; do 12 | ui_print "$line"; 13 | done < $1; 14 | } 15 | 16 | function apatchNote(){ 17 | ui_print "- APatch Unpatch Done" 18 | exit 19 | } 20 | 21 | function failed(){ 22 | ui_print "- APatch Unpatch Failed." 23 | ui_print "- Please feedback to the developer with the screenshots." 24 | exit 25 | } 26 | 27 | function boot_execute_ab(){ 28 | ./lib/arm64-v8a/libmagiskboot.so unpack boot.img 29 | mv kernel kernel-origin 30 | ./lib/arm64-v8a/libkptools.so -u --image kernel-origin --out ./kernel 31 | if [[ ! "$?" == 0 ]]; then 32 | failed 33 | fi 34 | ./lib/arm64-v8a/libmagiskboot.so repack boot.img 35 | dd if=/dev/tmp/install/new-boot.img of=/dev/block/by-name/boot$slot 36 | apatchNote 37 | } 38 | 39 | function boot_execute(){ 40 | ./lib/arm64-v8a/libmagiskboot.so unpack boot.img 41 | mv kernel kernel-origin 42 | ./lib/arm64-v8a/libkptools.so -u --image kernel-origin --out ./kernel 43 | if [[ ! "$?" == 0 ]]; then 44 | failed 45 | fi 46 | ./lib/arm64-v8a/libmagiskboot.so repack boot.img 47 | dd if=/dev/tmp/install/new-boot.img of=/dev/block/by-name/boot 48 | apatchNote 49 | } 50 | 51 | function main(){ 52 | 53 | cd /dev/tmp/install 54 | 55 | chmod a+x ./lib/arm64-v8a/libkptools.so 56 | chmod a+x ./lib/arm64-v8a/libmagiskboot.so 57 | 58 | slot=$(getprop ro.boot.slot_suffix) 59 | 60 | if [[ ! "$slot" == "" ]]; then 61 | 62 | ui_print "" 63 | ui_print "- You are using A/B device." 64 | 65 | # Get kernel 66 | ui_print "" 67 | dd if=/dev/block/by-name/boot$slot of=/dev/tmp/install/boot.img 68 | if [[ "$?" == 0 ]]; then 69 | ui_print "- Detected boot partition." 70 | boot_execute_ab 71 | fi 72 | 73 | else 74 | 75 | ui_print "You are using A Only device." 76 | 77 | # Get kernel 78 | ui_print "" 79 | dd if=/dev/block/by-name/boot of=/dev/tmp/install/boot.img 80 | if [[ "$?" == 0 ]]; then 81 | ui_print "- Detected boot partition." 82 | boot_execute 83 | fi 84 | 85 | fi 86 | 87 | } 88 | 89 | main -------------------------------------------------------------------------------- /app/src/main/assets/boot_extract.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | ARCH=$(getprop ro.product.cpu.abi) 4 | 5 | IS_INSTALL_NEXT_SLOT=$1 6 | 7 | # Load utility functions 8 | . ./util_functions.sh 9 | 10 | if [ "$IS_INSTALL_NEXT_SLOT" = "true" ]; then 11 | get_next_slot 12 | else 13 | get_current_slot 14 | fi 15 | 16 | find_boot_image 17 | 18 | [ -e "$BOOTIMAGE" ] || { >&2 echo "- can't find boot.img!"; exit 1; } 19 | 20 | true 21 | -------------------------------------------------------------------------------- /app/src/main/assets/boot_patch.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | ####################################################################################### 3 | # APatch Boot Image Patcher 4 | ####################################################################################### 5 | # 6 | # Usage: boot_patch.sh [ARGS_PASS_TO_KPTOOLS] 7 | # 8 | # This script should be placed in a directory with the following files: 9 | # 10 | # File name Type Description 11 | # 12 | # boot_patch.sh script A script to patch boot image for APatch. 13 | # (this file) The script will use files in its same 14 | # directory to complete the patching process. 15 | # bootimg binary The target boot image 16 | # kpimg binary KernelPatch core Image 17 | # kptools executable The KernelPatch tools binary to inject kpimg to kernel Image 18 | # magiskboot executable Magisk tool to unpack boot.img. 19 | # 20 | ####################################################################################### 21 | 22 | ARCH=$(getprop ro.product.cpu.abi) 23 | 24 | # Load utility functions 25 | . ./util_functions.sh 26 | 27 | echo "****************************" 28 | echo " APatch Boot Image Patcher" 29 | echo "****************************" 30 | 31 | SUPERKEY="$1" 32 | BOOTIMAGE=$2 33 | FLASH_TO_DEVICE=$3 34 | shift 2 35 | 36 | [ -z "$SUPERKEY" ] && { >&2 echo "- SuperKey empty!"; exit 1; } 37 | [ -e "$BOOTIMAGE" ] || { >&2 echo "- $BOOTIMAGE does not exist!"; exit 1; } 38 | 39 | # Check for dependencies 40 | command -v ./magiskboot >/dev/null 2>&1 || { >&2 echo "- Command magiskboot not found!"; exit 1; } 41 | command -v ./kptools >/dev/null 2>&1 || { >&2 echo "- Command kptools not found!"; exit 1; } 42 | 43 | if [ ! -f kernel ]; then 44 | echo "- Unpacking boot image" 45 | ./magiskboot unpack "$BOOTIMAGE" >/dev/null 2>&1 46 | if [ $? -ne 0 ]; then 47 | >&2 echo "- Unpack error: $?" 48 | exit $? 49 | fi 50 | fi 51 | 52 | if [ ! $(./kptools -i kernel -f | grep CONFIG_KALLSYMS=y) ]; then 53 | echo "- Patcher has Aborted!" 54 | echo "- APatch requires CONFIG_KALLSYMS to be Enabled." 55 | echo "- But your kernel seems NOT enabled it." 56 | exit 0 57 | fi 58 | 59 | mv kernel kernel.ori 60 | 61 | echo "- Patching kernel" 62 | 63 | set -x 64 | ./kptools -p -i kernel.ori -S "$SUPERKEY" -k kpimg -o kernel "$@" 65 | patch_rc=$? 66 | set +x 67 | 68 | if [ $patch_rc -ne 0 ]; then 69 | >&2 echo "- Patch kernel error: $patch_rc" 70 | exit $? 71 | fi 72 | 73 | echo "- Repacking boot image" 74 | ./magiskboot repack "$BOOTIMAGE" >/dev/null 2>&1 75 | 76 | if [ ! $(./kptools -i kernel.ori -f | grep CONFIG_KALLSYMS_ALL=y) ]; then 77 | echo "- Detected CONFIG_KALLSYMS_ALL is not set!" 78 | echo "- APatch has patched but maybe your device won't boot." 79 | echo "- Make sure you have original boot image backup." 80 | fi 81 | 82 | if [ $? -ne 0 ]; then 83 | >&2 echo "- Repack error: $?" 84 | exit $? 85 | fi 86 | 87 | if [ "$FLASH_TO_DEVICE" = "true" ]; then 88 | # flash 89 | if [ -b "$BOOTIMAGE" ] || [ -c "$BOOTIMAGE" ] && [ -f "new-boot.img" ]; then 90 | echo "- Flashing new boot image" 91 | flash_image new-boot.img "$BOOTIMAGE" 92 | if [ $? -ne 0 ]; then 93 | >&2 echo "- Flash error: $?" 94 | exit $? 95 | fi 96 | fi 97 | 98 | echo "- Successfully Flashed!" 99 | else 100 | echo "- Successfully Patched!" 101 | fi 102 | 103 | echo "- Cleaning up" 104 | ./magiskboot cleanup >/dev/null 2>&1 105 | rm -f kernel.ori 106 | -------------------------------------------------------------------------------- /app/src/main/assets/boot_unpatch.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | ####################################################################################### 3 | # APatch Boot Image Unpatcher 4 | ####################################################################################### 5 | 6 | ARCH=$(getprop ro.product.cpu.abi) 7 | 8 | # Load utility functions 9 | . ./util_functions.sh 10 | 11 | echo "****************************" 12 | echo " APatch Boot Image Unpatcher" 13 | echo "****************************" 14 | 15 | BOOTIMAGE=$1 16 | 17 | [ -e "$BOOTIMAGE" ] || { echo "- $BOOTIMAGE does not exist!"; exit 1; } 18 | 19 | echo "- Target image: $BOOTIMAGE" 20 | 21 | # Check for dependencies 22 | command -v ./magiskboot >/dev/null 2>&1 || { echo "- Command magiskboot not found!"; exit 1; } 23 | command -v ./kptools >/dev/null 2>&1 || { echo "- Command kptools not found!"; exit 1; } 24 | 25 | if [ ! -f kernel ]; then 26 | echo "- Unpacking boot image" 27 | ./magiskboot unpack "$BOOTIMAGE" >/dev/null 2>&1 28 | if [ $? -ne 0 ]; then 29 | >&2 echo "- Unpack error: $?" 30 | exit $? 31 | fi 32 | fi 33 | 34 | mv kernel kernel.ori 35 | 36 | echo "- Unpatching kernel" 37 | ./kptools -u --image kernel.ori --out kernel 38 | 39 | if [ $? -ne 0 ]; then 40 | >&2 echo "- Unpatch error: $?" 41 | exit $? 42 | fi 43 | 44 | echo "- Repacking boot image" 45 | ./magiskboot repack "$BOOTIMAGE" >/dev/null 2>&1 46 | 47 | if [ $? -ne 0 ]; then 48 | >&2 echo "- Repack error: $?" 49 | exit $? 50 | fi 51 | 52 | if [ -f "new-boot.img" ]; then 53 | echo "- Flashing boot image" 54 | flash_image new-boot.img "$BOOTIMAGE" 55 | 56 | if [ $? -ne 0 ]; then 57 | >&2 echo "- Flash error: $?" 58 | exit $? 59 | fi 60 | fi 61 | 62 | echo "- Flash successful" 63 | 64 | echo "- Cleaning up" 65 | ./magiskboot cleanup >/dev/null 2>&1 66 | rm -f kernel.ori 67 | 68 | # Reset any error code 69 | true 70 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28.0) 2 | project(apjni) 3 | 4 | find_program(CCACHE ccache) 5 | 6 | if (CCACHE) 7 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) 8 | set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) 9 | endif () 10 | 11 | find_package(cxx REQUIRED CONFIG) 12 | link_libraries(cxx::cxx) 13 | 14 | macro(SET_OPTION option value) 15 | set(${option} ${value} CACHE INTERNAL "" FORCE) 16 | endmacro() 17 | 18 | set(CHERISH_POLLY_FLAGS "-mllvm -polly -mllvm -polly-run-dce -mllvm -polly-run-inliner -mllvm -polly-reschedule=1 -mllvm -polly-loopfusion-greedy=1 -mllvm -polly-postopts=1 -mllvm -polly-num-threads=0 -mllvm -polly-omp-backend=LLVM -mllvm -polly-scheduling=dynamic -mllvm -polly-scheduling-chunksize=1 -mllvm -polly-isl-arg=--no-schedule-serialize-sccs -mllvm -polly-ast-use-context -mllvm -polly-detect-keep-going -mllvm -polly-position=before-vectorizer -mllvm -polly-vectorizer=stripmine -mllvm -polly-detect-profitability-min-per-loop-insts=40 -mllvm -polly-invariant-load-hoisting") 19 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CHERISH_POLLY_FLAGS} -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -O3 -flto") 20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CHERISH_POLLY_FLAGS} -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -fno-rtti -fno-exceptions -O3 -flto") 21 | set(CMAKE_CXX_STANDARD 23) 22 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 23 | 24 | add_library(${PROJECT_NAME} SHARED apjni.cpp) 25 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 26 | COMMAND ${CMAKE_STRIP} --strip-all --remove-section=.note.gnu.build-id --remove-section=.note.android.ident $) 27 | 28 | target_link_libraries(${PROJECT_NAME} PRIVATE log) 29 | target_compile_options(${PROJECT_NAME} PRIVATE -flto) 30 | target_link_options(${PROJECT_NAME} PRIVATE "-Wl,--build-id=none" "-Wl,-icf=all,--lto-O3" "-Wl,-s,-x,--gc-sections" "-Wl,--no-undefined") 31 | -------------------------------------------------------------------------------- /app/src/main/cpp/apjni.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by GarfieldHan on 2024/6/11. 3 | // 4 | 5 | #ifndef APATCH_APJNI_HPP 6 | #define APATCH_APJNI_HPP 7 | 8 | #define LOG_TAG "APatchNative" 9 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 10 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 11 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 12 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 13 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 14 | 15 | 16 | #endif //APATCH_APJNI_HPP 17 | -------------------------------------------------------------------------------- /app/src/main/cpp/uapi/scdefs.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ 2 | /* 3 | * Copyright (C) 2023 bmax121. All Rights Reserved. 4 | */ 5 | 6 | #ifndef _KP_UAPI_SCDEF_H_ 7 | #define _KP_UAPI_SCDEF_H_ 8 | 9 | static inline long hash_key(const char *key) 10 | { 11 | long hash = 1000000007; 12 | for (int i = 0; key[i]; i++) { 13 | hash = hash * 31 + key[i]; 14 | } 15 | return hash; 16 | } 17 | 18 | #define SUPERCALL_HELLO_ECHO "hello1158" 19 | 20 | // #define __NR_supercall __NR3264_truncate // 45 21 | #define __NR_supercall 45 22 | 23 | #define SUPERCALL_HELLO 0x1000 24 | #define SUPERCALL_KLOG 0x1004 25 | 26 | #define SUPERCALL_KERNELPATCH_VER 0x1008 27 | #define SUPERCALL_KERNEL_VER 0x1009 28 | 29 | #define SUPERCALL_SKEY_GET 0x100a 30 | #define SUPERCALL_SKEY_SET 0x100b 31 | #define SUPERCALL_SKEY_ROOT_ENABLE 0x100c 32 | 33 | #define SUPERCALL_SU 0x1010 34 | #define SUPERCALL_SU_TASK 0x1011 // syscall(__NR_gettid) 35 | 36 | #define SUPERCALL_KPM_LOAD 0x1020 37 | #define SUPERCALL_KPM_UNLOAD 0x1021 38 | #define SUPERCALL_KPM_CONTROL 0x1022 39 | 40 | #define SUPERCALL_KPM_NUMS 0x1030 41 | #define SUPERCALL_KPM_LIST 0x1031 42 | #define SUPERCALL_KPM_INFO 0x1032 43 | 44 | struct kernel_storage 45 | { 46 | void *data; 47 | int len; 48 | }; 49 | 50 | #define SUPERCALL_KSTORAGE_ALLOC_GROUP 0x1040 51 | #define SUPERCALL_KSTORAGE_WRITE 0x1041 52 | #define SUPERCALL_KSTORAGE_READ 0x1042 53 | #define SUPERCALL_KSTORAGE_LIST_IDS 0x1043 54 | #define SUPERCALL_KSTORAGE_REMOVE 0x1044 55 | #define SUPERCALL_KSTORAGE_REMOVE_GROUP 0x1045 56 | 57 | #define KSTORAGE_SU_LIST_GROUP 0 58 | #define KSTORAGE_EXCLUDE_LIST_GROUP 1 59 | #define KSTORAGE_UNUSED_GROUP_2 2 60 | #define KSTORAGE_UNUSED_GROUP_3 3 61 | 62 | #define SUPERCALL_BOOTLOG 0x10fd 63 | #define SUPERCALL_PANIC 0x10fe 64 | #define SUPERCALL_TEST 0x10ff 65 | 66 | #define SUPERCALL_KEY_MAX_LEN 0x40 67 | #define SUPERCALL_SCONTEXT_LEN 0x60 68 | 69 | struct su_profile 70 | { 71 | uid_t uid; 72 | uid_t to_uid; 73 | char scontext[SUPERCALL_SCONTEXT_LEN]; 74 | }; 75 | 76 | #ifdef ANDROID 77 | #define SH_PATH "/system/bin/sh" 78 | #define SU_PATH "/system/bin/kp" 79 | #define LEGACY_SU_PATH "/system/bin/su" 80 | #define ECHO_PATH "/system/bin/echo" 81 | #define KERNELPATCH_DATA_DIR "/data/adb/kp" 82 | #define KERNELPATCH_MODULE_DATA_DIR KERNELPATCH_DATA_DIR "/modules" 83 | #define APD_PATH "/data/adb/apd" 84 | #define ALL_ALLOW_SCONTEXT "u:r:kp:s0" 85 | #define ALL_ALLOW_SCONTEXT_MAGISK "u:r:magisk:s0" 86 | #define ALL_ALLOW_SCONTEXT_KERNEL "u:r:kernel:s0" 87 | #else 88 | #define SH_PATH "/usr/bin/sh" 89 | #define ECHO_PATH "/usr/bin/echo" 90 | #define SU_PATH "/usr/bin/kp" 91 | #define ALL_ALLOW_SCONTEXT "u:r:kernel:s0" 92 | #endif 93 | 94 | #define SU_PATH_MAX_LEN 128 95 | 96 | #define SUPERCMD "/system/bin/truncate" 97 | 98 | #define SAFE_MODE_FLAG_FILE "/dev/.safemode" 99 | 100 | #define SUPERCALL_SU_GRANT_UID 0x1100 101 | #define SUPERCALL_SU_REVOKE_UID 0x1101 102 | #define SUPERCALL_SU_NUMS 0x1102 103 | #define SUPERCALL_SU_LIST 0x1103 104 | #define SUPERCALL_SU_PROFILE 0x1104 105 | #define SUPERCALL_SU_GET_ALLOW_SCTX 0x1105 106 | #define SUPERCALL_SU_SET_ALLOW_SCTX 0x1106 107 | #define SUPERCALL_SU_GET_PATH 0x1110 108 | #define SUPERCALL_SU_RESET_PATH 0x1111 109 | #define SUPERCALL_SU_GET_SAFEMODE 0x1112 110 | 111 | #define SUPERCALL_MAX 0x1200 112 | 113 | #define SUPERCALL_RES_SUCCEED 0 114 | 115 | #define SUPERCALL_HELLO_MAGIC 0x11581158 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /app/src/main/cpp/version: -------------------------------------------------------------------------------- 1 | #define MAJOR 0 2 | #define MINOR 11 3 | #define PATCH 1 4 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponces/APatchLite/98f9cb734d936e3f5716252429dcdb6a026851f0/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/Natives.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch 2 | 3 | import android.os.Parcelable 4 | import androidx.annotation.Keep 5 | import androidx.compose.runtime.Immutable 6 | import kotlinx.parcelize.Parcelize 7 | 8 | object Natives { 9 | init { 10 | System.loadLibrary("apjni") 11 | } 12 | 13 | @Immutable 14 | @Parcelize 15 | @Keep 16 | data class Profile( 17 | var uid: Int = 0, 18 | var toUid: Int = 0, 19 | var scontext: String = APApplication.MAGISK_SCONTEXT, 20 | ) : Parcelable 21 | 22 | @Keep 23 | class KPMCtlRes { 24 | var rc: Long = 0 25 | var outMsg: String? = null 26 | 27 | constructor() 28 | 29 | constructor(rc: Long, outMsg: String?) { 30 | this.rc = rc 31 | this.outMsg = outMsg 32 | } 33 | } 34 | 35 | 36 | private external fun nativeSu(superKey: String, toUid: Int, scontext: String?): Int 37 | 38 | fun su(toUid: Int, scontext: String?): Boolean { 39 | return nativeSu(APApplication.superKey, toUid, scontext) == 0 40 | } 41 | 42 | fun su(): Boolean { 43 | return su(0, "") 44 | } 45 | 46 | external fun nativeReady(superKey: String): Boolean 47 | 48 | private external fun nativeSuPath(superKey: String): String 49 | 50 | fun suPath(): String { 51 | return nativeSuPath(APApplication.superKey) 52 | } 53 | 54 | private external fun nativeSuUids(superKey: String): IntArray 55 | 56 | fun suUids(): IntArray { 57 | return nativeSuUids(APApplication.superKey) 58 | } 59 | 60 | private external fun nativeKernelPatchVersion(superKey: String): Long 61 | fun kernelPatchVersion(): Long { 62 | return nativeKernelPatchVersion(APApplication.superKey) 63 | } 64 | 65 | private external fun nativeLoadKernelPatchModule( 66 | superKey: String, modulePath: String, args: String 67 | ): Long 68 | 69 | fun loadKernelPatchModule(modulePath: String, args: String): Long { 70 | return nativeLoadKernelPatchModule(APApplication.superKey, modulePath, args) 71 | } 72 | 73 | private external fun nativeUnloadKernelPatchModule(superKey: String, moduleName: String): Long 74 | fun unloadKernelPatchModule(moduleName: String): Long { 75 | return nativeUnloadKernelPatchModule(APApplication.superKey, moduleName) 76 | } 77 | 78 | private external fun nativeKernelPatchModuleNum(superKey: String): Long 79 | 80 | fun kernelPatchModuleNum(): Long { 81 | return nativeKernelPatchModuleNum(APApplication.superKey) 82 | } 83 | 84 | private external fun nativeKernelPatchModuleList(superKey: String): String 85 | fun kernelPatchModuleList(): String { 86 | return nativeKernelPatchModuleList(APApplication.superKey) 87 | } 88 | 89 | private external fun nativeKernelPatchModuleInfo(superKey: String, moduleName: String): String 90 | fun kernelPatchModuleInfo(moduleName: String): String { 91 | return nativeKernelPatchModuleInfo(APApplication.superKey, moduleName) 92 | } 93 | 94 | private external fun nativeControlKernelPatchModule( 95 | superKey: String, modName: String, jctlargs: String 96 | ): KPMCtlRes 97 | 98 | fun kernelPatchModuleControl(moduleName: String, controlArg: String): KPMCtlRes { 99 | return nativeControlKernelPatchModule(APApplication.superKey, moduleName, controlArg) 100 | } 101 | 102 | external fun nativeThreadSu(superKey: String, uid: Int, scontext: String?): Long 103 | 104 | private external fun nativeGrantSu( 105 | superKey: String, uid: Int, toUid: Int, scontext: String? 106 | ): Long 107 | 108 | fun grantSu(uid: Int, toUid: Int, scontext: String?): Long { 109 | return nativeGrantSu(APApplication.superKey, uid, toUid, scontext) 110 | } 111 | 112 | private external fun nativeRevokeSu(superKey: String, uid: Int): Long 113 | fun revokeSu(uid: Int): Long { 114 | return nativeRevokeSu(APApplication.superKey, uid) 115 | } 116 | 117 | private external fun nativeSetUidExclude(superKey: String, uid: Int, exclude: Int): Int 118 | fun setUidExclude(uid: Int, exclude: Int): Int { 119 | return nativeSetUidExclude(APApplication.superKey, uid, exclude) 120 | } 121 | 122 | private external fun nativeGetUidExclude(superKey: String, uid: Int): Int 123 | fun isUidExclude(uid: Int): Int { 124 | return nativeGetUidExclude(APApplication.superKey, uid) 125 | } 126 | 127 | private external fun nativeSuProfile(superKey: String, uid: Int): Profile 128 | 129 | fun suProfile(uid: Int): Profile { 130 | return nativeSuProfile(APApplication.superKey, uid) 131 | } 132 | 133 | private external fun nativeResetSuPath(superKey: String, path: String): Boolean 134 | fun resetSuPath(path: String): Boolean { 135 | return nativeResetSuPath(APApplication.superKey, path) 136 | } 137 | 138 | private external fun nativeGetSafeMode(superKey: String): Boolean 139 | fun getSafeMode(): Boolean { 140 | return nativeGetSafeMode(APApplication.superKey) 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/services/RootServices.java: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.services; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.IBinder; 8 | import android.os.UserHandle; 9 | import android.os.UserManager; 10 | import android.util.Log; 11 | 12 | import androidx.annotation.NonNull; 13 | 14 | import com.topjohnwu.superuser.ipc.RootService; 15 | 16 | import java.lang.reflect.Method; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import me.bmax.apatch.IAPRootService; 21 | import rikka.parcelablelist.ParcelableListSlice; 22 | 23 | public class RootServices extends RootService { 24 | private static final String TAG = "RootServices"; 25 | 26 | @Override 27 | public IBinder onBind(@NonNull Intent intent) { 28 | return new Stub(); 29 | } 30 | 31 | List getUserIds() { 32 | List result = new ArrayList<>(); 33 | UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); 34 | List userProfiles = um.getUserProfiles(); 35 | for (UserHandle userProfile : userProfiles) { 36 | int userId = userProfile.hashCode(); 37 | result.add(userProfile.hashCode()); 38 | } 39 | return result; 40 | } 41 | 42 | ArrayList getInstalledPackagesAll(int flags) { 43 | ArrayList packages = new ArrayList<>(); 44 | for (Integer userId : getUserIds()) { 45 | Log.i(TAG, "getInstalledPackagesAll: " + userId); 46 | packages.addAll(getInstalledPackagesAsUser(flags, userId)); 47 | } 48 | return packages; 49 | } 50 | 51 | List getInstalledPackagesAsUser(int flags, int userId) { 52 | try { 53 | PackageManager pm = getPackageManager(); 54 | Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod("getInstalledPackagesAsUser", int.class, int.class); 55 | return (List) getInstalledPackagesAsUser.invoke(pm, flags, userId); 56 | } catch (Throwable e) { 57 | Log.e(TAG, "err", e); 58 | } 59 | 60 | return new ArrayList<>(); 61 | } 62 | 63 | class Stub extends IAPRootService.Stub { 64 | @Override 65 | public ParcelableListSlice getPackages(int flags) { 66 | List list = getInstalledPackagesAll(flags); 67 | Log.i(TAG, "getPackages: " + list.size()); 68 | return new ParcelableListSlice<>(list); 69 | } 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/WebUIActivity.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.ui 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.ActivityManager 5 | import android.os.Build 6 | import android.os.Bundle 7 | import android.view.ViewGroup.MarginLayoutParams 8 | import android.webkit.WebResourceRequest 9 | import android.webkit.WebResourceResponse 10 | import android.webkit.WebView 11 | import android.webkit.WebViewClient 12 | import androidx.activity.ComponentActivity 13 | import androidx.activity.enableEdgeToEdge 14 | import androidx.core.view.ViewCompat 15 | import androidx.core.view.WindowInsetsCompat 16 | import androidx.core.view.updateLayoutParams 17 | import androidx.webkit.WebViewAssetLoader 18 | import me.bmax.apatch.APApplication 19 | import me.bmax.apatch.ui.webui.SuFilePathHandler 20 | import me.bmax.apatch.ui.webui.WebViewInterface 21 | import java.io.File 22 | 23 | @SuppressLint("SetJavaScriptEnabled") 24 | class WebUIActivity : ComponentActivity() { 25 | private lateinit var webViewInterface: WebViewInterface 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | 29 | enableEdgeToEdge() 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 31 | window.isNavigationBarContrastEnforced = false 32 | } 33 | 34 | super.onCreate(savedInstanceState) 35 | 36 | val moduleId = intent.getStringExtra("id")!! 37 | val name = intent.getStringExtra("name")!! 38 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { 39 | @Suppress("DEPRECATION") 40 | setTaskDescription(ActivityManager.TaskDescription("APatch - $name")) 41 | } else { 42 | val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("APatch - $name").build() 43 | setTaskDescription(taskDescription) 44 | } 45 | 46 | val prefs = APApplication.sharedPreferences 47 | WebView.setWebContentsDebuggingEnabled(prefs.getBoolean("enable_web_debugging", false)) 48 | 49 | val webRoot = File("/data/adb/modules/${moduleId}/webroot") 50 | val webViewAssetLoader = WebViewAssetLoader.Builder() 51 | .setDomain("mui.kernelsu.org") 52 | .addPathHandler( 53 | "/", 54 | SuFilePathHandler(this, webRoot) 55 | ) 56 | .build() 57 | 58 | val webViewClient = object : WebViewClient() { 59 | override fun shouldInterceptRequest( 60 | view: WebView, 61 | request: WebResourceRequest 62 | ): WebResourceResponse? { 63 | return webViewAssetLoader.shouldInterceptRequest(request.url) 64 | } 65 | } 66 | 67 | val webView = WebView(this).apply { 68 | ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets -> 69 | val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()) 70 | view.updateLayoutParams { 71 | leftMargin = inset.left 72 | rightMargin = inset.right 73 | topMargin = inset.top 74 | bottomMargin = inset.bottom 75 | } 76 | return@setOnApplyWindowInsetsListener insets 77 | } 78 | settings.javaScriptEnabled = true 79 | settings.domStorageEnabled = true 80 | settings.allowFileAccess = false 81 | webViewInterface = WebViewInterface(this@WebUIActivity, this) 82 | addJavascriptInterface(webViewInterface, "ksu") 83 | setWebViewClient(webViewClient) 84 | loadUrl("https://mui.kernelsu.org/index.html") 85 | } 86 | 87 | setContentView(webView) 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/component/DropdownMenu.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.ui.component 2 | 3 | import androidx.compose.foundation.shape.CornerBasedShape 4 | import androidx.compose.foundation.shape.RoundedCornerShape 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.unit.dp 8 | 9 | @Composable 10 | fun ProvideMenuShape( 11 | value: CornerBasedShape = RoundedCornerShape(8.dp), content: @Composable () -> Unit 12 | ) = MaterialTheme( 13 | shapes = MaterialTheme.shapes.copy(extraSmall = value), content = content 14 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/component/KeyEventBlocker.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.ui.component 2 | 3 | import androidx.compose.foundation.focusable 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.LaunchedEffect 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.focus.FocusRequester 10 | import androidx.compose.ui.focus.focusRequester 11 | import androidx.compose.ui.input.key.KeyEvent 12 | import androidx.compose.ui.input.key.onKeyEvent 13 | 14 | @Composable 15 | fun KeyEventBlocker(predicate: (KeyEvent) -> Boolean) { 16 | val requester = remember { FocusRequester() } 17 | Box( 18 | Modifier 19 | .onKeyEvent { 20 | predicate(it) 21 | } 22 | .focusRequester(requester) 23 | .focusable() 24 | ) 25 | LaunchedEffect(Unit) { 26 | requester.requestFocus() 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/component/ModuleCardComponents.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.ui.component 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.layout.PaddingValues 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.requiredSize 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.foundation.layout.width 10 | import androidx.compose.material3.FilledTonalButton 11 | import androidx.compose.material3.Icon 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.graphics.ColorFilter 18 | import androidx.compose.ui.res.painterResource 19 | import androidx.compose.ui.res.stringResource 20 | import androidx.compose.ui.text.style.TextOverflow 21 | import androidx.compose.ui.unit.dp 22 | import me.bmax.apatch.R 23 | 24 | @Composable 25 | fun ModuleUpdateButton( 26 | onClick: () -> Unit 27 | ) = FilledTonalButton( 28 | onClick = onClick, enabled = true, contentPadding = PaddingValues(horizontal = 12.dp) 29 | ) { 30 | Icon( 31 | modifier = Modifier.size(20.dp), 32 | painter = painterResource(id = R.drawable.device_mobile_down), 33 | contentDescription = null 34 | ) 35 | 36 | Spacer(modifier = Modifier.width(6.dp)) 37 | Text( 38 | text = stringResource(id = R.string.apm_update), 39 | maxLines = 1, 40 | overflow = TextOverflow.Visible, 41 | softWrap = false 42 | ) 43 | } 44 | 45 | @Composable 46 | fun ModuleRemoveButton( 47 | enabled: Boolean, onClick: () -> Unit 48 | ) = FilledTonalButton( 49 | onClick = onClick, enabled = enabled, contentPadding = PaddingValues(horizontal = 12.dp) 50 | ) { 51 | Icon( 52 | modifier = Modifier.size(20.dp), 53 | painter = painterResource(id = R.drawable.trash), 54 | contentDescription = null 55 | ) 56 | 57 | Spacer(modifier = Modifier.width(6.dp)) 58 | Text( 59 | text = stringResource(id = R.string.apm_remove), 60 | maxLines = 1, 61 | overflow = TextOverflow.Visible, 62 | softWrap = false 63 | ) 64 | } 65 | 66 | @Composable 67 | fun KPModuleRemoveButton( 68 | enabled: Boolean, onClick: () -> Unit 69 | ) = FilledTonalButton( 70 | onClick = onClick, enabled = enabled, contentPadding = PaddingValues(horizontal = 12.dp) 71 | ) { 72 | Icon( 73 | modifier = Modifier.size(20.dp), 74 | painter = painterResource(id = R.drawable.trash), 75 | contentDescription = null 76 | ) 77 | 78 | Spacer(modifier = Modifier.width(6.dp)) 79 | Text( 80 | text = stringResource(id = R.string.kpm_unload), 81 | maxLines = 1, 82 | overflow = TextOverflow.Visible, 83 | softWrap = false 84 | ) 85 | } 86 | 87 | @Composable 88 | fun ModuleStateIndicator( 89 | @DrawableRes icon: Int, color: Color = MaterialTheme.colorScheme.outline 90 | ) { 91 | Image( 92 | modifier = Modifier.requiredSize(150.dp), 93 | painter = painterResource(id = icon), 94 | contentDescription = null, 95 | alpha = 0.1f, 96 | colorFilter = ColorFilter.tint(color) 97 | ) 98 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/component/SettingsItem.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.ui.component 2 | 3 | import androidx.compose.foundation.LocalIndication 4 | import androidx.compose.foundation.interaction.MutableInteractionSource 5 | import androidx.compose.foundation.selection.toggleable 6 | import androidx.compose.material3.Icon 7 | import androidx.compose.material3.ListItem 8 | import androidx.compose.material3.LocalContentColor 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.material3.RadioButton 11 | import androidx.compose.material3.Switch 12 | import androidx.compose.material3.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.remember 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.vector.ImageVector 17 | import androidx.compose.ui.semantics.Role 18 | 19 | @Composable 20 | fun SwitchItem( 21 | icon: ImageVector? = null, 22 | title: String, 23 | summary: String? = null, 24 | checked: Boolean, 25 | enabled: Boolean = true, 26 | onCheckedChange: (Boolean) -> Unit 27 | ) { 28 | val interactionSource = remember { MutableInteractionSource() } 29 | 30 | ListItem( 31 | modifier = Modifier.toggleable( 32 | value = checked, 33 | interactionSource = interactionSource, 34 | role = Role.Switch, 35 | enabled = enabled, 36 | indication = LocalIndication.current, 37 | onValueChange = onCheckedChange 38 | ), 39 | headlineContent = { 40 | Text( 41 | title, 42 | style = MaterialTheme.typography.bodyLarge, 43 | color = LocalContentColor.current 44 | ) 45 | }, 46 | leadingContent = icon?.let { 47 | { Icon(icon, title) } 48 | }, 49 | trailingContent = { 50 | Switch( 51 | checked = checked, 52 | enabled = enabled, 53 | onCheckedChange = onCheckedChange, 54 | interactionSource = interactionSource 55 | ) 56 | }, 57 | supportingContent = { 58 | if (summary != null) { 59 | Text( 60 | summary, 61 | style = MaterialTheme.typography.bodyMedium, 62 | color = MaterialTheme.colorScheme.outline 63 | ) 64 | } 65 | } 66 | ) 67 | } 68 | 69 | @Composable 70 | fun RadioItem( 71 | title: String, 72 | selected: Boolean, 73 | onClick: () -> Unit, 74 | ) { 75 | ListItem( 76 | headlineContent = { 77 | Text(title) 78 | }, 79 | leadingContent = { 80 | RadioButton(selected = selected, onClick = onClick) 81 | }, 82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/screen/BottomBarDestination.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.ui.screen 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.filled.Apps 6 | import androidx.compose.material.icons.filled.Build 7 | import androidx.compose.material.icons.filled.Home 8 | import androidx.compose.material.icons.filled.Security 9 | import androidx.compose.material.icons.filled.Settings 10 | import androidx.compose.material.icons.outlined.Apps 11 | import androidx.compose.material.icons.outlined.Build 12 | import androidx.compose.material.icons.outlined.Home 13 | import androidx.compose.material.icons.outlined.Security 14 | import androidx.compose.material.icons.outlined.Settings 15 | import androidx.compose.ui.graphics.vector.ImageVector 16 | import com.ramcosta.composedestinations.generated.destinations.APModuleScreenDestination 17 | import com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination 18 | import com.ramcosta.composedestinations.generated.destinations.KPModuleScreenDestination 19 | import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination 20 | import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination 21 | import com.ramcosta.composedestinations.spec.DirectionDestinationSpec 22 | import me.bmax.apatch.R 23 | 24 | enum class BottomBarDestination( 25 | val direction: DirectionDestinationSpec, 26 | @StringRes val label: Int, 27 | val iconSelected: ImageVector, 28 | val iconNotSelected: ImageVector, 29 | val kPatchRequired: Boolean, 30 | val aPatchRequired: Boolean, 31 | ) { 32 | Home( 33 | HomeScreenDestination, 34 | R.string.home, 35 | Icons.Filled.Home, 36 | Icons.Outlined.Home, 37 | false, 38 | false 39 | ), 40 | /*KModule( 41 | KPModuleScreenDestination, 42 | R.string.kpm, 43 | Icons.Filled.Build, 44 | Icons.Outlined.Build, 45 | true, 46 | false 47 | ),*/ 48 | SuperUser( 49 | SuperUserScreenDestination, 50 | R.string.su_title, 51 | Icons.Filled.Security, 52 | Icons.Outlined.Security, 53 | true, 54 | false 55 | ), 56 | AModule( 57 | APModuleScreenDestination, 58 | R.string.apm, 59 | Icons.Filled.Apps, 60 | Icons.Outlined.Apps, 61 | false, 62 | true 63 | ), 64 | Settings( 65 | SettingScreenDestination, 66 | R.string.settings, 67 | Icons.Filled.Settings, 68 | Icons.Outlined.Settings, 69 | false, 70 | false 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.ui.theme 2 | 3 | import androidx.compose.ui.text.TextStyle 4 | import androidx.compose.ui.text.font.FontFamily 5 | import androidx.compose.ui.text.font.FontWeight 6 | import androidx.compose.ui.unit.sp 7 | 8 | // Set of Material typography styles to start with 9 | val Typography = androidx.compose.material3.Typography( 10 | bodyLarge = TextStyle( 11 | fontFamily = FontFamily.Default, 12 | fontWeight = FontWeight.Normal, 13 | fontSize = 16.sp, 14 | lineHeight = 24.sp, 15 | letterSpacing = 0.5.sp 16 | ) 17 | /* Other default text styles to override 18 | titleLarge = TextStyle( 19 | fontFamily = FontFamily.Default, 20 | fontWeight = FontWeight.Normal, 21 | fontSize = 22.sp, 22 | lineHeight = 28.sp, 23 | letterSpacing = 0.sp 24 | ), 25 | labelSmall = TextStyle( 26 | fontFamily = FontFamily.Default, 27 | fontWeight = FontWeight.Medium, 28 | fontSize = 11.sp, 29 | lineHeight = 16.sp, 30 | letterSpacing = 0.5.sp 31 | ) 32 | */ 33 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/viewmodel/KPModel.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.ui.viewmodel 2 | 3 | import android.os.Parcelable 4 | import androidx.annotation.Keep 5 | import androidx.compose.runtime.Immutable 6 | import kotlinx.parcelize.Parcelize 7 | 8 | object KPModel { 9 | 10 | enum class TriggerEvent(val event: String) { 11 | PAGING_INIT("paging-init"), 12 | PRE_KERNEL_INIT("pre-kernel-init"), 13 | POST_KERNEL_INIT("post-kernel-init"), 14 | } 15 | 16 | 17 | enum class ExtraType(val desc: String) { 18 | NONE("none"), 19 | KPM("kpm"), 20 | SHELL("shell"), 21 | EXEC("exec"), 22 | RAW("raw"), 23 | ANDROID_RC("android_rc"); 24 | } 25 | 26 | interface IExtraInfo : Parcelable { 27 | var type: ExtraType 28 | var name: String 29 | var event: String 30 | var args: String 31 | } 32 | 33 | @Immutable 34 | @Parcelize 35 | @Keep 36 | data class KPMInfo( 37 | override var type: ExtraType, 38 | override var name: String, 39 | override var event: String, 40 | override var args: String, 41 | var version: String, 42 | var license: String, 43 | var author: String, 44 | var description: String, 45 | ) : IExtraInfo 46 | 47 | @Immutable 48 | @Parcelize 49 | @Keep 50 | data class KPImgInfo( 51 | var version: String, 52 | var compileTime: String, 53 | var config: String, 54 | var superKey: String, 55 | var rootSuperkey: String 56 | ) : Parcelable 57 | 58 | @Immutable 59 | @Parcelize 60 | @Keep 61 | data class KImgInfo( 62 | var banner: String, 63 | var patched: Boolean, 64 | ) : Parcelable 65 | 66 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/viewmodel/KPModuleViewModel.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.ui.viewmodel 2 | 3 | import android.os.SystemClock 4 | import android.util.Log 5 | import androidx.compose.runtime.derivedStateOf 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.mutableStateOf 8 | import androidx.compose.runtime.setValue 9 | import androidx.lifecycle.ViewModel 10 | import androidx.lifecycle.viewModelScope 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.launch 13 | import me.bmax.apatch.Natives 14 | import java.text.Collator 15 | import java.util.Locale 16 | 17 | class KPModuleViewModel : ViewModel() { 18 | companion object { 19 | private const val TAG = "KPModuleViewModel" 20 | private var modules by mutableStateOf>(emptyList()) 21 | } 22 | 23 | var isRefreshing by mutableStateOf(false) 24 | private set 25 | 26 | val moduleList by derivedStateOf { 27 | val comparator = compareBy(Collator.getInstance(Locale.getDefault()), KPModel.KPMInfo::name) 28 | modules.sortedWith(comparator).also { 29 | isRefreshing = false 30 | } 31 | } 32 | 33 | var isNeedRefresh by mutableStateOf(false) 34 | private set 35 | 36 | fun markNeedRefresh() { 37 | isNeedRefresh = true 38 | } 39 | 40 | fun fetchModuleList() { 41 | viewModelScope.launch(Dispatchers.IO) { 42 | isRefreshing = true 43 | val oldModuleList = modules 44 | val start = SystemClock.elapsedRealtime() 45 | 46 | kotlin.runCatching { 47 | var names = Natives.kernelPatchModuleList() 48 | if (Natives.kernelPatchModuleNum() <= 0) 49 | names = "" 50 | val nameList = names.split('\n').toList() 51 | Log.d(TAG, "kpm list: $nameList") 52 | modules = nameList.filter { it.isNotEmpty() }.map { 53 | val infoline = Natives.kernelPatchModuleInfo(it) 54 | val spi = infoline.split('\n') 55 | val name = spi.find { it.startsWith("name=") }?.removePrefix("name=") 56 | val version = spi.find { it.startsWith("version=") }?.removePrefix("version=") 57 | val license = spi.find { it.startsWith("license=") }?.removePrefix("license=") 58 | val author = spi.find { it.startsWith("author=") }?.removePrefix("author=") 59 | val description = 60 | spi.find { it.startsWith("description=") }?.removePrefix("description=") 61 | val args = spi.find { it.startsWith("args=") }?.removePrefix("args=") 62 | val info = KPModel.KPMInfo( 63 | KPModel.ExtraType.KPM, 64 | name ?: "", 65 | "", 66 | args ?: "", 67 | version ?: "", 68 | license ?: "", 69 | author ?: "", 70 | description ?: "" 71 | ) 72 | info 73 | } 74 | isNeedRefresh = false 75 | }.onFailure { e -> 76 | Log.e(TAG, "fetchModuleList: ", e) 77 | isRefreshing = false 78 | } 79 | 80 | // when both old and new is kotlin.collections.EmptyList 81 | // moduleList update will don't trigger 82 | if (oldModuleList === modules) { 83 | isRefreshing = false 84 | } 85 | 86 | Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules") 87 | } 88 | } 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/ui/webui/MimeUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.bmax.apatch.ui.webui; 18 | 19 | import java.net.URLConnection; 20 | 21 | class MimeUtil { 22 | 23 | public static String getMimeFromFileName(String fileName) { 24 | if (fileName == null) { 25 | return null; 26 | } 27 | 28 | // Copying the logic and mapping that Chromium follows. 29 | // First we check against the OS (this is a limited list by default) 30 | // but app developers can extend this. 31 | // We then check against a list of hardcoded mime types above if the 32 | // OS didn't provide a result. 33 | String mimeType = URLConnection.guessContentTypeFromName(fileName); 34 | 35 | if (mimeType != null) { 36 | return mimeType; 37 | } 38 | 39 | return guessHardcodedMime(fileName); 40 | } 41 | 42 | // We should keep this map in sync with the lists under 43 | // //net/base/mime_util.cc in Chromium. 44 | // A bunch of the mime types don't really apply to Android land 45 | // like word docs so feel free to filter out where necessary. 46 | private static String guessHardcodedMime(String fileName) { 47 | int finalFullStop = fileName.lastIndexOf('.'); 48 | if (finalFullStop == -1) { 49 | return null; 50 | } 51 | 52 | final String extension = fileName.substring(finalFullStop + 1).toLowerCase(); 53 | 54 | switch (extension) { 55 | case "webm": 56 | return "video/webm"; 57 | case "mpeg": 58 | case "mpg": 59 | return "video/mpeg"; 60 | case "mp3": 61 | return "audio/mpeg"; 62 | case "wasm": 63 | return "application/wasm"; 64 | case "xhtml": 65 | case "xht": 66 | case "xhtm": 67 | return "application/xhtml+xml"; 68 | case "flac": 69 | return "audio/flac"; 70 | case "ogg": 71 | case "oga": 72 | case "opus": 73 | return "audio/ogg"; 74 | case "wav": 75 | return "audio/wav"; 76 | case "m4a": 77 | return "audio/x-m4a"; 78 | case "gif": 79 | return "image/gif"; 80 | case "jpeg": 81 | case "jpg": 82 | case "jfif": 83 | case "pjpeg": 84 | case "pjp": 85 | return "image/jpeg"; 86 | case "png": 87 | return "image/png"; 88 | case "apng": 89 | return "image/apng"; 90 | case "svg": 91 | case "svgz": 92 | return "image/svg+xml"; 93 | case "webp": 94 | return "image/webp"; 95 | case "mht": 96 | case "mhtml": 97 | return "multipart/related"; 98 | case "css": 99 | return "text/css"; 100 | case "html": 101 | case "htm": 102 | case "shtml": 103 | case "shtm": 104 | case "ehtml": 105 | return "text/html"; 106 | case "js": 107 | case "mjs": 108 | return "application/javascript"; 109 | case "xml": 110 | return "text/xml"; 111 | case "mp4": 112 | case "m4v": 113 | return "video/mp4"; 114 | case "ogv": 115 | case "ogm": 116 | return "video/ogg"; 117 | case "ico": 118 | return "image/x-icon"; 119 | case "woff": 120 | return "application/font-woff"; 121 | case "gz": 122 | case "tgz": 123 | return "application/gzip"; 124 | case "json": 125 | return "application/json"; 126 | case "pdf": 127 | return "application/pdf"; 128 | case "zip": 129 | return "application/zip"; 130 | case "bmp": 131 | return "image/bmp"; 132 | case "tiff": 133 | case "tif": 134 | return "image/tiff"; 135 | default: 136 | return null; 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/DeviceInfoUtils.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util 2 | 3 | import android.util.Log 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.res.stringResource 6 | import com.topjohnwu.superuser.Shell 7 | import me.bmax.apatch.R 8 | 9 | @Composable 10 | fun getSELinuxStatus(): String { 11 | val shell = Shell.Builder.create() 12 | .build("sh") 13 | 14 | val list = ArrayList() 15 | val result = shell.newJob().add("getenforce").to(list, list).exec() 16 | val output = result.out.joinToString("\n").trim() 17 | 18 | if (result.isSuccess) { 19 | return when (output) { 20 | "Enforcing" -> stringResource(R.string.home_selinux_status_enforcing) 21 | "Permissive" -> stringResource(R.string.home_selinux_status_permissive) 22 | "Disabled" -> stringResource(R.string.home_selinux_status_disabled) 23 | else -> stringResource(R.string.home_selinux_status_unknown) 24 | } 25 | } 26 | 27 | return if (output.endsWith("Permission denied")) { 28 | stringResource(R.string.home_selinux_status_enforcing) 29 | } else { 30 | stringResource(R.string.home_selinux_status_unknown) 31 | } 32 | } 33 | 34 | private fun getSystemProperty(key: String): Boolean { 35 | try { 36 | val c = Class.forName("android.os.SystemProperties") 37 | val get = c.getMethod( 38 | "getBoolean", 39 | String::class.java, 40 | Boolean::class.javaPrimitiveType 41 | ) 42 | return get.invoke(c, key, false) as Boolean 43 | } catch (e: Exception) { 44 | Log.e("APatch", "[DeviceUtils] Failed to get system property: ", e) 45 | } 46 | return false 47 | } 48 | 49 | // Check to see if device supports A/B (seamless) system updates 50 | fun isABDevice(): Boolean { 51 | return getSystemProperty("ro.build.ab_update") 52 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/IOStreamUtils.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util 2 | 3 | import android.content.ContentResolver 4 | import android.net.Uri 5 | import me.bmax.apatch.apApp 6 | import java.io.File 7 | import java.io.FileNotFoundException 8 | import java.io.InputStream 9 | import java.io.OutputStream 10 | 11 | 12 | val cr: ContentResolver get() = apApp.contentResolver 13 | 14 | fun Uri.inputStream() = cr.openInputStream(this) ?: throw FileNotFoundException() 15 | 16 | fun Uri.outputStream() = cr.openOutputStream(this, "rwt") ?: throw FileNotFoundException() 17 | 18 | fun Uri.fileDescriptor(mode: String) = 19 | cr.openFileDescriptor(this, mode) ?: throw FileNotFoundException() 20 | 21 | inline fun withStreams( 22 | inStream: In, 23 | outStream: Out, 24 | withBoth: (In, Out) -> Unit 25 | ) { 26 | inStream.use { reader -> 27 | outStream.use { writer -> 28 | withBoth(reader, writer) 29 | } 30 | } 31 | } 32 | 33 | fun InputStream.copyAndClose(out: OutputStream) = withStreams(this, out) { i, o -> i.copyTo(o) } 34 | fun InputStream.writeTo(file: File) = copyAndClose(file.outputStream()) 35 | 36 | fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyTo(it) } 37 | -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/LatestVersionInfo.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util 2 | 3 | data class LatestVersionInfo( 4 | val versionCode: Int = 0, val downloadUrl: String = "", val changelog: String = "" 5 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/LogEvent.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import android.system.Os 6 | import com.topjohnwu.superuser.ShellUtils 7 | import java.io.File 8 | import java.io.FileWriter 9 | import java.io.PrintWriter 10 | import java.time.LocalDateTime 11 | import java.time.format.DateTimeFormatter 12 | 13 | fun getBugreportFile(context: Context): File { 14 | 15 | val bugreportDir = File(context.cacheDir, "bugreport") 16 | bugreportDir.mkdirs() 17 | 18 | val dmesgFile = File(bugreportDir, "dmesg.txt") 19 | val logcatFile = File(bugreportDir, "logcat.txt") 20 | val tombstonesFile = File(bugreportDir, "tombstones.tar.gz") 21 | val dropboxFile = File(bugreportDir, "dropbox.tar.gz") 22 | val pstoreFile = File(bugreportDir, "pstore.tar.gz") 23 | val diagFile = File(bugreportDir, "diag.tar.gz") 24 | val bootlogFile = File(bugreportDir, "bootlog.tar.gz") 25 | val mountsFile = File(bugreportDir, "mounts.txt") 26 | val fileSystemsFile = File(bugreportDir, "filesystems.txt") 27 | val apFileTree = File(bugreportDir, "ap_tree.txt") 28 | val appListFile = File(bugreportDir, "packages.txt") 29 | val propFile = File(bugreportDir, "props.txt") 30 | val packageConfigFile = File(bugreportDir, "package_config") 31 | 32 | val shell = tryGetRootShell() 33 | 34 | shell.newJob().add("dmesg > ${dmesgFile.absolutePath}").exec() 35 | shell.newJob().add("logcat -d > ${logcatFile.absolutePath}").exec() 36 | shell.newJob().add("tar -czf ${tombstonesFile.absolutePath} -C /data/tombstones .").exec() 37 | shell.newJob().add("tar -czf ${dropboxFile.absolutePath} -C /data/system/dropbox .").exec() 38 | shell.newJob().add("tar -czf ${pstoreFile.absolutePath} -C /sys/fs/pstore .").exec() 39 | shell.newJob().add("tar -czf ${diagFile.absolutePath} -C /data/vendor/diag .").exec() 40 | shell.newJob().add("tar -czf ${bootlogFile.absolutePath} -C /data/adb/ap/log .").exec() 41 | 42 | shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec() 43 | shell.newJob().add("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec() 44 | shell.newJob().add("ls -alRZ /data/adb > ${apFileTree.absolutePath}").exec() 45 | shell.newJob().add("cp /data/system/packages.list ${appListFile.absolutePath}").exec() 46 | shell.newJob().add("getprop > ${propFile.absolutePath}").exec() 47 | shell.newJob().add("cp /data/adb/ap/package_config ${packageConfigFile.absolutePath}").exec() 48 | 49 | val selinux = ShellUtils.fastCmd(shell, "getenforce") 50 | 51 | // basic information 52 | val buildInfo = File(bugreportDir, "basic.txt") 53 | PrintWriter(FileWriter(buildInfo)).use { pw -> 54 | pw.println("Kernel: ${System.getProperty("os.version")}") 55 | pw.println("BRAND: " + Build.BRAND) 56 | pw.println("MODEL: " + Build.MODEL) 57 | pw.println("PRODUCT: " + Build.PRODUCT) 58 | pw.println("MANUFACTURER: " + Build.MANUFACTURER) 59 | pw.println("SDK: " + Build.VERSION.SDK_INT) 60 | pw.println("PREVIEW_SDK: " + Build.VERSION.PREVIEW_SDK_INT) 61 | pw.println("FINGERPRINT: " + Build.FINGERPRINT) 62 | pw.println("DEVICE: " + Build.DEVICE) 63 | pw.println("Manager: " + Version.getManagerVersion()) 64 | pw.println("SELinux: $selinux") 65 | 66 | val uname = Os.uname() 67 | pw.println("KernelRelease: ${uname.release}") 68 | pw.println("KernelVersion: ${uname.version}") 69 | pw.println("Mahcine: ${uname.machine}") 70 | pw.println("Nodename: ${uname.nodename}") 71 | pw.println("Sysname: ${uname.sysname}") 72 | 73 | pw.println("KPatch: ${Version.installedKPVString()}") 74 | pw.println("APatch: ${Version.installedApdVString}") 75 | val safeMode = false 76 | pw.println("SafeMode: $safeMode") 77 | } 78 | 79 | // modules 80 | val modulesFile = File(bugreportDir, "modules.json") 81 | modulesFile.writeText(listModules()) 82 | 83 | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm") 84 | val current = LocalDateTime.now().format(formatter) 85 | 86 | val targetFile = File(context.cacheDir, "APatch_bugreport_${current}.tar.gz") 87 | 88 | shell.newJob().add("tar czf ${targetFile.absolutePath} -C ${bugreportDir.absolutePath} .") 89 | .exec() 90 | shell.newJob().add("rm -rf ${bugreportDir.absolutePath}").exec() 91 | shell.newJob().add("chmod 0644 ${targetFile.absolutePath}").exec() 92 | 93 | return targetFile 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/PkgConfig.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util 2 | 3 | import android.os.Parcelable 4 | import android.util.Log 5 | import androidx.annotation.Keep 6 | import androidx.compose.runtime.Immutable 7 | import kotlinx.parcelize.Parcelize 8 | import me.bmax.apatch.APApplication 9 | import me.bmax.apatch.Natives 10 | import java.io.File 11 | import java.io.FileWriter 12 | import kotlin.concurrent.thread 13 | 14 | object PkgConfig { 15 | private const val TAG = "PkgConfig" 16 | 17 | private const val CSV_HEADER = "pkg,exclude,allow,uid,to_uid,sctx" 18 | 19 | @Immutable 20 | @Parcelize 21 | @Keep 22 | data class Config( 23 | var pkg: String = "", var exclude: Int = 0, var allow: Int = 0, var profile: Natives.Profile 24 | ) : Parcelable { 25 | companion object { 26 | fun fromLine(line: String): Config { 27 | val sp = line.split(",") 28 | val profile = Natives.Profile(sp[3].toInt(), sp[4].toInt(), sp[5]) 29 | return Config(sp[0], sp[1].toInt(), sp[2].toInt(), profile) 30 | } 31 | } 32 | 33 | fun isDefault(): Boolean { 34 | return allow == 0 && exclude == 0 35 | } 36 | 37 | fun toLine(): String { 38 | return "${pkg},${exclude},${allow},${profile.uid},${profile.toUid},${profile.scontext}" 39 | } 40 | } 41 | 42 | fun readConfigs(): HashMap { 43 | val configs = HashMap() 44 | val file = File(APApplication.PACKAGE_CONFIG_FILE) 45 | if (file.exists()) { 46 | file.readLines().drop(1).filter { it.isNotEmpty() }.forEach { 47 | Log.d(TAG, it) 48 | val p = Config.fromLine(it) 49 | if (!p.isDefault()) { 50 | configs[p.profile.uid] = p 51 | } 52 | } 53 | } 54 | return configs 55 | } 56 | 57 | private fun writeConfigs(configs: HashMap) { 58 | val file = File(APApplication.PACKAGE_CONFIG_FILE) 59 | if (!file.parentFile?.exists()!!) file.parentFile?.mkdirs() 60 | val writer = FileWriter(file, false) 61 | writer.write(CSV_HEADER + '\n') 62 | configs.values.forEach { 63 | if (!it.isDefault()) { 64 | writer.write(it.toLine() + '\n') 65 | } 66 | } 67 | writer.flush() 68 | writer.close() 69 | } 70 | 71 | fun changeConfig(config: Config) { 72 | thread { 73 | synchronized(PkgConfig.javaClass) { 74 | Natives.su() 75 | val configs = readConfigs() 76 | val uid = config.profile.uid 77 | // Root App should not be excluded 78 | if (config.allow == 1) { 79 | config.exclude = 0 80 | } 81 | if (config.allow == 0 && configs[uid] != null && config.exclude != 0) { 82 | configs.remove(uid) 83 | } else { 84 | Log.d(TAG, "change config: $config") 85 | configs[uid] = config 86 | } 87 | writeConfigs(configs) 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/Version.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util 2 | 3 | import android.util.Log 4 | import androidx.core.content.pm.PackageInfoCompat 5 | import me.bmax.apatch.APApplication 6 | import me.bmax.apatch.BuildConfig 7 | import me.bmax.apatch.Natives 8 | import me.bmax.apatch.apApp 9 | 10 | /** 11 | * version string is like 0.9.0 or 0.9.0-dev 12 | * version uint is hex number like: 0x000900 13 | */ 14 | object Version { 15 | 16 | private fun string2UInt(ver: String): UInt { 17 | val v = ver.trim().split("-")[0] 18 | val vn = v.split('.') 19 | val vi = vn[0].toInt().shl(16) + vn[1].toInt().shl(8) + vn[2].toInt() 20 | return vi.toUInt() 21 | } 22 | 23 | fun uInt2String(ver: UInt): String { 24 | return "%d.%d.%d".format( 25 | ver.and(0xff0000u).shr(16).toInt(), 26 | ver.and(0xff00u).shr(8).toInt(), 27 | ver.and(0xffu).toInt() 28 | ) 29 | } 30 | 31 | fun buildKPVUInt(): UInt { 32 | val buildVS = BuildConfig.buildKPV 33 | return string2UInt(buildVS) 34 | } 35 | 36 | fun buildKPVString(): String { 37 | return BuildConfig.buildKPV 38 | } 39 | 40 | /** 41 | * installed KernelPatch version (installed kpimg) 42 | */ 43 | fun installedKPVUInt(): UInt { 44 | return Natives.kernelPatchVersion().toUInt() 45 | } 46 | 47 | fun installedKPVString(): String { 48 | return uInt2String(installedKPVUInt()) 49 | } 50 | 51 | 52 | private fun installedKPatchVString(): String { 53 | val resultShell = rootShellForResult("${APApplication.APD_PATH} -V") 54 | val result = resultShell.out.toString() 55 | return result.trim().ifEmpty { "0" } 56 | } 57 | 58 | fun installedKPatchVUInt(): UInt { 59 | return installedKPatchVString().trim().toUInt(0x10) 60 | } 61 | 62 | private fun installedApdVString(): String { 63 | val resultShell = rootShellForResult("${APApplication.APD_PATH} -V") 64 | installedApdVString = if (resultShell.isSuccess) { 65 | val result = resultShell.out.toString() 66 | Log.i("APatch", "[installedApdVString@Version] resultFromShell: ${result}") 67 | Regex("\\d+").find(result)!!.value 68 | } else { 69 | "0" 70 | } 71 | return installedApdVString 72 | } 73 | 74 | fun installedApdVUInt(): Int { 75 | installedApdVInt = installedApdVString().toInt() 76 | return installedApdVInt 77 | } 78 | 79 | 80 | fun getManagerVersion(): Pair { 81 | val packageInfo = apApp.packageManager.getPackageInfo(apApp.packageName, 0)!! 82 | val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo) 83 | return Pair(packageInfo.versionName!!, versionCode) 84 | } 85 | 86 | var installedApdVInt: Int = 0 87 | var installedApdVString: String = "0" 88 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/apksign/ByteArrayStream.java: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util.apksign; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | public class ByteArrayStream extends ByteArrayOutputStream { 9 | 10 | public synchronized void readFrom(InputStream is) { 11 | readFrom(is, Integer.MAX_VALUE); 12 | } 13 | 14 | public synchronized void readFrom(InputStream is, int len) { 15 | int read; 16 | byte[] buffer = new byte[4096]; 17 | try { 18 | while ((read = is.read(buffer, 0, Math.min(len, buffer.length))) > 0) { 19 | write(buffer, 0, read); 20 | len -= read; 21 | } 22 | } catch (IOException e) { 23 | e.printStackTrace(); 24 | } 25 | } 26 | 27 | public ByteArrayInputStream getInputStream() { 28 | return new ByteArrayInputStream(buf, 0, count); 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/apksign/JarMap.java: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util.apksign; 2 | 3 | import java.io.Closeable; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | import java.util.Collections; 9 | import java.util.Enumeration; 10 | import java.util.LinkedHashMap; 11 | import java.util.jar.JarEntry; 12 | import java.util.jar.JarFile; 13 | import java.util.jar.JarInputStream; 14 | import java.util.jar.Manifest; 15 | import java.util.zip.ZipEntry; 16 | import java.util.zip.ZipFile; 17 | 18 | public abstract class JarMap implements Closeable { 19 | 20 | LinkedHashMap entryMap; 21 | 22 | public static JarMap open(File file, boolean verify) throws IOException { 23 | return new FileMap(file, verify, ZipFile.OPEN_READ); 24 | } 25 | 26 | public static JarMap open(InputStream is, boolean verify) throws IOException { 27 | return new StreamMap(is, verify); 28 | } 29 | 30 | public File getFile() { 31 | return null; 32 | } 33 | 34 | public abstract Manifest getManifest() throws IOException; 35 | 36 | public InputStream getInputStream(ZipEntry ze) throws IOException { 37 | JarMapEntry e = getMapEntry(ze.getName()); 38 | return e != null ? e.data.getInputStream() : null; 39 | } 40 | 41 | public OutputStream getOutputStream(ZipEntry ze) { 42 | if (entryMap == null) 43 | entryMap = new LinkedHashMap<>(); 44 | JarMapEntry e = new JarMapEntry(ze.getName()); 45 | entryMap.put(ze.getName(), e); 46 | return e.data; 47 | } 48 | 49 | public byte[] getRawData(ZipEntry ze) throws IOException { 50 | JarMapEntry e = getMapEntry(ze.getName()); 51 | return e != null ? e.data.toByteArray() : null; 52 | } 53 | 54 | public abstract Enumeration entries(); 55 | 56 | public final ZipEntry getEntry(String name) { 57 | return getJarEntry(name); 58 | } 59 | 60 | public JarEntry getJarEntry(String name) { 61 | return getMapEntry(name); 62 | } 63 | 64 | JarMapEntry getMapEntry(String name) { 65 | JarMapEntry e = null; 66 | if (entryMap != null) 67 | e = (JarMapEntry) entryMap.get(name); 68 | return e; 69 | } 70 | 71 | private static class FileMap extends JarMap { 72 | 73 | private final JarFile jarFile; 74 | 75 | FileMap(File file, boolean verify, int mode) throws IOException { 76 | jarFile = new JarFile(file, verify, mode); 77 | } 78 | 79 | @Override 80 | public File getFile() { 81 | return new File(jarFile.getName()); 82 | } 83 | 84 | @Override 85 | public Manifest getManifest() throws IOException { 86 | return jarFile.getManifest(); 87 | } 88 | 89 | @Override 90 | public InputStream getInputStream(ZipEntry ze) throws IOException { 91 | InputStream is = super.getInputStream(ze); 92 | return is != null ? is : jarFile.getInputStream(ze); 93 | } 94 | 95 | @Override 96 | public byte[] getRawData(ZipEntry ze) throws IOException { 97 | byte[] b = super.getRawData(ze); 98 | if (b != null) 99 | return b; 100 | ByteArrayStream bytes = new ByteArrayStream(); 101 | bytes.readFrom(jarFile.getInputStream(ze)); 102 | return bytes.toByteArray(); 103 | } 104 | 105 | @Override 106 | public Enumeration entries() { 107 | return jarFile.entries(); 108 | } 109 | 110 | @Override 111 | public JarEntry getJarEntry(String name) { 112 | JarEntry e = getMapEntry(name); 113 | return e != null ? e : jarFile.getJarEntry(name); 114 | } 115 | 116 | @Override 117 | public void close() throws IOException { 118 | jarFile.close(); 119 | } 120 | } 121 | 122 | private static class StreamMap extends JarMap { 123 | 124 | private final JarInputStream jis; 125 | 126 | StreamMap(InputStream is, boolean verify) throws IOException { 127 | jis = new JarInputStream(is, verify); 128 | entryMap = new LinkedHashMap<>(); 129 | JarEntry entry; 130 | while ((entry = jis.getNextJarEntry()) != null) { 131 | entryMap.put(entry.getName(), new JarMapEntry(entry, jis)); 132 | } 133 | } 134 | 135 | @Override 136 | public Manifest getManifest() { 137 | return jis.getManifest(); 138 | } 139 | 140 | @Override 141 | public Enumeration entries() { 142 | return Collections.enumeration(entryMap.values()); 143 | } 144 | 145 | @Override 146 | public void close() throws IOException { 147 | jis.close(); 148 | } 149 | } 150 | 151 | private static class JarMapEntry extends JarEntry { 152 | 153 | ByteArrayStream data; 154 | 155 | JarMapEntry(JarEntry je, InputStream is) { 156 | super(je); 157 | data = new ByteArrayStream(); 158 | data.readFrom(is); 159 | } 160 | 161 | JarMapEntry(String s) { 162 | super(s); 163 | data = new ByteArrayStream(); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/hideapk/AXML.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util.hideapk 2 | 3 | import java.io.ByteArrayOutputStream 4 | import java.nio.ByteBuffer 5 | import java.nio.ByteOrder.LITTLE_ENDIAN 6 | import java.nio.charset.Charset 7 | 8 | class AXML(b: ByteArray) { 9 | 10 | var bytes = b 11 | private set 12 | 13 | companion object { 14 | private const val CHUNK_SIZE_OFF = 4 15 | private const val STRING_INDICES_OFF = 7 * 4 16 | private val UTF_16LE = Charset.forName("UTF-16LE") 17 | } 18 | 19 | /** 20 | * String pool header: 21 | * 0: 0x1C0001 22 | * 1: chunk size 23 | * 2: number of strings 24 | * 3: number of styles (assert as 0) 25 | * 4: flags 26 | * 5: offset to string data 27 | * 6: offset to style data (assert as 0) 28 | * 29 | * Followed by an array of uint32_t with size = number of strings 30 | * Each entry points to an offset into the string data 31 | */ 32 | fun patchStrings(patchFn: (Array) -> Unit): Boolean { 33 | val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN) 34 | 35 | fun findStringPool(): Int { 36 | var offset = 8 37 | while (offset < bytes.size) { 38 | if (buffer.getInt(offset) == 0x1C0001) 39 | return offset 40 | offset += buffer.getInt(offset + CHUNK_SIZE_OFF) 41 | } 42 | return -1 43 | } 44 | 45 | val start = findStringPool() 46 | if (start < 0) 47 | return false 48 | 49 | // Read header 50 | buffer.position(start + 4) 51 | val intBuf = buffer.asIntBuffer() 52 | val size = intBuf.get() 53 | val count = intBuf.get() 54 | intBuf.get() 55 | intBuf.get() 56 | val dataOff = start + intBuf.get() 57 | intBuf.get() 58 | 59 | val strList = ArrayList(count) 60 | // Collect all strings in the pool 61 | for (i in 0 until count) { 62 | val off = dataOff + intBuf.get() 63 | val len = buffer.getShort(off) 64 | strList.add(String(bytes, off + 2, len * 2, UTF_16LE)) 65 | } 66 | 67 | val strArr = strList.toTypedArray() 68 | patchFn(strArr) 69 | 70 | // Write everything before string data, will patch values later 71 | val baos = RawByteStream() 72 | baos.write(bytes, 0, dataOff) 73 | 74 | // Write string data 75 | val offList = IntArray(count) 76 | for (i in 0 until count) { 77 | offList[i] = baos.size() - dataOff 78 | val str = strArr[i] 79 | baos.write(str.length.toShortBytes()) 80 | baos.write(str.toByteArray(UTF_16LE)) 81 | // Null terminate 82 | baos.write(0) 83 | baos.write(0) 84 | } 85 | baos.align() 86 | 87 | val sizeDiff = baos.size() - start - size 88 | val newBuffer = ByteBuffer.wrap(baos.buf).order(LITTLE_ENDIAN) 89 | 90 | // Patch XML size 91 | newBuffer.putInt(CHUNK_SIZE_OFF, buffer.getInt(CHUNK_SIZE_OFF) + sizeDiff) 92 | // Patch string pool size 93 | newBuffer.putInt(start + CHUNK_SIZE_OFF, size + sizeDiff) 94 | // Patch index table 95 | newBuffer.position(start + STRING_INDICES_OFF) 96 | val newIntBuf = newBuffer.asIntBuffer() 97 | offList.forEach { newIntBuf.put(it) } 98 | 99 | // Write the rest of the chunks 100 | val nextOff = start + size 101 | baos.write(bytes, nextOff, bytes.size - nextOff) 102 | 103 | bytes = baos.toByteArray() 104 | return true 105 | } 106 | 107 | private fun Int.toShortBytes(): ByteArray { 108 | val b = ByteBuffer.allocate(2).order(LITTLE_ENDIAN) 109 | b.putShort(this.toShort()) 110 | return b.array() 111 | } 112 | 113 | private class RawByteStream : ByteArrayOutputStream() { 114 | val buf: ByteArray get() = super.buf 115 | 116 | fun align(alignment: Int = 4) { 117 | val newCount = (count + alignment - 1) / alignment * alignment 118 | for (i in 0 until (newCount - count)) 119 | write(0) 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/hideapk/HideAPK.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util.hideapk 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.Runnable 7 | import kotlinx.coroutines.withContext 8 | import me.bmax.apatch.BuildConfig.APPLICATION_ID 9 | import me.bmax.apatch.R 10 | import me.bmax.apatch.util.apksign.JarMap 11 | import me.bmax.apatch.util.apksign.SignApk 12 | import me.bmax.apatch.util.rootShellForResult 13 | import timber.log.Timber 14 | import java.io.File 15 | import java.io.FileOutputStream 16 | import java.io.OutputStream 17 | import java.security.SecureRandom 18 | 19 | private const val TAG = "HideAPK" 20 | 21 | object HideAPK { 22 | private const val ALPHA = "abcdefghijklmnopqrstuvwxyz" 23 | private const val ALPHADOTS = "$ALPHA....." 24 | private const val ANDROID_MANIFEST = "AndroidManifest.xml" 25 | 26 | @JvmStatic 27 | private fun genPackageName(): String { 28 | val random = SecureRandom() 29 | val len = 5 + random.nextInt(15) 30 | val builder = StringBuilder(len) 31 | var next: Char 32 | var prev = 0.toChar() 33 | for (i in 0 until len) { 34 | next = if (prev == '.' || i == 0 || i == len - 1) { 35 | ALPHA[random.nextInt(ALPHA.length)] 36 | } else { 37 | ALPHADOTS[random.nextInt(ALPHADOTS.length)] 38 | } 39 | builder.append(next) 40 | prev = next 41 | } 42 | if (!builder.contains('.')) { 43 | // Pick a random index and set it as dot 44 | val idx = random.nextInt(len - 2) 45 | builder[idx + 1] = '.' 46 | } 47 | return builder.toString() 48 | } 49 | 50 | @JvmStatic 51 | private fun patch( 52 | apk: File, out: OutputStream, 53 | pkg: String, label: CharSequence 54 | ): Boolean { 55 | val origLabel = "APatch" // TODO: Get this in a better way instead of hardcode 56 | try { 57 | JarMap.open(apk, true).use { jar -> 58 | val je = jar.getJarEntry(ANDROID_MANIFEST) 59 | val xml = AXML(jar.getRawData(je)) 60 | 61 | if (!xml.patchStrings { 62 | for (i in it.indices) { 63 | val s = it[i] 64 | if (s.contains(APPLICATION_ID) && !s.contains("ui.MainActivity") && !s.contains( 65 | "WebUIActivity" 66 | ) && !s.contains(".APApplication") 67 | ) { 68 | it[i] = s.replace(APPLICATION_ID, pkg) 69 | } else if (s == origLabel) { 70 | it[i] = label.toString() 71 | } 72 | } 73 | }) { 74 | return false 75 | } 76 | 77 | // Write apk changes 78 | jar.getOutputStream(je).use { it.write(xml.bytes) } 79 | val keys = Keygen() 80 | SignApk.sign(keys.cert, keys.key, jar, out) 81 | return true 82 | } 83 | } catch (e: Exception) { 84 | Timber.e(e) 85 | return false 86 | } 87 | } 88 | 89 | @JvmStatic 90 | private fun patchAndHide(context: Context, label: String): Boolean { 91 | val apkPath: String = context.packageManager.getApplicationInfo(context.packageName, 0).sourceDir 92 | val source = File(apkPath) 93 | 94 | // Generate a new random package name and signature 95 | val patchedApk = File(context.cacheDir, "patched.apk") 96 | val newPkgName = genPackageName() 97 | 98 | if (!patch(source, FileOutputStream(patchedApk), newPkgName, label)) 99 | return false 100 | 101 | val cmds = arrayOf( 102 | "pm install -r -t $patchedApk", 103 | "[ $? = 0 ] && appops set $newPkgName REQUEST_INSTALL_PACKAGES allow;", 104 | "am start -n $newPkgName/$APPLICATION_ID.ui.MainActivity", 105 | "pm uninstall $APPLICATION_ID", 106 | ) 107 | val result = rootShellForResult(*cmds) 108 | 109 | return result.isSuccess 110 | } 111 | 112 | @Suppress("DEPRECATION") 113 | suspend fun hide(context: Context, label: String) { 114 | val dialog = android.app.ProgressDialog(context).apply { 115 | setTitle(context.getString(R.string.hide_apatch_manager)) 116 | isIndeterminate = true 117 | setCancelable(false) 118 | show() 119 | } 120 | val onFailure = Runnable { 121 | dialog.dismiss() 122 | Toast.makeText( 123 | context, 124 | context.getString(R.string.hide_apatch_manager_failure), 125 | Toast.LENGTH_LONG 126 | ).show() 127 | } 128 | val success = withContext(Dispatchers.IO) { patchAndHide(context, label) } 129 | if (!success) onFailure.run() 130 | } 131 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/hideapk/Keygen.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util.hideapk 2 | 3 | import android.util.Base64 4 | import android.util.Base64OutputStream 5 | import org.bouncycastle.asn1.x500.X500Name 6 | import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo 7 | import org.bouncycastle.cert.X509v3CertificateBuilder 8 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter 9 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder 10 | import java.io.ByteArrayOutputStream 11 | import java.math.BigInteger 12 | import java.security.KeyPairGenerator 13 | import java.security.KeyStore 14 | import java.security.PrivateKey 15 | import java.security.cert.X509Certificate 16 | import java.util.Calendar 17 | import java.util.Locale 18 | import java.util.Random 19 | import java.util.zip.GZIPOutputStream 20 | 21 | private interface CertKeyProvider { 22 | val cert: X509Certificate 23 | val key: PrivateKey 24 | } 25 | 26 | class Keygen : CertKeyProvider { 27 | 28 | companion object { 29 | private const val ALIAS = "apatch" 30 | private val PASSWORD get() = "apatch".toCharArray() 31 | private const val DNAME = 32 | "C=US,ST=California,L=Mountain View,O=Google Inc.,OU=Android,CN=Android" 33 | private const val BASE64_FLAG = Base64.NO_PADDING or Base64.NO_WRAP 34 | 35 | } 36 | 37 | private val start = Calendar.getInstance().apply { add(Calendar.MONTH, -3) } 38 | private val end = (start.clone() as Calendar).apply { add(Calendar.YEAR, 30) } 39 | 40 | private val ks = init() 41 | override val cert = ks.getCertificate(ALIAS) as X509Certificate 42 | override val key = ks.getKey(ALIAS, PASSWORD) as PrivateKey 43 | 44 | private fun init(): KeyStore { 45 | val ks = KeyStore.getInstance("PKCS12") 46 | ks.load(null) 47 | 48 | // Generate new private key and certificate 49 | val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(4096) }.genKeyPair() 50 | val dname = X500Name(DNAME) 51 | val builder = X509v3CertificateBuilder( 52 | dname, BigInteger(160, Random()), 53 | start.time, end.time, Locale.ROOT, dname, 54 | SubjectPublicKeyInfo.getInstance(kp.public.encoded) 55 | ) 56 | val signer = JcaContentSignerBuilder("SHA1WithRSA").build(kp.private) 57 | val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer)) 58 | 59 | // Store them into keystore 60 | ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert)) 61 | val bytes = ByteArrayOutputStream() 62 | GZIPOutputStream(Base64OutputStream(bytes, BASE64_FLAG)).use { 63 | ks.store(it, PASSWORD) 64 | } 65 | 66 | return ks 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/ui/APDialogBlurBehindUtils.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util.ui 2 | 3 | import android.animation.ValueAnimator 4 | import android.annotation.SuppressLint 5 | import android.os.Build 6 | import android.util.Log 7 | import android.view.SurfaceControl 8 | import android.view.View 9 | import android.view.Window 10 | import android.view.WindowManager 11 | import android.view.animation.DecelerateInterpolator 12 | import java.lang.reflect.Method 13 | 14 | open class APDialogBlurBehindUtils { 15 | companion object { 16 | private val bIsBlurSupport = 17 | getSystemProperty("ro.surface_flinger.supports_background_blur") && !getSystemProperty("persist.sys.sf.disable_blurs") 18 | 19 | private fun getSystemProperty(key: String?): Boolean { 20 | var value = false 21 | try { 22 | val c = Class.forName("android.os.SystemProperties") 23 | val get = c.getMethod( 24 | "getBoolean", String::class.java, Boolean::class.javaPrimitiveType 25 | ) 26 | value = get.invoke(c, key, false) as Boolean 27 | } catch (e: Exception) { 28 | Log.e("APatchUI", "[APDialogBlurBehindUtils] Failed to getSystemProperty: ", e) 29 | } 30 | return value 31 | } 32 | 33 | private fun updateWindowForBlurs(window: Window, blursEnabled: Boolean) { 34 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 35 | window.setDimAmount(0.27f) 36 | window.attributes.blurBehindRadius = 20 37 | } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { 38 | if (blursEnabled) { 39 | val view = window.decorView 40 | val animator = ValueAnimator.ofInt(1, 53) 41 | animator.duration = 667 42 | animator.interpolator = DecelerateInterpolator() 43 | try { 44 | val viewRootImpl = 45 | view.javaClass.getMethod("getViewRootImpl").invoke(view) ?: return 46 | val surfaceControl = viewRootImpl.javaClass.getMethod("getSurfaceControl") 47 | .invoke(viewRootImpl) as SurfaceControl 48 | @SuppressLint("BlockedPrivateApi") val setBackgroundBlurRadius: Method = 49 | SurfaceControl.Transaction::class.java.getDeclaredMethod( 50 | "setBackgroundBlurRadius", 51 | SurfaceControl::class.java, 52 | Int::class.javaPrimitiveType 53 | ) 54 | animator.addUpdateListener { animation: ValueAnimator -> 55 | try { 56 | val transaction = SurfaceControl.Transaction() 57 | val animatedValue = animation.animatedValue 58 | if (animatedValue != null) { 59 | setBackgroundBlurRadius.invoke( 60 | transaction, surfaceControl, animatedValue as Int 61 | ) 62 | } 63 | transaction.apply() 64 | } catch (t: Throwable) { 65 | Log.e( 66 | "APatchUI", 67 | "[APDialogBlurBehindUtils] Blur behind dialog builder: " + t.toString() 68 | ) 69 | } 70 | } 71 | } catch (t: Throwable) { 72 | Log.e( 73 | "APatchUI", 74 | "[APDialogBlurBehindUtils] Blur behind dialog builder: " + t.toString() 75 | ) 76 | } 77 | view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { 78 | override fun onViewAttachedToWindow(v: View) {} 79 | override fun onViewDetachedFromWindow(v: View) { 80 | animator.cancel() 81 | } 82 | }) 83 | animator.start() 84 | } 85 | } 86 | } 87 | 88 | fun setupWindowBlurListener(window: Window) { 89 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 90 | window.setFlags( 91 | WindowManager.LayoutParams.FLAG_BLUR_BEHIND, 92 | WindowManager.LayoutParams.FLAG_BLUR_BEHIND 93 | ) 94 | updateWindowForBlurs(window, true) 95 | } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { 96 | updateWindowForBlurs( 97 | window, bIsBlurSupport 98 | ) 99 | } 100 | } 101 | 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/ui/CompositionProvider.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util.ui 2 | 3 | import androidx.compose.material3.SnackbarHostState 4 | import androidx.compose.runtime.compositionLocalOf 5 | 6 | val LocalSnackbarHost = compositionLocalOf { 7 | error("CompositionLocal LocalSnackbarController not present") 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/ui/HyperlinkText.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util.ui 2 | 3 | import androidx.compose.foundation.gestures.detectTapGestures 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.mutableStateOf 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.input.pointer.pointerInput 11 | import androidx.compose.ui.platform.LocalUriHandler 12 | import androidx.compose.ui.text.SpanStyle 13 | import androidx.compose.ui.text.TextLayoutResult 14 | import androidx.compose.ui.text.buildAnnotatedString 15 | import androidx.compose.ui.text.style.TextDecoration 16 | import java.util.regex.Pattern 17 | 18 | @Composable 19 | fun LinkifyText( 20 | text: String, 21 | modifier: Modifier = Modifier 22 | ) { 23 | val uriHandler = LocalUriHandler.current 24 | val layoutResult = remember { 25 | mutableStateOf(null) 26 | } 27 | val linksList = extractUrls(text) 28 | val annotatedString = buildAnnotatedString { 29 | append(text) 30 | linksList.forEach { 31 | addStyle( 32 | style = SpanStyle( 33 | color = MaterialTheme.colorScheme.primary, 34 | textDecoration = TextDecoration.Underline 35 | ), 36 | start = it.start, 37 | end = it.end 38 | ) 39 | addStringAnnotation( 40 | tag = "URL", 41 | annotation = it.url, 42 | start = it.start, 43 | end = it.end 44 | ) 45 | } 46 | } 47 | Text( 48 | text = annotatedString, 49 | modifier = modifier.pointerInput(Unit) { 50 | detectTapGestures { offsetPosition -> 51 | layoutResult.value?.let { 52 | val position = it.getOffsetForPosition(offsetPosition) 53 | annotatedString.getStringAnnotations(position, position).firstOrNull() 54 | ?.let { result -> 55 | if (result.tag == "URL") { 56 | uriHandler.openUri(result.item) 57 | } 58 | } 59 | } 60 | } 61 | }, 62 | onTextLayout = { layoutResult.value = it } 63 | ) 64 | } 65 | 66 | private val urlPattern: Pattern = Pattern.compile( 67 | "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" 68 | + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" 69 | + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", 70 | Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL 71 | ) 72 | 73 | private data class LinkInfo( 74 | val url: String, 75 | val start: Int, 76 | val end: Int 77 | ) 78 | 79 | private fun extractUrls(text: String): List = buildList { 80 | val matcher = urlPattern.matcher(text) 81 | while (matcher.find()) { 82 | val matchStart = matcher.start(1) 83 | val matchEnd = matcher.end() 84 | val url = text.substring(matchStart, matchEnd).replaceFirst("http://", "https://") 85 | add(LinkInfo(url, matchStart, matchEnd)) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/me/bmax/apatch/util/ui/NavigationBarsSpacer.kt: -------------------------------------------------------------------------------- 1 | package me.bmax.apatch.util.ui 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.WindowInsets 6 | import androidx.compose.foundation.layout.asPaddingValues 7 | import androidx.compose.foundation.layout.navigationBars 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | 12 | @Composable 13 | fun NavigationBarsSpacer( 14 | modifier: Modifier = Modifier 15 | ) { 16 | val paddingValues = WindowInsets.navigationBars.asPaddingValues() 17 | 18 | Box( 19 | modifier = Modifier.padding(paddingValues) 20 | ) { 21 | Spacer(modifier = modifier) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/device_mobile_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 30 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/github.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/info_circle_filled.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/launcher_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/package_import.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/telegram.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/trash.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 30 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/weblate.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/resources.properties: -------------------------------------------------------------------------------- 1 | unqualifiedResLocale=en -------------------------------------------------------------------------------- /app/src/main/res/values-arq/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values-ca/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Inici 4 | Ha fallat 5 | La instal·lació suposa un risc. Si us plau assegura\'t de tenir una còpia de seguretat de les teves dades. 6 | Pegat 7 | Pegat de l\'Android 8 | Configuració 9 | Reinicialitza al gestor d\'arrencada (Bootloader) 10 | Reinicialitza al mode recuperació (Recovery) 11 | Reinicialitza al mode de descàrrega d\'emergència (EDL) 12 | Llengua 13 | Mode d\'espai de noms global 14 | Cerca actualitzacions 15 | Aprèn APatch 16 | Obteniu informació sobre les funcions d\'APatch i com utilitzar-lo 17 | No instal·lat ni autenticat 18 | Treballant 😋 19 | Versió: %s 20 | kpatch 21 | Insereix SuperClau 22 | Actualitzar 23 | Desinstal·lar 24 | Reinicialitza 25 | Èxit 26 | Pegat del Kernel 27 | Reinicialitza al mode descàrrega 28 | Reinicia 29 | Envia els registres 30 | Totes les sessions arrel utilitzen l\'espai de noms de muntatge global 31 | Realment voleu continuar? 32 | Comprova automàticament si hi ha actualitzacions en obrir l\'aplicació 33 | Sobre 34 | <p>Veure el codi font a %1$s<p/> Uneix-te al nostre %2$s canal<p/>Uneix-te al nostre %3$s group 35 | Desa els registres 36 | Opció per defecte del sistema 37 | Elimina SuperClau 38 | SuperClau 39 | Següent pas 40 | Mode segur 41 | Estableix SuperClau 42 | No instal·lada 43 | Clic per instal·lar 44 | Clic per instal·lar 45 | Versió: %s 46 | Versió: %s -> %s 47 | Informació 48 | Comença després de la certificació 49 | Instal·lar 50 | » 51 | Només credencial per KernelPatch 52 | Versió: %s -> %s 53 | Parxe 54 | Mode: Parxejat 55 | Mode: Parxejar i instal·lar 56 | Instal·lant 57 | Hi ha una nova versió disponible 58 | -------------------------------------------------------------------------------- /app/src/main/res/values-fa/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | خانه 4 | موفق 5 | ناموفق 6 | راه اندازی به ریکاوری 7 | راه اندازی به دانلود مود 8 | راه اندازی به EDL 9 | درباره 10 | تنظیمات پیشفرض سیستم 11 | Global Namespace Mode 12 | آیا واقعا میخواهید ادامه دهید؟ 13 | برسی نسخه به روز 14 | با ویژگی های APatch و نحو استفاده آن آشنا شوید 15 | با APatch آشنا شو 16 | ارسال گزارش 17 | ذخیره گزارش 18 | حالت امن 19 | SuperKey 20 | نصب با خطر همراه است! لطفا مطمئن شوید که از اطلاعات شما نسخه پشتیبان تهیه شده است. 21 | زبان 22 | راه اندازی مجدد 23 | تنظیمات 24 | پچ 25 | پچ کردن کرنل 26 | راه اندازی به بوتلودر 27 | پچ کردن اندروید 28 | هنگام باز کردن برنامه، به طور خودکار نسخه به روز را برسی کن 29 | -------------------------------------------------------------------------------- /app/src/main/res/values-gl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | APatch 4 | Fogar 5 | A instalación conleva uns riscos. Por favor, asegura os teus datos. 6 | Parche 7 | Parche do Kernel 8 | Parche de Android 9 | Reiniciar 10 | Configuración 11 | Reiniciar no cargador de arranque 12 | Reiniciar para descargar 13 | Reiniciar a EDL 14 | Acerca de 15 | Por defecto do sistema 16 | Reiniciar para recuperar 17 | Lingua 18 | -------------------------------------------------------------------------------- /app/src/main/res/values-jv/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | APatch 4 | AndroidPatch 5 | Reboot 6 | Aturan 7 | Mbalek nang Donlot 8 | Delok kode sumber nang %1$s%1$sGabong saluran %2$s awakdewe 9 | Kirem catatan 10 | Apos Kunci 11 | Set SuperKey 12 | Omah 13 | Tindakan bahaya lur, backup data mu disek. 14 | KernelPatch 15 | Mbalek nang Recovery 16 | Mbalek nang Bootloader 17 | Mbalek nang EDL 18 | Tentang 19 | Sinau AndroidPatch 20 | Sinau fitur AndroidPatch lan coro ngunakno 21 | Mode aman 22 | SuperKey 23 | -------------------------------------------------------------------------------- /app/src/main/res/values-lt/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values-ms/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | APatch 4 | Halaman Utama 5 | Pemasangan datang dengan risiko. Sila pastikan anda telah membuat sandaran data. 6 | But semula (Reboot) 7 | Tetapan 8 | Perihal 9 | Ketahui tentang APatch 10 | But semula ke Recovery 11 | KernelPatch 12 | AndroidPatch 13 | But semula ke Bootloader 14 | But semula ke Download 15 | But semula ke EDL 16 | Lihat kod sumber di %1$s
Sertai saluran kami di %2$s
17 | Hantar log 18 | Safe mode 19 | SuperKey 20 | Clear Key 21 | Bahasa 22 | Ketahui tentang ciri-ciri APatch dan cara menggunakannya 23 | Lalai sistem 24 | Memasang 25 | Versi: %s -> %s 26 | Langkah Seterusnya 27 | Tidak di pasang 28 | Tidak diketahui 29 | Tekan untuk pasang 30 | Versi baharu tersedia 31 | Versi: %d.%d.%d 32 | Versi: %s 33 | Tekan untuk pasang 34 | Berjaya 35 | Gagal 36 | Mod ruang nama global 37 | Adakah anda benar-benar mahu meneruskan? 38 | Semak kemas kini 39 | Semak kemas kini secara automatik apabila membuka aplikasi 40 | Penerangan 41 | !!RALAT!! 42 | Menanggalkan tampalan 43 | Memuat semula 44 | Lesen 45 | Pengarang 46 | Simpan Log 47 | Tetapkan SuperKey 48 | Hanya kelayakan untuk KernelPatch 49 | Sedang bekerja 😋 50 | Versi: %s -> %s 51 | kpatch 52 | Masukkan SuperKey 53 | Mula selepas pengesahan 54 | Info 55 | Memuat turun 56 | Kemas kini 57 | Nyahpasang 58 | Mula semula 59 | Tampalan 60 | Tampalan 61 | Mod: Tampalan 62 | Mod: Tampalan dan memuat turun 63 | Mod: Memuat turun pada slot seterusnya (selepas OTA) 64 | Mod: Memadam ktampalan 65 | Pilih mula semula 66 | Benamkan KPM 67 | Mula 68 | slot: 69 | Versi 70 | tampalan 71 | APatch 72 | APatch 73 | APatch 74 | Versi 75 | Kemas kini 76 |
-------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 127.0.0.1 5 | 0.0.0.0 6 | ::1 7 | 8 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.api.dsl.ApplicationDefaultConfig 2 | import com.android.build.api.dsl.CommonExtension 3 | import com.android.build.gradle.api.AndroidBasePlugin 4 | import java.io.ByteArrayOutputStream 5 | 6 | plugins { 7 | alias(libs.plugins.agp.app) apply false 8 | alias(libs.plugins.agp.lib) apply false 9 | alias(libs.plugins.kotlin) apply false 10 | alias(libs.plugins.kotlin.compose.compiler) apply false 11 | alias(libs.plugins.lsplugin.cmaker) 12 | } 13 | 14 | cmaker { 15 | default { 16 | arguments += "-DANDROID_STL=none" 17 | arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" 18 | abiFilters("arm64-v8a") 19 | } 20 | } 21 | 22 | project.ext.set("kernelPatchVersion", "0.11.1-dev") 23 | 24 | val androidMinSdkVersion = 26 25 | val androidTargetSdkVersion = 35 26 | val androidCompileSdkVersion = 35 27 | 28 | val androidCompileNdkVersion = "27.1.12297006" 29 | val managerVersionCode by extra(getVersionCode()) 30 | val managerVersionName by extra(getVersionName()) 31 | 32 | fun getGitCommitCount(): Int { 33 | val out = ByteArrayOutputStream() 34 | exec { 35 | commandLine("git", "rev-list", "--count", "HEAD") 36 | standardOutput = out 37 | } 38 | return out.toString().trim().toInt() 39 | } 40 | 41 | fun getGitDescribe(): String { 42 | val out = ByteArrayOutputStream() 43 | exec { 44 | commandLine("git", "describe", "--tags", "--always") 45 | standardOutput = out 46 | } 47 | return out.toString().trim() 48 | } 49 | 50 | fun getVersionCode(): Int { 51 | val commitCount = getGitCommitCount() 52 | val major = 1 53 | return major * 10000 + commitCount + 198 54 | } 55 | 56 | fun getVersionName(): String { 57 | return getGitDescribe() 58 | } 59 | 60 | tasks.register("printVersion") { 61 | doLast { 62 | println("Version code: $managerVersionCode") 63 | println("Version name: $managerVersionName") 64 | } 65 | } 66 | 67 | subprojects { 68 | plugins.withType(AndroidBasePlugin::class.java) { 69 | extensions.configure(CommonExtension::class.java) { 70 | compileSdk = androidCompileSdkVersion 71 | ndkVersion = androidCompileNdkVersion 72 | 73 | defaultConfig { 74 | minSdk = androidMinSdkVersion 75 | if (this is ApplicationDefaultConfig) { 76 | targetSdk = androidTargetSdkVersion 77 | versionCode = managerVersionCode 78 | versionName = managerVersionName 79 | } 80 | } 81 | 82 | lint { 83 | abortOnError = true 84 | checkReleaseBuilds = false 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /docs/az/faq_az.md: -------------------------------------------------------------------------------- 1 | # TSS 2 | 3 | 4 | ## APatch nədir? 5 | APatch, hər ikisinin ən yaxşısını birləşdirən Magisk və ya KernelSU-ya bənzər kök həllidir. 6 | O, Magisk-in `boot.img` vasitəsilə rahat və asan quraşdırma metodunu KernelSU-nun güclü nüvə yamaqlamaq qabiliyyətləri ilə birləşdirir. 7 | 8 | 9 | ## APatch və Magisk arasındakı fərq nədir? 10 | - Magisk başlanğıc sistemini nüvə imajınızın ramdiskindəki yamaq ilə dəyişdirir, APatch isə nüvəni birbaşa yamaqlayır. 11 | 12 | 13 | ## APatch vs KernelSU 14 | - KernelSU cihazınızın nüvəsi üçün həmişə OEM tərəfindən təmin edilməyən mənbə kodunu tələb edir. APAtch yalnız sizin stok `boot.img` ilə işləyir. 15 | 16 | 17 | ## APatch vs Magisk, KernelSU 18 | - APatch istəyə bağlı olaraq SELinux-u dəyişdirməməyə imkan verir, bu o deməkdir ki, Android proqramı köklənə bilər, libsu və IPC zəruri deyil. 19 | - **Nüvə Yamaq Modulu** təmin edilmişdir. 20 | 21 | 22 | ## Nüvə Yamaq Modulu nədir? 23 | Bəzi kodlar Yüklənəbilən Nüvə Modulları (LKM) kimi Nüvə Məkanında işləyir. 24 | 25 | Əlavə olaraq, KPM nüvə məkanında daxili-çəngəl, sistem-zəngi-cədvəli-çəngəlləri etmək imkanı verir. 26 | 27 | Ətraflı məlumat üçün [KPM necə yazılır](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) bölməsinə baxın. 28 | 29 | 30 | ## APatch və NüvəYamağı arasındakı əlaqə 31 | 32 | APatch NüvəYamağından asılıdır, onun bütün imkanlarını miras alır və genişləndirilib. 33 | 34 | Siz yalnızca NüvəYamağı quraşdıra bilərsiniz, lakin bu, Magisk modullarından istifadə etməyə imkan verməyəcək və superistifadəçi idarəçiliyindən istifadə etmək üçün AndroidYamağını quraşdırmalı və sonra onu silməlisiniz. 35 | 36 | [NüvəYamağı haqqında ətraflı öyrənin](https://github.com/bmax121/KernelPatch) 37 | 38 | 39 | ## SuperAçar nədir? 40 | NüvəYamağı istifadəçi məkanındakı tətbiq və proqramlara bütün imkanları təmin etmək üçün yeni sistem zəngi (syscall) əlavə edir, bu sistem zəngi **SuperZəng** adlanır. 41 | Tətbiq/proqram **SuperZəngi** işə salmağa çalışdıqda, o, **SuperAçar** kimi tanınan giriş etimadnaməsini təmin etməlidir. 42 | **SuperZəng** yalnız **SuperAçar** düzgün olduqda uğurla işə salına bilər və bu deyilsə, zəng edən şəxs təsirsiz qalacaq. 43 | 44 | 45 | ## SELinux bəs? 46 | - NüvəYamağı SELinux kontekstini dəyişdirmir və çəngəl vasitəsilə SELinux-dan yan keçir. 47 | Bu, yeni prosesə başlamaq və sonra IPC yerinə yetirmək üçün libsu-dan istifadə etmədən proqram kontekstində Android mövzusunu kökləməyə imkan verir. 48 | Bu çox rahatdır. 49 | - Bundan əlavə, APatch əlavə SELinux dəstəyi təmin etmək üçün birbaşa magiskpolicy-dən istifadə edir. 50 | -------------------------------------------------------------------------------- /docs/cn/faq_cn.md: -------------------------------------------------------------------------------- 1 | # 常见问题解答 2 | 3 | ## 什么是APatch? 4 | 5 | APatch是一种类似于Magisk或KernelSU的root解决方案,但APatch提供更多功能。 6 | APatch分别结合了Magisk方便易用的通过`boot.img`安装的方法,和KernelSU强大的内核修补能力。 7 | 8 | ## APatch与Magisk的区别? 9 | 10 | - Magisk对启动映像中的ramdisk进行补丁,以修改init系统。而APatch则直接修补Linux内核。 11 | 12 | ## APatch与KernelSU的区别? 13 | 14 | - KernelSU需要您设备的内核的源代码,而OEM并不总是提供该源码。而APatch仅需要您的设备原本的`boot.img`。 15 | 16 | ## APatch与Magisk、KernelSU的区别? 17 | 18 | - APatch可选择不修改SELinux,这意味着Android应用程序线程可以被root,无需libsu和IPC。 19 | - APatch提供**Kernel Patch Module(KP模块)**。 20 | 21 | ## 什么是Kernel Patch Module(KP模块)? 22 | 23 | 一些代码在内核空间运行,类似于Loadable Kernel Modules(LKM)。 24 | 25 | 此外,KPM提供在内核空间进行内联hook、系统调用表hook的能力。 26 | 27 | 更多相关信息,请参阅[如何编写KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) 28 | 29 | ## APatch与KernelPatch的关系 30 | 31 | APatch依赖于KernelPatch,继承了其所有功能并进行了扩展。 32 | 33 | 您可以仅安装KernelPatch,但如此将不允许您使用Magisk模块。 34 | 要使用超级用户管理,您需要安装AndroidPatch,然后卸载KernelPatch。 35 | 36 | [了解更多关于KernelPatch的信息](https://github.com/bmax121/KernelPatch) 37 | 38 | ## 什么是SuperKey(超级密钥)? 39 | 40 | KernelPatch 添加了一个新的系统调用(syscall),为应用程序和用户空间中的程序提供所有功能,此系统调用称为SuperCall。 41 | 当应用程序/程序尝试调用SuperCall时,它需要提供访问凭据,称为SuperKey。 42 | 只有当SuperKey正确时,才能成功调用 SuperCall。否则,调用方将不受影响。 43 | 44 | ## 关于SELinux如何处理? 45 | 46 | - KernelPatch不修改SELinux上下文,而是通过hook绕过SELinux。 这允许您在应用程序上下文中root Android线程,无需使用libsu启动新进程,然后执行IPC。这非常方便。 47 | - 此外,APatch直接利用magiskpolicy提供额外的SELinux支持。 48 | -------------------------------------------------------------------------------- /docs/cn_tw/faq_cn_tw.md: -------------------------------------------------------------------------------- 1 | # 〈常見問題集〉 2 | 3 | 4 | ## 什麼是 APatch? 5 | APatch 為一套汲取 Magisk、KernelSU 優勢於一身的 Root 解決方案。 6 | 不僅保留 Magisk 自身便捷、修補 `boot.img` 即用的特性,也有 KernelSU 強大的核心修補功能。 7 | 8 | 9 | ## APatch 與 Magisk 的差別為何? 10 | - Magisk 會先調用 boot 修補鏡像的 ramdisk,再修改 init 系統;APatch 則經由修補鏡像,直接修改核心本身。 11 | 12 | 13 | ## APatch vs KernelSU 14 | - KernelSU 依託 OEM 廠商提供的裝置核心原始碼,但並非每間廠商都會提供;而 APatch 只需修補原廠 `boot.img`。 15 | 16 | 17 | ## APatch vs Magisk、KernelSU 18 | - APatch 可選擇不修改 SELinux,但仍讓應用程式執行緒調用 Root 權限。過程不需要 libsu 以及 IPC。 19 | - 支援**核心修補模組**。 20 | 21 | 22 | ## 什麼是核心修補模組? 23 | **核心修補模組**為一套執行在核心空間的程式片段——類似於裝載式核心模組(LKM)——。 24 | 25 | 包括 inline-hook、syscall-table-hook,核心修補模組也能做到。 26 | 27 | 更多詳情,請參閱[〈如何編寫核心修補模組?〉](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md)(英語)。 28 | 29 | 30 | ## APatch 和 KernelPatch 的關係 31 | 32 | APatch 基於 KernelPatch。繼承 KernelPatch 特性的基礎之上,新增更多功能。 33 | 34 | 你也能選擇只安裝 KernelPatch,但這樣就沒辦法使用 Magisk 模組,且包括管理超級使用者權限在內的功能,你都必須解除安裝 KernelPatch 後再安裝 AndroidPatch。 35 | 36 | [深入了解 KernelPatch](https://github.com/bmax121/KernelPatch) 37 | 38 | 39 | ## 什麼是超級密鑰? 40 | **超級密鑰**為 KernelPatch 上的系統呼叫(syscall)服務,旨在讓使用者空間內的程式也能套用修補變更;也稱作**超級呼叫**。當程式請求**超級呼叫**,需提供一份稱作**超級密鑰**的存取憑證。 41 | **超級呼叫**僅在**超級密鑰**正確無虞之下生效,反之則失效。 42 | 43 | 44 | ## 那 SELinux 怎麼辦? 45 | - KernelPatch 不修改 SELinux 內容,而是使用 Hook 忽略 SELinux 層; 46 | 換句話說,應用程式內的 Android 執行緒可直接調用 Root 權限,而不用經過讓 libsu 新增運算排程、安排 IPC 等步驟。 47 | 可謂事半功倍。 48 | - 此外,由於 APatch 使用了 magiskpolicy,使你能有額外的 SELinux 支援。 49 | -------------------------------------------------------------------------------- /docs/de/faq_de.md: -------------------------------------------------------------------------------- 1 | # Häufig gestellte Fragen 2 | 3 | 4 | ## Was ist APatch? 5 | APatch ist eine Root-Lösung, ähnlich wie Magisk oder KernelSU, welche das Beste beider vereint. 6 | Es kombiniert Magisks bequeme und leichte Installationsmethode über `boot.img` mit KernelSUs starken Kernel-Korrektur-Fähigkeiten. 7 | 8 | 9 | ## Was ist der Unterschied zwischen APatch und Magisk? 10 | - Magisk modifiziert das Init-System mit einer Korrektur in Ihrem boot image ramdisk, während APatch den Kernel direkt korrigiert. 11 | 12 | 13 | ## APatch gegen KernelSU 14 | - KernelSU benötigt den Quellcode für dein Geräte-Kernel, welcher nicht immer von der OEM gegeben wird. APatch funktioniert allein mit Ihrem Standard-`boot.img`. 15 | 16 | 17 | ## APatch gegen Magisk, KernelSU 18 | - APatch erlaubt Ihnen, optional, nicht SELinux zu modifizieren, was bedeutet, dass ein App-Kontext gerootet werden kann, sodass libsu und IPC nicht benötigt werden. 19 | - **Kernel Korrektur Modul** gegeben. 20 | 21 | 22 | ## Was ist ein Kernel Korrigtur Modul? 23 | Etwas Code läuft im Kernelbereich, ähnlich zu Ladbaren Kernel-Modulen (LKM). 24 | 25 | Dazu stellt KPM die Fähigkeit bereit, "inline-hook", "syscall-table-hook" im Kernelbereich zu machen. 26 | 27 | Für mehr Informationen, siehe [Wie schreibt man ein KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) 28 | 29 | 30 | ## Beziehung zwischen APatch und KernelPatch 31 | 32 | APatch benötigt KernelPatch, übernimmt all seine Fähigkeiten und wurde erweitert. 33 | 34 | Sie können nur KernelPatch installieren, aber dies wird Ihnen nicht erlauben, Magisk-Module zu nutzen. 35 | 36 | [Lerne mehr über KernelPatch](https://github.com/bmax121/KernelPatch) 37 | 38 | 39 | ## Was ist SuperKey? 40 | KernelPatch fügt einen neuen System-Abruf (syscall) hinzu, um alle Möglichkeiten, Apps und anderen Programmen im Benutzerbereich, bereitzustellen, dieser System-Abruf ist bekannt als **SuperCall**. 41 | Wenn eine App / ein Programm versucht, **SuperCall** abzurufen, muss es ein Berechtigungsnachweis vorlegen, bekannt als **SuperKey**. 42 | **SuperCall** kann nur erfolgreich abgerufen werden, wenn der **SuperKey** korrekt ist und wenn nicht, dann bleibt der Rufer unbetroffen. 43 | 44 | 45 | ## Was ist mit SELinux? 46 | - KernelPatch bearbeitet den SELinux-Kontext nicht und umgeht den SELinux über einen Haken. 47 | Dies erlaubt Ihnen, einen Android-Kontext in dem App-Kontext, ohne dem Gebrauch, libsu zu nutzen, um einen neuen Prozess zu starten und darauf IPC auszuführen, zu rooten. 48 | Dies ist bequem. 49 | - Dazu verwendet APatch direkt das Magiskkonzept, um weitere SELinux-Unterstützung bereitzustellen. 50 | -------------------------------------------------------------------------------- /docs/en/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | 4 | ## What is APatch? 5 | APatch is a root solution similar to Magisk or KernelSU that unites the best of both. 6 | It combines Magisk's convenient and easy install method through `boot.img` with KernelSU's powerful kernel patching abilities. 7 | 8 | 9 | ## What's the difference between APatch and Magisk? 10 | - Magisk modifies the init system with a patch in your boot image's ramdisk, while APatch patches the kernel directly. 11 | 12 | 13 | ## APatch vs KernelSU 14 | - KernelSU requires the source code for your device's kernel which is not always provided by the OEM. APatch works with just your stock `boot.img`. 15 | 16 | 17 | ## APatch vs Magisk, KernelSU 18 | - APatch allows you to optionally not modify SELinux, this means that the APP thread can be rooted, libsu and IPC are not necessary. 19 | - **Kernel Patch Module** provided. 20 | 21 | 22 | ## What is Kernel Patch Module? 23 | Some code runs in Kernel Space, similar to Loadable Kernel Modules (LKM). 24 | 25 | Additionally, KPM provides the ability to do inline-hook, syscall-table-hook in kernel space. 26 | 27 | For more information, see [How to write a KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) 28 | 29 | 30 | ## Relationship between APatch and KernelPatch 31 | 32 | APatch depends on KernelPatch, inherits all its capabilities, and has been expanded. 33 | 34 | You can install KernelPatch only, but this will not allow you to use Magisk modules 35 | 36 | [Learn more about KernelPatch](https://github.com/bmax121/KernelPatch) 37 | 38 | 39 | ## What is SuperKey? 40 | KernelPatch adds a new system call (syscall) to provide all capabilities to apps and programs in userspace, this syscall is referred to as **SuperCall**. 41 | When an app/program tries to invoke **SuperCall**, it needs to provide an access credential, known as the **SuperKey**. 42 | **SuperCall** can only be successfully invoked if the **SuperKey** is correct and if it's not the caller will remain unaffected. 43 | 44 | 45 | ## How about SELinux? 46 | - KernelPatch doesn't modify the SELinux context and bypasses SELinux via a hook. 47 | This allows you to root an Android thread within the app context without the need to use libsu to start a new process and then perform IPC. 48 | This is very convenient. 49 | - In addition, APatch directly utilizes magiskpolicy to provide additional SELinux support. 50 | -------------------------------------------------------------------------------- /docs/es/faq_es.md: -------------------------------------------------------------------------------- 1 | # Preguntas frecuentes 2 | 3 | ## ¿Qué es APatch? 4 | APatch es una solución de root similar a Magisk o KernelSU que une lo mejor de ambos. 5 | Combina el método de instalación conveniente y fácil de Magisk a través de un patche en `boot.img` y las potentes capacidades de KernelSU para parchear el kernel. 6 | 7 | 8 | ## Diferencias entre APatch y Magisk 9 | - Magisk modifica el sistema de init (el sistema que inicializa todos los subsistemas del dispositivo) con un parche en la ramdisk de tu imagen `boot.img`, mientras que APatch parchea el kernel directamente. 10 | 11 | 12 | ## Diferencias entre APatch y KernelSU 13 | - KernelSU requiere el código fuente del kernel del dispositivo, que no siempre es brindado por el fabricante del mismo. APatch solo requiere la imagen `boot.img` 14 | 15 | 16 | ## Diferencias entre APatch y ambas soluciones de root 17 | - APatch permite opcionalmente no modificar el contexto de SELinux. También da acceso root a las apps en su propio contexto, lo cual significa que no hay necesidad de usar libsu ni IPC. 18 | - Se añaden los **KPM** (Kernel Patch Modules), explicados abajo 19 | 20 | 21 | ## ¿Qué es un Kernel Patch Module (KPM, Módulo de Parche del Kernel)? 22 | Permite implementar código personalizado que corra en kernelspace, es decir, en el mismo entorno que el kernel. Funciona similar a los módulos de kernel (los LKM, Loadable Kernel Modules) que se cargan con insmod/rmmod/modprobe/etc. 23 | 24 | Además, KPM permite hacer inline-hooks ("engancharse" a una función en el mismo lugar de memoria, he de ahí el "inline") y syscall-table-hook (modificar la tabla de syscalls) 25 | 26 | Para más información, lee [Como escribir un KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) (en Inglés) 27 | 28 | 29 | ## Relación entre APatch y KernelPatch 30 | APatch depende de KernelPatch, hereda todas sus capacidades y las expande. 31 | 32 | Puedes instalar solo KernelPatch, pero esto no va a permitir que uses módulos de Magisk, y para poder administrar el acceso root vas a tener que instalar AndroidPatch y desinstalarlo. 33 | 34 | [Aprende más de KernelPatch](https://github.com/bmax121/KernelPatch) (en Inglés) 35 | 36 | 37 | ## ¿Qué es SuperKey? 38 | KernelPatch agrega una nueva syscall (llamada al sistema) para dar todas las capacidades a apps y programas en userspace (el espacio del usuario fuera del kernel), y esta syscall se llama **SuperCall**. 39 | Cuando una app o programa intenta llamar **SuperCall**, necesita proveer una credencial de acceso llamada **SuperKey**. 40 | **SuperCall** solo se puede llamar si se provee una **SuperKey** correcta, caso contrario la app/programa que lo llame quedará intacta. 41 | 42 | 43 | ## ¿Y qué pasa con SELinux? 44 | - KernelPatch no modifica el contexto de SELinux y simplemente se lo salta a través de un hook, lo cual permite que des acceso root a un hilo de Android dentro del contexto de la aplicación sin necesidad de usar libsu para iniciar un nuevo proceso y luego comunicarte con él a través de IPC (Inter-Process Communication) 45 | - Además, APatch usa magiskpolicy directamente para obtener mayor soporte de SELinux. Solo esto va a ser detectado como Magisk, y cualquiera que lo desee puede intentar evitar esto ya que el problema ya está bastante claro. 46 | -------------------------------------------------------------------------------- /docs/fr/faq_fr.md: -------------------------------------------------------------------------------- 1 | ## Foire aux questions (FAQ) 2 | 3 | ## Qu'est-ce qu'APatch ? 4 | 5 | APatch est une méthode de root, similaire à Magisk ou KernelSU, offrant encore plus de fonctionnalités. 6 | 7 | ## Quelle est la différence entre APatch et Magisk ? 8 | 9 | - Magisk modifie init, tandis qu'APatch patche le noyau Linux. 10 | 11 | ## Quelle est la différence entre APatch et KernelSU ? 12 | 13 | - KernelSU nécessite le code source. APatch n'a besoin que du fichier boot.img. 14 | 15 | ## Quelle est la différence entre APatch, Magisk et KernelSU ? 16 | 17 | - Optionnellement, ne modifie pas SELinux. Root dans le contexte d'application Android, libsu et d'IPC non nécessaires 18 | - Fournit **Kernel Patch Module** 19 | 20 | ## Qu'est-ce que Kernel Patch Module ? 21 | 22 | Certains codes s'exécutent dans l'espace du noyau, à l'instar des modules noyau chargeables (LKM, Loadable Kernel Modules). 23 | 24 | De plus, KPM offre la possibilité d'effectuer des inline-hook, syscall-table-hook dans l'espace noyau. 25 | 26 | [Comment écrire un module KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/fr/module.md) 27 | 28 | ## Relation entre APatch et KernelPatch 29 | 30 | APatch dépend de KernelPatch, héritant de toutes ses fonctionnalités, et l'étendant. 31 | 32 | Vous pouvez installer KernelPatch seul, mais cela ne vous permettra pas d'utiliser de module Magisk. 33 | Pour utiliser la gestion super utilisateur, vous devez installer AndroidPatch puis le désinstaller. 34 | 35 | [En savoir plus sur KernelPatch](https://github.com/bmax121/KernelPatch) 36 | 37 | ## Qu'est-ce que la clé (SuperKey) ? 38 | 39 | KernelPatch fournit toutes les fonctionnalités à l'espace utilisateur en effectuant un appel système appelé **SuperCall**. 40 | L'appel du SuperCall nécessite le passage d'un type d'informations d'identification appelé **SuperKey**. 41 | Un SuperCall ne peut être effectué avec succès que si la clé est correcte. Si la clé est incorrecte, l'appelant ne sera pas affecté. 42 | 43 | ## Qu'en est-il de SELinux ? 44 | 45 | - KernelPatch ne modifie pas le contexte SELinux et contourne SELinux via des hooks. 46 | Cela vous permet de rooter un processus Android dans le contexte de applicatif, sans avoir à démarrer un nouveau processus avec libsu et ensuite exécuter l'IPC. 47 | - De plus, APatch fournit un support SELinux supplémentaire directement via magiskpolicy. 48 | Cependant, seul ce dernier sera détecté en tant que Magisk. Toute personne intéressée peut essayer de le contourner, le problème est déjà très clair. 49 | -------------------------------------------------------------------------------- /docs/id/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## Apa itu APatch? 4 | APatch adalah solusi root yang mirip dengan Magisk atau KernelSU yang menyatukan yang terbaik dari keduanya. 5 | Ia menggabungkan metode instalasi Magisk yang mudah dan praktis melalui `boot.img` dengan kemampuan patching kernel KernelSU yang hebat. 6 | 7 | ## Apa perbedaan antara APatch dan Magisk? 8 | 9 | - Magisk memodifikasi sistem init dengan patch di ramdisk boot image Anda, sementara APatch menambal kernel secara langsung. 10 | 11 | ## APatch vs KernelSU 12 | - KernelSU memerlukan kode sumber untuk kernel perangkat Anda yang tidak selalu disediakan oleh OEM. APatch hanya berfungsi dengan `boot.img` bawaan Anda. 13 | 14 | ## APatch vs Magisk, KernelSU 15 | - APatch memungkinkan Anda untuk secara opsional tidak memodifikasi SELinux, ini berarti bahwa utas APP dapat di-root, libsu dan IPC tidak diperlukan. 16 | 17 | - **Modul Patch Kernel** disediakan. 18 | 19 | ## Apa itu Modul Patch Kernel? Beberapa kode berjalan di Kernel Space, mirip dengan Loadable Kernel Modules (LKM). 20 | 21 | Selain itu, KPM menyediakan kemampuan untuk melakukan inline-hook, syscall-table-hook di kernel space. 22 | 23 | Untuk informasi selengkapnya, lihat [Cara menulis KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) 24 | 25 | ## Hubungan antara APatch dan KernelPatch 26 | 27 | APatch bergantung pada KernelPatch, mewarisi semua kemampuannya, dan telah diperluas. 28 | 29 | Anda hanya dapat menginstal KernelPatch, tetapi ini tidak akan memungkinkan Anda untuk menggunakan modul Magisk 30 | 31 | [Pelajari selengkapnya tentang KernelPatch](https://github.com/bmax121/KernelPatch) 32 | 33 | ## Apa itu SuperKey? 34 | KernelPatch menambahkan panggilan sistem baru (syscall) untuk menyediakan semua kemampuan bagi aplikasi dan program di userspace, syscall ini disebut sebagai **SuperCall**. Saat aplikasi/program mencoba memanggil **SuperCall**, aplikasi/program tersebut perlu menyediakan kredensial akses, yang dikenal sebagai **SuperKey**. 35 | **SuperCall** hanya dapat dipanggil dengan sukses jika **SuperKey** benar dan jika tidak, pemanggil tidak akan terpengaruh. 36 | 37 | ## Bagaimana dengan SELinux? 38 | - KernelPatch tidak mengubah konteks SELinux dan melewati SELinux melalui hook. 39 | 40 | Ini memungkinkan Anda untuk melakukan root pada thread Android dalam konteks aplikasi tanpa perlu menggunakan libsu untuk memulai proses baru dan kemudian melakukan IPC. 41 | 42 | Ini sangat praktis. 43 | 44 | - Selain itu, APatch secara langsung menggunakan magiskpolicy untuk menyediakan dukungan SELinux tambahan. -------------------------------------------------------------------------------- /docs/it/faq_it.md: -------------------------------------------------------------------------------- 1 | # Domande frequenti 2 | 3 | ## Cos'è APatch 4 | 5 | APatch è una soluzione per il root simile a Magisk o KernelSU, ma offre funzionalità aggiuntive. 6 | 7 | ## APatch vs Magisk 8 | 9 | - Magisk modifica l'init, mentre APatch patcha il kernel Linux. 10 | 11 | ## APatch vs KernelSU 12 | 13 | - KernelSU richiede il codice sorgente del kernel, mentre per APatch è sufficiente solo il file boot.img. 14 | 15 | ## APatch vs Magisk, KerenlSU 16 | 17 | - Opzionalmente, non modifica SELinux. 18 | - Consente di ottenere i permessi di root nel contesto dell'app Android, senza la necessità di libsu e IPC. 19 | - Possibilità di utilizzare **Kernel Patch Module** 20 | 21 | ## Cos'è un Kernel Patch Module 22 | 23 | È del codice eseguito nello spazio del Kernel, simile ai Loadable Kernel Modules (LKM). 24 | 25 | Inoltre, KPM fornisce la possibilità di effettuare inline-hook e syscall-table-hook nello spazio del kernel. 26 | 27 | [Come scrivere un KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) 28 | 29 | ## Relazione tra APatch e KernelPatch 30 | 31 | APatch dipende da KernelPatch, eredita tutte le sue capacità ed è stato ampliato. 32 | 33 | Puoi installare solo KernelPatch, ma ciò non consentirà l'uso dei moduli Magisk. 34 | Per gestire i permessi di root, è necessario installare AndroidPatch e successivamente disinstallarlo. 35 | 36 | [Scopri di più su KernelPatch](https://github.com/bmax121/KernelPatch) 37 | 38 | ## Cos'è la SuperKey 39 | 40 | KernelPatch aggancia le chiamate di sistema per fornire tutte le capacità allo spazio utente, e questa chiamata di sistema è chiamata **SuperCall**. 41 | Invocare SuperCall richiede il passaggio di una credenziale, nota come **SuperKey**. 42 | SuperCall può essere invocato con successo solo quando la SuperKey è corretta; se la SuperKey è errata, chi effettua la chiamata rimane inalterato. 43 | 44 | ## Riguardo a SELinux 45 | 46 | - KernelPatch non modifica il contesto SELinux e bypassa SELinux tramite hook, 47 | consentendo di ottenere i privilegi di root in un thread Android all'interno del contesto dell'app senza la necessità di utilizzare libsu per avviare un nuovo processo e quindi eseguire IPC. 48 | Questo è molto conveniente. 49 | - Inoltre, APatch utilizza direttamente magiskpolicy per fornire ulteriore supporto SELinux. 50 | Tuttavia, solo questo sarà rilevato come Magisk. Chiunque sia interessato può cercare di bypassarlo; il problema è già abbastanza chiaro. -------------------------------------------------------------------------------- /docs/kr/faq_kr.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | 4 | ## APatch가 뭔가요? 5 | APatch는 Magisk와 KernelSU와 유사한 루팅툴로, 두 가지의 장점을 결합하였습니다. 6 | `boot.img`을 통해 설치하는 Magisk의 편리하고 간편한 방법과 강력한 커널 패치 기능을 제공하는 KernelSU를 결합하였습니다. 7 | 8 | 9 | ## APatch와 Magisk의 차이점은 무엇인가요? 10 | - Magisk는 부트 이미지의 램디스크에 패치를 적용하여 초기 시스템을 수정하는 반면, APatch는 커널을 직접 패치합니다. 11 | 12 | 13 | ## APatch vs KernelSU 14 | - KernelSU는 기기의 커널 소스 코드가 필요하지만 OEM에서 항상 제공하지는 않습니다. 반면, APatch는 단지 여러분의 기본 `boot.img`만으로 작동합니다. 15 | 16 | 17 | ## APatch vs Magisk, KernelSU 18 | - APatch는 선택적으로 SELinux를 수정하지 않을 수 있어, APP 스레드를 루팅할 수 있으며, libsu와 IPC가 필요하지 않습니다. 19 | - **커널 패치 모듈**을 제공합니다.. 20 | 21 | 22 | ## 커널 패치 모듈이 뭔가요? 23 | 커널 패치 모듈은 Loadable Kernel Modules (LKM)과 유사하게 커널 공간에서 실행되는 코드입니다. 24 | 25 | 또한, KPM은 커널에서 인라인 훅과 시스템 콜 테이블 훅을 수행할 수 있습니다. 26 | 27 | 자세한 정보는 [KPM 작성 방법](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md)에서 확인하세요. 28 | 29 | 30 | ## APatch와 KernelPatch의 관계 31 | 32 | APatch는 KernelPatch에 의존하며, 모든 기능을 상속받고 확장되었습니다. 33 | 34 | KernelPatch만 설치할 수도 있지만, 이 경우 Magisk 모듈을 사용할 수 없습니다. 35 | 36 | [KernelPatch에 대해 더 알아보기](https://github.com/bmax121/KernelPatch) 37 | 38 | 39 | ## SuperKey가 뭔가요? 40 | KernelPatch는 사용자 공간의 앱 및 프로그램에 모든 기능을 제공하는 새로운 시스템 콜 (syscall)을 추가하며, 이것을 **SuperCall**이라고 합니다. 41 | 앱이나 프로그램이 **SuperCall**를 호출하려고 할 때, 접근 자격증명인 **SuperKey**를 제공해야 합니다. 42 | **SuperCall**은 **SuperKey**가 정확하고 호출자에게 영향을 미치지 않을 경우에만 성공적으로 수행됩니다. 43 | 44 | 45 | ## SELinux는 어떻게 다루나요? 46 | - KernelPatch는 SELinux 컨텍스트를 수정하지 않고 SELinux를 우회하는 훅을 사용합니다. 47 | 이를 통해 앱 컨텍스트 내에서 안드로이드 스레드를 루팅할 수 있으며, 새 프로세스를 시작하고 IPC를 수행하기 위해 libsu를 사용할 필요가 없습니다. 48 | 이 방법은 매우 편리합니다. 49 | - 또한, APatch는 추가적인 SELinux 지원을 제공하기 위해 직접 magiskpolicy를 활용합니다. 50 | -------------------------------------------------------------------------------- /docs/pt_br/faq_pt_br.md: -------------------------------------------------------------------------------- 1 | # Perguntas frequentes 2 | 3 | ## O que é APatch? 4 | 5 | APatch é uma solução root semelhante ao Magisk ou KernelSU que une o melhor de ambos. Ele combina o método de instalação fácil e conveniente do Magisk por meio do `boot.img` com as poderosas habilidades de patch de kernel do KernelSU. 6 | 7 | ## Qual é a diferença entre APatch e Magisk? 8 | 9 | - Magisk modifica o sistema init com um patch no ramdisk da sua imagem de inicialização, enquanto o APatch corrige o kernel diretamente. 10 | 11 | ## APatch vs KernelSU 12 | 13 | - KernelSU requer o código-fonte do kernel de seu dispositivo, que nem sempre é fornecido pelo OEM. APatch funciona apenas com seu `boot.img` stock. 14 | 15 | ## APatch vs Magisk e KernelSU 16 | 17 | - APatch permite opcionalmente não modificar o SELinux, isso significa que o thread do app pode ser rooteado, libsu e IPC não são necessários. 18 | - **Módulo KernelPatch** fornecido. 19 | 20 | ## O que é Módulo KernelPatch? 21 | 22 | Alguns códigos são executados no Kernel Space, semelhante ao Loadable Kernel Modules (LKM). 23 | 24 | Além disso, o KPM fornece a capacidade de executar inline-hook e syscall-table-hook no Kernel Space. 25 | 26 | Para mais informações, veja [como escrever um KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md). 27 | 28 | ## Relacionamento entre APatch e KernelPatch 29 | 30 | APatch depende do KernelPatch. Ele herda todas as suas capacidades e foi expandido. 31 | 32 | Você pode instalar apenas o KernelPatch, mas isso não permitirá o uso de módulos Magisk. 33 | 34 | [Saiba mais sobre o KernelPatch](https://github.com/bmax121/KernelPatch). 35 | 36 | ## O que é SuperKey? 37 | 38 | KernelPatch conecta chamadas do sistema para fornecer todos os recursos ao espaço do usuário, e essa chamada do sistema é chamada de **SuperCall**. Invocar o SuperCall requer a passagem de uma credencial, conhecida como **SuperKey**. SuperCall só pode ser invocado com sucesso quando a SuperKey estiver correta. Se a SuperKey estiver incorreta, o chamador não será afetado. 39 | 40 | ## E o SELinux? 41 | 42 | - KernelPatch não modifica o contexto do SELinux e ignora o SELinux via hook. Isso permite que você faça root em um thread do Android dentro do contexto do app sem a necessidade de usar libsu para iniciar um novo processo e então executar o IPC. 43 | - Além disso, o APatch utiliza diretamente o magiskpolicy para fornecer suporte adicional ao SELinux. 44 | -------------------------------------------------------------------------------- /docs/ru/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponces/APatchLite/98f9cb734d936e3f5716252429dcdb6a026851f0/docs/ru/.gitkeep -------------------------------------------------------------------------------- /docs/ru/faq_ru.md: -------------------------------------------------------------------------------- 1 | # Часто задаваемые вопросы 2 | 3 | 4 | ## Что такое APatch? 5 | APatch - это root решение, похожее на Magisk или KernelSU, которое объединяет лучшее из обоих. 6 | Оно сочетает удобный и простой метод установки Magisk через `boot.img` с мощными возможностями исправления ядра KernelSU. 7 | 8 | ## В чем разница между APatch и Magisk? 9 | - Magisk изменяет систему инициализации с помощью патча на RAM-диске вашего загрузочного образа, в то время как APatch вносит изменения непосредственно в ядро. 10 | 11 | 12 | ## APatch против KernelSU 13 | - Для KernelSU требуется исходный код ядра вашего устройства, который не всегда предоставляется OEM-производителем. APatch работает напрямую с вашим исходным `boot.img`. 14 | 15 | 16 | ## APatch против Magisk, KernelSU 17 | - APatch позволяет вам при желании не изменять SELinux. Это означает, что поток приложения может быть рутирован, в libsu и IPC нет необходимости. 18 | - **Kernel Patch Module** предоставляется. 19 | 20 | 21 | ## Что такое Kernel Patch Module? 22 | Некоторый код выполняется в пространстве ядра, аналогично загружаемым модулям ядра (Loadable Kernel Modules, LKM). 23 | 24 | Кроме того, KPM предоставляет возможность выполнять inline-hook, syscall-table-hook в пространстве ядра. 25 | 26 | Для получения дополнительной информации смотрите [Как написать KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) 27 | 28 | 29 | ## Связь между APatch и KernelPatch 30 | 31 | APatch основан на KernelPatch, унаследовал все его возможности и был расширен. 32 | 33 | Вы можете установить только KernelPatch, но это не позволит вам использовать модули Magisk, а чтобы использовать управление суперпользователем, вам необходимо установить AndroidPatch, а затем удалить его. 34 | 35 | [Узнать больше о KernelPatch](https://github.com/bmax121/KernelPatch) 36 | 37 | 38 | ## Что такое SuperKey? 39 | KernelPatch добавляет новый системный вызов (syscall) для предоставления всех возможностей приложениям и программам в пользовательском пространстве. Этот системный вызов называется **SuperCall**. 40 | Когда приложение/программа пытается вызвать **SuperCall**, ему необходимо предоставить учетные данные для доступа, называемые **SuperKey**. 41 | **SuperCall** может быть успешно вызван только в том случае, если **SuperKey** правильный, а в противном случае вызывающий объект останется незатронутым. 42 | 43 | 44 | ## Что насчет SELinux? 45 | - KernelPatch не изменяет контекст SELinux и обходит SELinux с помощью перехвата. 46 | Это позволяет вам рутировать поток Android в контексте приложения без необходимости использовать libsu для запуска нового процесса и последующего выполнения IPC. 47 | Это очень удобно. 48 | - Кроме того, APatch напрямую использует magiskpolicy для обеспечения дополнительной поддержки SELinux. 49 | -------------------------------------------------------------------------------- /docs/tr/faq_tr.md: -------------------------------------------------------------------------------- 1 | # SSS 2 | 3 | ## APatch nedir? 4 | 5 | APatch, Magisk veya KernelSU benzeri ve her ikisinin de en iyi yönlerini birleştiren bir root çözümdür. Magisk'in `boot.img` aracılığıyla kullanışlı ve kolay kurulum yöntemini ve KernelSU'nun güçlü kernel yamalama yeteneklerini birleştirir. 6 | 7 | ## APatch ve Magisk arasındaki fark nedir? 8 | 9 | - Magisk başlangıç sistemini kernel'in ramdisk'indeki bir yama ile değiştirirken; APatch kernel'i doğrudan yamalar. 10 | 11 | ## APatch vs KernelSU 12 | 13 | - KernelSU, cihazınızın kernel'i için her zaman OEM tarafından sağlanmayan kaynak kodunu gerektirir. APatch yalnızca stok `boot.img` dosyanızla çalışır. 14 | 15 | ## APatch vs Magisk, KernelSU 16 | 17 | - İsteğe bağlı olarak SELinux'u değiştirmez. Bu şu anlama gelir; android uygulama parçacığının root'lanması için libsu ve IPC'ye gerek yoktur. 18 | - **Kernel Patch (Yama) Modülü** sağlanır. 19 | 20 | ## Kernel Patch (Yama) Modülü nedir? 21 | 22 | Bazı kodlar, Yüklenebilir Kernel Modüllerinde (LKM) olduğu gibi Kernel Seviyesinde çalışır. 23 | 24 | Ek olarak KPM, kernel sviyesinde satır içi kanca, sistem çağrısı-tablo kancası yapma yeteneği sağlar. 25 | 26 | Daha fazla bilgi için bkz [KPM nasıl yazılır?](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) 27 | 28 | ## APatch and KernelPatch arasındaki ilişki 29 | 30 | APatch, KernelPatch'e dayalıdır; onun tüm yeteneklerini devralır ve onu daha da geliştirmiştir. 31 | 32 | Yalnızca KernelPatch'i kurabilirsiniz ancak bu magisk modüllerini kullanmanıza izin vermez, 33 | Superuser yönetimini kullanmak için de AndroidPatch'i yüklemeniz ve ardından kaldırmanız gerekir. 34 | 35 | [KernelPatch hakkında daha fazlasını öğrenin](https://github.com/bmax121/KernelPatch) 36 | 37 | ## Süper Anahtar nedir? 38 | 39 | KernelPatch, tüm yetenekleri kullanıcı alanına (userspace) sağlamak için sistem çağrılarını (syscall) kancalar ve bu sistem çağrısına **SuperCall** denir. 40 | Bir uygulama/program SuperCall'ı çağırmayı denediğinde, **Süper Anahtar** olarak bilinen bir kimlik bilgisinin iletilmesini gerektirir. 41 | SuperCall yalnızca Süper Anahtar doğru olduğunda başarılı bir şekilde çağrılabilir; Süper Anahtar hatalıysa çağıran bundan etkilenmez. 42 | 43 | ## Peki ya SELinux? 44 | 45 | - KernelPatch, SELinux içeriğini değiştirmez ve SELinux'u kanca yoluyla atlamaz. Bu, yeni bir işlem başlatmak ve ardından IPC gerçekleştirmek için libsu kullanmanıza gerek kalmadan, uygulama bağlamında bir Android iş parçacığını rootlamanıza olanak tanır. Bu çok kullanışlıdır. 46 | - Ayrıca APatch, ek SELinux desteği sağlamak için doğrudan magiskpolicy'yi kullanır. 47 | -------------------------------------------------------------------------------- /docs/uk/faq_uk.md: -------------------------------------------------------------------------------- 1 | # Поширені запитання 2 | 3 | 4 | ## Що таке APatch? 5 | APatch - це root рішення, схоже на Magisk або KernelSU, яке поєднує найкраще з обох. 6 | Воно поєднує зручний і простий метод установки Magisk через boot.img з потужними можливостями виправлення ядра KernelSU. 7 | 8 | ## У чому різниця між APatch та Magisk? 9 | - Magisk змінює систему ініціалізації за допомогою патча на RAM диску вашого завантажувального образу, в той час як APatch вносить зміни безпосередньо в ядро. 10 | 11 | 12 | ## APatch проти KernelSU 13 | - Для KernelSU потрібен вихідний код ядра вашого пристрою, який не завжди надається OEM-виробником. APatch працює безпосередньо з вашим вихідним `boot.img`. 14 | 15 | 16 | ## APatch проти Magisk, KernelSU 17 | - APatch дозволяє вам за бажання не змінювати SELinux. Це означає, що потік програми може бути рутований, в libsu і IPC немає необхідності. 18 | - **Kernel Patch Module** надається. 19 | 20 | 21 | ## Що таке Kernel Patch Module? 22 | Деякий код виконується у просторі ядра, аналогічно модулям ядра (Loadable Kernel Modules, LKM). 23 | 24 | Крім того, KPM надає можливість виконувати inline-hook, syscall-table-hook у просторі ядра. 25 | 26 | Для отримання додаткової інформації дивіться [Як написати KPM](https://github.com/bmax121/KernelPatch/blob/main/doc/module.md) 27 | 28 | 29 | ## Зв'язок між APatch та KernelPatch 30 | 31 | APatch заснований на KernelPatch, успадкував усі його можливості та був розширений. 32 | 33 | Ви можете встановити тільки KernelPatch, але це не дозволить вам використовувати модулі Magisk, а щоб використовувати керування суперкористувачем, вам необхідно встановити AndroidPatch, а потім видалити його. 34 | 35 | [Дізнатись більше про KernelPatch](https://github.com/bmax121/KernelPatch) 36 | 37 | 38 | ## Що таке SuperKey? 39 | KernelPatch додає новий системний дзвінок (syscall) для надання всіх можливостей додаткам і програмам в просторі користувача. Цей системний виклик називається **SuperCall**. 40 | Коли програма намагається викликати **SuperCall**, їй необхідно надати облікові дані для доступу, які називають **SuperKey**. 41 | **SuperCall** може бути успішно викликаний тільки в тому випадку, якщо **SuperKey** правильний, а в іншому випадку об'єкт, що викликає, не змінюється. 42 | 43 | 44 | ## Як щодо SELinux? 45 | - KernelPatch не змінює контекст SELinux та обходить SELinux за допомогою перехоплення. 46 | Це дозволяє вам рутувати потік Android у контексті програми без необхідності використовувати libsu для запуску нового процесу та подальшого виконання IPC. 47 | Це дуже зручно. 48 | - Крім того, APatch безпосередньо використовує magiskpolicy для забезпечення додаткової підтримки SELinux. 49 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | The patching of Android kernel and Android system. 2 |

    3 |
  • A new kernel-based root solution for Android devices.
  • 4 |
  • APM: Support for modules similar to Magisk.
  • 5 |
  • KPM: Support for modules that allow you to inject any code into the kernel (Requires kernel function inline-hook and syscall-table-hook enabled).
  • 6 |
  • APatch relies on KernelPatch.
  • 7 |
  • The APatch UI and the APModule source code have been derived and modified from KernelSU.
  • 8 |
9 |
    10 |
  • Only supports the ARM64 architecture.
  • 11 |
  • Only supports Android kernel versions 3.18 - 6.1
  • 12 |
13 | Support for Samsung devices with security protection: Planned 14 | Kernel configs: 15 |
    16 |
  • 17 | CONFIG_KALLSYMS=y and CONFIG_KALLSYMS_ALL=y 18 |
  • 19 |
  • 20 | CONFIG_KALLSYMS=y and CONFIG_KALLSYMS_ALL=n: Initial support 21 |
  • 22 |
23 | The SuperKey has higher privileges than root access. 24 | Weak or compromised keys can lead to unauthorized control of your device. 25 | It is critical to use robust keys and safeguard them from exposure to maintain the security of your device. 26 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- 1 | ../../../../../app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | The patching of Android kernel and Android system 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.experimental.enableNewResourceShrinker.preciseShrinking=true 2 | android.enableAppCompileTimeRClass=true 3 | android.useAndroidX=true 4 | 5 | org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.7.2" 3 | kotlin = "2.0.21" 4 | ksp = "2.0.21-1.0.28" 5 | compose-bom = "2024.11.00" 6 | lifecycle = "2.8.7" 7 | compose-destination = "2.1.0-beta14" 8 | libsu = "6.0.0" 9 | sheets-compose-dialogs = "1.3.0" 10 | 11 | [plugins] 12 | agp-app = { id = "com.android.application", version.ref = "agp" } 13 | agp-lib = { id = "com.android.library", version.ref = "agp" } 14 | kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 15 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 16 | lsplugin-apksign = { id = "org.lsposed.lsplugin.apksign", version = "1.4" } 17 | lsplugin-resopt = { id = "org.lsposed.lsplugin.resopt", version = "1.6" } 18 | lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version = "1.2" } 19 | kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 20 | 21 | [libraries] 22 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version = "1.7.0" } 23 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.9.3" } 24 | androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version = "1.0.1" } 25 | androidx-webkit = { group = "androidx.webkit", name = "webkit", version = "1.12.1" } 26 | 27 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } 28 | androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } 29 | androidx-compose-material = { group = "androidx.compose.material", name = "material" } 30 | androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.4.0-alpha04" } # For material3 PullToRefresh, work around https://issuetracker.google.com/issues/359949836, remove the `version` when 1.4.0 released 31 | androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } 32 | androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } 33 | androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } 34 | androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } 35 | androidx-compose-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata" } 36 | 37 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } 38 | androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } 39 | androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } 40 | 41 | com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } 42 | com-github-topjohnwu-libsu-service = { group = "com.github.topjohnwu.libsu", name = "service", version.ref = "libsu" } 43 | com-github-topjohnwu-libsu-nio = { group = "com.github.topjohnwu.libsu", name = "nio", version.ref = "libsu" } 44 | com-github-topjohnwu-libsu-io = { group = "com.github.topjohnwu.libsu", name = "io", version.ref = "libsu" } 45 | 46 | dev-rikka-rikkax-parcelablelist = { module = "dev.rikka.rikkax.parcelablelist:parcelablelist", version = "2.0.1" } 47 | 48 | io-coil-kt-coil-compose = { group = "io.coil-kt", name = "coil-compose", version = "2.7.0" } 49 | 50 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.9.0" } 51 | 52 | me-zhanghai-android-appiconloader-coil = { group = "me.zhanghai.android.appiconloader", name = "appiconloader-coil", version = "1.5.0" } 53 | 54 | compose-destinations-core = { group = "io.github.raamcosta.compose-destinations", name = "core", version.ref = "compose-destination" } 55 | compose-destinations-ksp = { group = "io.github.raamcosta.compose-destinations", name = "ksp", version.ref = "compose-destination" } 56 | 57 | sheet-compose-dialogs-core = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "core", version.ref = "sheets-compose-dialogs" } 58 | sheet-compose-dialogs-list = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "list", version.ref = "sheets-compose-dialogs" } 59 | sheet-compose-dialogs-input = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "input", version.ref = "sheets-compose-dialogs" } 60 | 61 | markdown = { group = "io.noties.markwon", name = "core", version = "4.6.2" } 62 | 63 | timber = { group = "com.jakewharton.timber", name = "timber", version = "5.0.1" } 64 | ini4j = { group = "org.ini4j", name = "ini4j", version = "0.5.4" } 65 | bcpkix = { group = "org.bouncycastle", name = "bcpkix-jdk18on", version = "1.79" } 66 | 67 | cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" } 68 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponces/APatchLite/98f9cb734d936e3f5716252429dcdb6a026851f0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /scripts/update_binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TMPDIR=/dev/tmp 4 | rm -rf $TMPDIR 5 | mkdir -p $TMPDIR 2>/dev/null 6 | 7 | export BBBIN=$TMPDIR/busybox 8 | unzip -o "$3" "busybox" -d $TMPDIR >&2 9 | 10 | for arch in "arm64-v8a" ; do 11 | unzip -o "$3" "lib/$arch/libbusybox.so" -d $TMPDIR >&2 12 | libpath="$TMPDIR/lib/$arch/libbusybox.so" 13 | chmod 755 $libpath 14 | if [ -x $libpath ] && $libpath >/dev/null 2>&1; then 15 | mv -f $libpath $BBBIN 16 | break 17 | fi 18 | done 19 | $BBBIN rm -rf $TMPDIR/lib 20 | 21 | export INSTALLER=$TMPDIR/install 22 | $BBBIN mkdir -p $INSTALLER 23 | $BBBIN unzip -o "$3" "assets/*" "META-INF/com/google/*" "lib/*" "META-INF/com/google/*" -x "lib/*/libbusybox.so" -d $INSTALLER >&2 24 | export ASH_STANDALONE=1 25 | if echo "$3" | $BBBIN grep -q "uninstall"; then 26 | exec $BBBIN sh "$INSTALLER/assets/UninstallAP.sh" "$@" 27 | elif echo "$3" | $BBBIN grep -q "uninstaller"; then 28 | exec $BBBIN sh "$INSTALLER/assets/UninstallAP.sh" "$@" 29 | else 30 | exec $BBBIN sh "$INSTALLER/assets/InstallAP.sh" "$@" 31 | fi -------------------------------------------------------------------------------- /scripts/update_script.sh: -------------------------------------------------------------------------------- 1 | ###################### 2 | # APatch Empty script 3 | # Check update-binary 4 | ###################### 5 | 6 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | google() 7 | mavenCentral() 8 | } 9 | } 10 | 11 | plugins { 12 | id("org.gradle.toolchains.foojay-resolver-convention").version("0.8.0") 13 | } 14 | 15 | dependencyResolutionManagement { 16 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 17 | repositories { 18 | google() 19 | mavenCentral() 20 | maven("https://jitpack.io") 21 | } 22 | } 23 | 24 | rootProject.name = "APatchLite" 25 | include(":app") 26 | --------------------------------------------------------------------------------