Showing preview only (1,549K chars total). Download the full file or copy to clipboard to get everything.
Repository: bmax121/APatch
Branch: main
Commit: a202a4fd83ad
Files: 220
Total size: 1.4 MB
Directory structure:
gitextract_f37ualu1/
├── .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.toml
│ ├── build.rs
│ └── src/
│ ├── apd.rs
│ ├── assets.rs
│ ├── banner
│ ├── cli.rs
│ ├── defs.rs
│ ├── event.rs
│ ├── installer.sh
│ ├── installer_bind.sh
│ ├── lua.rs
│ ├── main.rs
│ ├── metamodule.rs
│ ├── module.rs
│ ├── package.rs
│ ├── pty.rs
│ ├── restorecon.rs
│ ├── sepolicy.rs
│ ├── supercall.rs
│ └── utils.rs
├── app/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── libs/
│ │ └── arm64-v8a/
│ │ └── .gitignore
│ ├── 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
│ │ ├── jni_helper.hpp
│ │ ├── supercall.h
│ │ ├── type_traits.hpp
│ │ ├── uapi/
│ │ │ └── scdefs.h
│ │ └── version
│ ├── 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
│ │ │ │ └── WarningCard.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/
│ │ │ ├── AppIconUtil.kt
│ │ │ ├── Insets.kt
│ │ │ ├── MimeUtil.java
│ │ │ ├── MonetColorsProvider.kt
│ │ │ ├── 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
│ │ └── 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
│ │ ├── play_circle.xml
│ │ ├── settings.xml
│ │ ├── telegram.xml
│ │ ├── trash.xml
│ │ ├── weblate.xml
│ │ └── webui.xml
│ ├── mipmap-anydpi/
│ │ └── ic_launcher.xml
│ ├── resources.properties
│ ├── values/
│ │ ├── arrays.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ ├── values-ab/
│ │ └── strings.xml
│ ├── values-apc/
│ │ └── strings.xml
│ ├── 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-da/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-el/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-fa/
│ │ └── strings.xml
│ ├── values-fi/
│ │ └── 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-lv/
│ │ └── 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/
│ │ └── strings.xml
│ ├── values-pt-rBR/
│ │ └── strings.xml
│ ├── values-ro/
│ │ └── strings.xml
│ ├── values-ru/
│ │ └── strings.xml
│ ├── values-si/
│ │ └── strings.xml
│ ├── values-sv/
│ │ └── strings.xml
│ ├── values-ta/
│ │ └── 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
│ └── xml/
│ ├── backup_rules.xml
│ ├── data_extraction_rules.xml
│ ├── file_paths.xml
│ └── network_security_config.xml
├── build.gradle.kts
├── docs/
│ ├── BG/
│ │ └── faq_bg.md
│ ├── ar/
│ │ └── faq_ar.md
│ ├── 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
│ └── short_description.txt
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── scripts/
│ ├── update_binary.sh
│ └── update_script.sh
└── settings.gradle.kts
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Set the default behavior, in case people don't have core.autocrlf set.
* text eol=lf
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
# *.c text
# *.h text
# Declare files that will always have CRLF line endings on checkout.
*.cmd text eol=crlf
*.bat text eol=crlf
# Denote all files that are truly binary and should not be modified.
tools/** binary
*.jar binary
*.exe binary
*.apk binary
*.png binary
*.jpg binary
*.ttf binary
*.so binary
# Help GitHub detect languages
native/jni/external/** linguist-vendored
native/jni/systemproperties/** linguist-language=C++
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report | 反馈 Bug
description: Report bugs or unexpected behavior | 报告错误或未预料的行为
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thanks for reporting issues of APatch!
To better assist you, please provide the following information.
To avoid duplicate issues, please use English in the title.
For KernelPatch specific issues (e.g., patching failures, kernel panics, etc.), please report them at: [KernelPatch](https://github.com/bmax121/KernelPatch)
感谢给 APatch 汇报问题!
为了使我们更好地帮助你,请提供以下信息。
为了防止重复汇报,标题请务必使用英文。
如果是 KernelPatch 的问题(例如修补失败,内核崩溃等),请移步 [KernelPatch 仓库](https://github.com/bmax121/KernelPatch)。
- type: checkboxes
attributes:
label: Please check before submitting an issue | 在提交 Issue 前请检查
options:
- label: I searched the issues and didn't found anything relevant | 我已经搜索了 Issues 列表,没有发现于本问题相关内容
required: true
- label: If the patch fails or the image cannot be booted after flashing the new boot.img, visit KernelPatch to clarify your doubts | 修复失败或刷入修补后镜像不能启动,请前往 KernelPatch 提问
required: true
- label: I will upload the bug report file in APatch Manager > Settings > Send logs | 我会上传 Bug Report 文件从 APatch 管理器 > 设置 > 发送日志
required: true
- label: I know how to reproduce the issue, which might not be specific to my device | 我知道如何重新复现这个问题
required: false
- type: checkboxes
id: latest
attributes:
label: Version requirements | 版本要求
options:
- label: I'm using the latest CI version of APatch Manager | 我正在使用最新 CI 版本
required: true
- type: textarea
attributes:
label: Bug description | 描述 Bug
description: |
Please enter a clear and concise description of the bug.
对 Bug 的清晰简洁的描述。
validations:
required: true
- type: textarea
attributes:
label: Reproduce method | 复现方法
description: |
Steps to reproduce the bug.
复现的步骤。
placeholder: |
- 1. Go to...
- 2. Click on...
- 3. Scroll down to...
- 4. See error
validations:
required: true
- type: textarea
attributes:
label: Expected behavior | 预期行为
description: |
Please enter a clear and concise description of what you expected to happen.
对你期望发生的行为进行清晰简洁的描述。
validations:
required: true
- type: textarea
attributes:
label: Actual behavior | 实际行为
description: |
Tell us what actually happened.
告诉我们实际发生了什么。
validations:
required: true
- type: textarea
attributes:
label: Screenshots | 截图
description: |
If possible, add screenshots to help explain your issue.
如果可以的话,添加截图可以帮你解释问题。
- type: textarea
attributes:
label: Logs | 日志
description: |
If possible, add the crash log to help us find your issue.
如果可以的话,添加崩溃日志可以帮助我们找到问题。
- type: input
attributes:
label: Device name | 设备名称
validations:
required: true
- type: input
attributes:
label: OS version | 系统版本
validations:
required: true
- type: input
attributes:
label: APatch version | APatch 版本
validations:
required: true
- type: input
attributes:
label: Kernel version | 内核版本
validations:
required: true
- type: input
attributes:
label: KernelPatch version | KernelPatch 版本
validations:
required: true
- type: textarea
attributes:
label: Other information | 其他信息
description: |
Add any information about the issue.
添加关于问题的任何信息。
placeholder: |
Upload logs in .zip format by clicking the bottom bar. Uploading logs to other websites or using external links isn't allowed
点击文本框底栏上传日志压缩包,禁止上传到其它网站或使用外链提供日志
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Ask a question | 提问
url: https://github.com/bmax121/APatch/discussions/new?category=Q-A
about: If you've any questions, ask them here | 如果有任何疑问请在这里提问
- name: Official Telegram channel | 官方 Telegram 频道
url: https://t.me/APatchChannel
about: Subscribe to receive releases and announcements | 可以订阅通知和发行版
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
---
name: Feature request | 新特性请求
description: Suggest an idea for this project | 提出建议
labels: [enhancement]
body:
- type: textarea
attributes:
label: Is your request related to a specific issue? | 你的请求是否与某个问题相关?
description: |
Please enter a clear and concise description of the issue.
请清晰准确表述该问题。
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like | 描述你想要的解决方案
description: |
Please enter a clear and concise description of what you'd like.
请清晰准确描述新特性的预期行为。
validations:
required: true
- type: textarea
attributes:
label: Describe the alternatives you've considered | 描述您考虑过的备选方案
description: |
Please enter a clear and concise description of any alternative solutions or features you've considered.
对您考虑过的任何替代解决方案或功能的清晰简洁的描述。
validations:
required: true
- type: textarea
attributes:
label: Other information | 其他信息
description: |
Add any information or screenshots about the feature request.
其他关于新特性的信息或者截图。
validations:
required: false
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: gradle
directory: "/"
schedule:
interval: daily
target-branch: main
registries:
- maven-google
- gradle-plugin
groups:
maven-dependencies:
patterns:
- "*"
- package-ecosystem: github-actions
target-branch: main
directory: /
schedule:
interval: daily
groups:
action-dependencies:
patterns:
- "*"
- package-ecosystem: cargo
target-branch: main
directory: apd/
schedule:
interval: daily
allow:
- dependency-type: "all"
groups:
rust-dependencies:
patterns:
- "*"
registries:
maven-google:
type: maven-repository
url: "https://dl.google.com/dl/android/maven2/"
gradle-plugin:
type: maven-repository
url: "https://plugins.gradle.org/m2/"
================================================
FILE: .github/scripts/telegram_url.py
================================================
import json
import os
import sys
import urllib.parse
url = f'https://api.telegram.org/bot{os.environ["BOT_TOKEN"]}'
url += f'/sendMediaGroup?chat_id={urllib.parse.quote(sys.argv[1])}&media='
# https://core.telegram.org/bots/api#markdownv2-style
msg = os.environ["COMMIT_MESSAGE"]
for c in ['\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']:
msg = msg.replace(c, f'\\{c}')
commit_url = os.environ["COMMIT_URL"]
commit_id = os.environ["COMMIT_ID"][:7]
caption = f"[{commit_id}]({commit_url})\n{msg}"[:1024]
data = json.dumps([
{"type": "document", "media": "attach://Release1"},
{"type": "document", "media": "attach://Release2", "caption": caption, "parse_mode": "MarkdownV2"}
])
url += urllib.parse.quote(data)
print(url)
================================================
FILE: .github/workflows/build.yml
================================================
name: Build Manager
on:
push:
tags: [ "*" ]
branches: [ "main" ]
paths:
- '.github/workflows/build.yml'
- 'app/**'
- 'apd/**'
- 'build.gradle.kts'
- 'gradle/libs.versions.toml'
pull_request:
branches: [ "main" ]
paths:
- '.github/workflows/build.yml'
- 'app/**'
- 'apd/**'
- 'build.gradle.kts'
- 'gradle/libs.versions.toml'
workflow_call:
workflow_dispatch:
jobs:
build-manager:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Generate version
id: parse_version
run: |
COMMIT_NUM=$(git rev-list --count HEAD)
VERSION=$(echo "$COMMIT_NUM + 200 + 10000" | bc)
echo "Generated Version: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: jetbrains
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
packages: ''
- name: Install toolchain
run: |
rustup default stable
rustup update stable
cargo install cargo-ndk
rustup target install aarch64-linux-android
- name: Cache Rust
uses: Swatinem/rust-cache@v2
with:
workspaces: apd
cache-targets: false
- name: Build with Gradle
run: |
echo 'org.gradle.parallel=true' >> gradle.properties
echo 'org.gradle.vfs.watch=true' >> gradle.properties
echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
echo 'android.native.buildOutput=verbose' >> gradle.properties
sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties
./gradlew clean assembleDebug assembleRelease
echo "BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)" >> $GITHUB_ENV
- name: Sign Release
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
if: ${{ env.SIGNING_KEY != '' }}
continue-on-error: true
uses: kevin-david/zipalign-sign-android-release@v2
id: sign_app
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
zipAlign: true
- name: Sign Debug APK
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
if: ${{ env.SIGNING_KEY != '' }}
continue-on-error: true
uses: kevin-david/zipalign-sign-android-release@v2
id: sign_debug_app
with:
releaseDirectory: app/build/outputs/apk/debug
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
zipAlign: true
- name: Upload mappings
uses: actions/upload-artifact@v6
with:
name: "mappings"
path: "app/build/outputs/mapping/release/"
- name: Upload build artifact1
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
if: ${{ env.SIGNING_KEY != '' }}
uses: actions/upload-artifact@v6
with:
name: APatch-Release
path: |
${{ steps.sign_app.outputs.signedReleaseFile }}
- name: Upload build artifact2
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
if: ${{ env.SIGNING_KEY != '' }}
uses: actions/upload-artifact@v6
with:
name: APatch-Debug
path: |
${{ steps.sign_debug_app.outputs.signedReleaseFile }}
- name: Post to channel
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && github.ref_type != 'tag' }}
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
COMMIT_URL: ${{ github.event.head_commit.url }}
COMMIT_ID: ${{ github.event.head_commit.id }}
run: |
if [ -n "${BOT_TOKEN}" ]; then
CHANNELS=(-1002058433411 -1001910818234)
RELEASE_APK="${{ steps.sign_app.outputs.signedReleaseFile }}"
DEBUG_APK="${{ steps.sign_debug_app.outputs.signedReleaseFile }}"
for CHANNEL in "${CHANNELS[@]}"; do
URL=$(python3 .github/scripts/telegram_url.py $CHANNEL)
if [ -f "$RELEASE_APK" ] && [ -f "$DEBUG_APK" ]; then
curl -v "$URL" -F "Release1=@$RELEASE_APK" -F "Release2=@$DEBUG_APK"
fi
done
fi
- name: Release apk
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
if: ${{ env.SIGNING_KEY != '' && github.ref_type == 'tag' }}
continue-on-error: true
uses: ncipollo/release-action@v1
with:
token: ${{ github.token }}
tag: ${{ steps.parse_version.outputs.VERSION }}
artifacts: ${{steps.sign_app.outputs.signedReleaseFile}}
generateReleaseNotes: true
makeLatest: true
replacesArtifacts: true
================================================
FILE: .gitignore
================================================
*.iml
.gradle
local.properties
.idea
.DS_Store
build
captures
.cxx
key.jks
key.jks.base64.txt
.vscode
.kotlin
app/src/main/resources/
private
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: README.md
================================================
<div align="center">
<a href="https://github.com/bmax121/APatch/releases/latest"><img src="https://images.weserv.nl/?url=https://raw.githubusercontent.com/bmax121/APatch/main/app/src/main/ic_launcher-playstore.png&mask=circle" style="width: 128px;" alt="logo"></a>
<h1 align="center">APatch</h1>
[](https://github.com/bmax121/APatch/releases/latest)
[](https://nightly.link/bmax121/APatch/workflows/build/main/APatch)
[](https://hosted.weblate.org/engage/APatch)
[](https://t.me/APatchGroup)
[](/LICENSE)
</div>
The patching of Android kernel and Android system.
- A new kernel-based root solution for Android devices.
- APM: Support for modules similar to Magisk.
- KPM: Support for modules that allow you to inject any code into the kernel (Provides kernel function `inline-hook` and `syscall-table-hook`).
- APatch relies on [KernelPatch](https://github.com/bmax121/KernelPatch/).
- The APatch UI and the APModule source code have been derived and modified from [KernelSU](https://github.com/tiann/KernelSU).
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/me.bmax.apatch/)
Or download the latest APK from the [Releases Section](https://github.com/bmax121/APatch/releases/latest).
## Supported Versions
- Only supports the ARM64 architecture.
- Only supports Android kernel versions 3.18 - 6.12
Support for Samsung devices with security protection: Planned
## Requirement
Kernel configs:
- `CONFIG_KALLSYMS=y` and `CONFIG_KALLSYMS_ALL=y`
- `CONFIG_KALLSYMS=y` and `CONFIG_KALLSYMS_ALL=n`: Initial support
## Security Alert
The **SuperKey** has higher privileges than root access.
Weak or compromised keys can lead to unauthorized control of your device.
It is critical to use robust keys and safeguard them from exposure to maintain the security of your device.
## Translation
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.
<div align="center">
[](https://hosted.weblate.org/engage/APatch/)
</div>
## Get Help
### Usage
For usage, please refer to [our official documentation](https://apatch.dev).
It's worth noting that the documentation is currently not quite complete, and the content may change at any time.
Furthermore, we need more volunteers to [contribute to the documentation](https://github.com/AndroidPatch/APatchDocs) in other languages.
### Updates
- Telegram Channel: [@APatchUpdates](https://t.me/APatchChannel)
### Discussions
- Telegram Group: [@APatchDiscussions(EN/CN)](https://t.me/Apatch_discuss)
- Telegram Group: [中文](https://t.me/APatch_CN_Group)
### More Information
- [Documents](docs/)
## Credits
- [KernelPatch](https://github.com/bmax121/KernelPatch/): The core.
- [Magisk](https://github.com/topjohnwu/Magisk): magiskpolicy.
- [KernelSU](https://github.com/tiann/KernelSU): App UI, and Magisk module like support.
## License
APatch is licensed under the GNU General Public License v3 [GPL-3](http://www.gnu.org/copyleft/gpl.html).
================================================
FILE: apd/.gitignore
================================================
/target
.cargo/
================================================
FILE: apd/Cargo.toml
================================================
[package]
name = "apd"
version = "0.1.0"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
mlua = { version = "0.11.5", features = ["lua54","vendored"] }
anyhow = "1"
csv = "1.3.1"
clap = { version = "4", features = ["derive"] }
const_format = "0.2"
zip = { version = "7.2.0",features = [
"deflate",
"deflate64",
"time",
"lzma",
"xz",
], default-features = false }
zip-extensions = { git = "https://github.com/AndroidPatch/zip-extensions-rs.git", branch = "master", features = [
"deflate",
"lzma",
"xz",
], default-features = false }
java-properties = { git = "https://github.com/AndroidPatch/java-properties.git", branch = "master", default-features = false }
log = "0.4"
env_logger = "0.11"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
encoding_rs = "0.8"
walkdir="2.4"
retry = "2"
libc = "0.2"
extattr = "1"
jwalk = "0.8"
is_executable = "1"
nom = "8"
derive-new = "0.7.0"
which = "8"
getopts = "0.2"
errno = "0.3.14"
notify = "8.2"
signal-hook = "0.4"
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
rustix = { version = "1", features = ["all-apis"] }
# some android specific dependencies which compiles under unix are also listed here for convenience of coding
android-properties = { version = "0.2.2", features = ["bionic-deprecated"] }
procfs = "0.18"
loopdev = { git = "https://github.com/AndroidPatch/loopdev" }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = { version = "0.15", default-features = false }
[profile.release]
strip = true
overflow-checks = false
rpath = false
opt-level = 3
codegen-units = 1
panic = "abort"
lto = "fat"
================================================
FILE: apd/build.rs
================================================
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::process::Command;
fn get_git_version() -> Result<(u32, String), std::io::Error> {
let output = Command::new("git")
.args(["rev-list", "--count", "HEAD"])
.output()?;
let output = output.stdout;
let version_code = String::from_utf8(output).expect("Failed to read git count stdout");
let version_code: u32 = version_code
.trim()
.parse()
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to parse git count"))?;
let version_code = 10000 + 200 + version_code; // For historical reasons
let version_name = String::from_utf8(
Command::new("git")
.args(["describe", "--tags", "--always"])
.output()?
.stdout,
)
.map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to read git describe stdout",
)
})?;
let version_name = version_name.trim_start_matches('v').to_string();
Ok((version_code, version_name))
}
fn main() {
// update VersionCode when git repository change
println!("cargo:rerun-if-changed=../.git/HEAD");
println!("cargo:rerun-if-changed=../.git/refs/");
let (code, name) = match get_git_version() {
Ok((code, name)) => (code, name),
Err(_) => {
// show warning if git is not installed
println!("cargo:warning=Failed to get git version, using 0.0.0");
(0, "0.0.0".to_string())
}
};
let out_dir = env::var("OUT_DIR").expect("Failed to get $OUT_DIR");
println!("out_dir: ${out_dir}");
println!("code: ${code}");
let out_dir = Path::new(&out_dir);
File::create(Path::new(out_dir).join("VERSION_CODE"))
.expect("Failed to create VERSION_CODE")
.write_all(code.to_string().as_bytes())
.expect("Failed to write VERSION_CODE");
File::create(Path::new(out_dir).join("VERSION_NAME"))
.expect("Failed to create VERSION_NAME")
.write_all(name.trim().as_bytes())
.expect("Failed to write VERSION_NAME");
}
================================================
FILE: apd/src/apd.rs
================================================
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::{env, ffi::CStr, path::PathBuf, process::Command};
use anyhow::{Ok, Result};
#[cfg(unix)]
use getopts::Options;
use rustix::thread::{Gid, Uid, set_thread_res_gid, set_thread_res_uid};
#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::pty::prepare_pty;
use crate::{
defs,
utils::{self, umask},
};
fn print_usage(opts: Options) {
let brief = "APatch\n\nUsage: <command> [options] [-] [user [argument...]]".to_string();
print!("{}", opts.usage(&brief));
}
fn set_identity(uid: u32, gid: u32) {
#[cfg(any(target_os = "linux", target_os = "android"))]
let gid = Gid::from_raw(gid);
let uid = Uid::from_raw(uid);
set_thread_res_gid(gid, gid, gid).ok();
set_thread_res_uid(uid, uid, uid).ok();
}
#[cfg(not(unix))]
pub fn root_shell() -> Result<()> {
unimplemented!()
}
#[cfg(unix)]
pub fn root_shell() -> Result<()> {
// we are root now, this was set in kernel!
let env_args: Vec<String> = env::args().collect();
let args = env_args
.iter()
.position(|arg| arg == "-c")
.map(|i| {
let rest = env_args[i + 1..].to_vec();
let mut new_args = env_args[..i].to_vec();
new_args.push("-c".to_string());
if !rest.is_empty() {
new_args.push(rest.join(" "));
}
new_args
})
.unwrap_or_else(|| env_args.clone());
let mut opts = Options::new();
opts.optopt(
"c",
"command",
"pass COMMAND to the invoked shell",
"COMMAND",
);
opts.optflag("h", "help", "display this help message and exit");
opts.optflag("l", "login", "pretend the shell to be a login shell");
opts.optflag(
"p",
"preserve-environment",
"preserve the entire environment",
);
opts.optopt(
"s",
"shell",
"use SHELL instead of the default /system/bin/sh",
"SHELL",
);
opts.optflag("v", "version", "display version number and exit");
opts.optflag("V", "", "display version code and exit");
opts.optflag(
"M",
"mount-master",
"force run in the global mount namespace",
);
opts.optflag("", "no-pty", "Do not allocate a new pseudo terminal.");
// Replace -cn with -z, -mm with -M for supporting getopt_long
let args = args
.into_iter()
.map(|e| {
if e == "-mm" {
"-M".to_string()
} else if e == "-cn" {
"-z".to_string()
} else {
e
}
})
.collect::<Vec<String>>();
let matches = match opts.parse(&args[1..]) {
Result::Ok(m) => m,
Err(f) => {
println!("{f}");
print_usage(opts);
std::process::exit(-1);
}
};
if matches.opt_present("h") {
print_usage(opts);
return Ok(());
}
if matches.opt_present("v") {
println!("{}:APatch", defs::VERSION_NAME);
return Ok(());
}
if matches.opt_present("V") {
println!("{}", defs::VERSION_CODE);
return Ok(());
}
let shell = matches.opt_str("s").unwrap_or("/system/bin/sh".to_string());
let mut is_login = matches.opt_present("l");
let preserve_env = matches.opt_present("p");
let mount_master = matches.opt_present("M");
// we've made sure that -c is the last option and it already contains the whole command, no need to construct it again
let args = matches
.opt_str("c")
.map(|cmd| vec!["-c".to_string(), cmd])
.unwrap_or_default();
let mut free_idx = 0;
if !matches.free.is_empty() && matches.free[free_idx] == "-" {
is_login = true;
free_idx += 1;
}
// use current uid if no user specified, these has been done in kernel!
let mut uid = unsafe { libc::getuid() };
let gid = unsafe { libc::getgid() };
if free_idx < matches.free.len() {
let name = &matches.free[free_idx];
uid = unsafe {
#[cfg(target_arch = "aarch64")]
let pw = libc::getpwnam(name.as_ptr()).as_ref();
#[cfg(target_arch = "x86_64")]
let pw = libc::getpwnam(name.as_ptr() as *const i8).as_ref();
match pw {
Some(pw) => pw.pw_uid,
None => name.parse::<u32>().unwrap_or(0),
}
}
}
// https://github.com/topjohnwu/Magisk/blob/master/native/src/core/su/su_daemon.cpp#L408
let arg0 = if is_login { "-" } else { &shell };
let mut command = &mut Command::new(&shell);
if !preserve_env {
// This is actually incorrect, I don't know why.
// command = command.env_clear();
let pw = unsafe { libc::getpwuid(uid).as_ref() };
if let Some(pw) = pw {
let home = unsafe { CStr::from_ptr(pw.pw_dir) };
let pw_name = unsafe { CStr::from_ptr(pw.pw_name) };
let home = home.to_string_lossy();
let pw_name = pw_name.to_string_lossy();
command = command
.env("HOME", home.as_ref() as &str)
.env("USER", pw_name.as_ref() as &str)
.env("LOGNAME", pw_name.as_ref() as &str)
.env("SHELL", &shell);
}
}
// add /data/adb/ap/bin to PATH
#[cfg(any(target_os = "linux", target_os = "android"))]
add_path_to_env(defs::BINARY_DIR)?;
// when AP_RC_PATH exists and ENV is not set, set ENV to AP_RC_PATH
if PathBuf::from(defs::AP_RC_PATH).exists() && env::var("ENV").is_err() {
command = command.env("ENV", defs::AP_RC_PATH);
}
#[cfg(target_os = "android")]
if !matches.opt_present("no-pty") {
if let Err(e) = prepare_pty() {
log::error!("failed to prepare pty: {:?}", e);
}
}
// escape from the current cgroup and become session leader
// WARNING!!! This cause some root shell hang forever!
// command = command.process_group(0);
command = unsafe {
command.pre_exec(move || {
umask(0o22);
utils::switch_cgroups();
// switch to global mount namespace
#[cfg(any(target_os = "linux", target_os = "android"))]
let global_namespace_enable =
std::fs::read_to_string(defs::GLOBAL_NAMESPACE_FILE).unwrap_or("0".to_string());
if global_namespace_enable.trim() == "1" || mount_master {
let _ = utils::switch_mnt_ns(1);
}
set_identity(uid, gid);
Result::Ok(())
})
};
command = command.args(args).arg0(arg0);
Err(command.exec().into())
}
fn add_path_to_env(path: &str) -> Result<()> {
let mut paths =
env::var_os("PATH").map_or(Vec::new(), |val| env::split_paths(&val).collect::<Vec<_>>());
let new_path = PathBuf::from(path.trim_end_matches('/'));
paths.push(new_path);
let new_path_env = env::join_paths(paths)?;
unsafe { env::set_var("PATH", new_path_env) };
Ok(())
}
================================================
FILE: apd/src/assets.rs
================================================
use anyhow::Result;
use const_format::concatcp;
use crate::{defs::BINARY_DIR, utils};
pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop");
pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox");
pub const MAGISKPOLICY_PATH: &str = concatcp!(BINARY_DIR, "magiskpolicy");
pub fn ensure_binaries() -> Result<()> {
utils::ensure_binary(RESETPROP_PATH)?;
utils::ensure_binary(BUSYBOX_PATH)?;
utils::ensure_binary(MAGISKPOLICY_PATH)?;
Ok(())
}
================================================
FILE: apd/src/banner
================================================
_ ____ _ _
/ \ | _ \ __ _| |_ ___| |__
/ _ \ | |_) / _` | __/ __| '_ \
/ ___ \| __/ (_| | || (__| | | |
/_/ \_\_| \__,_|\__\___|_| |_|
================================================
FILE: apd/src/cli.rs
================================================
use crate::{defs, event, lua, module, supercall, utils};
#[cfg(target_os = "android")]
use android_logger::Config;
use anyhow::Result;
use clap::Parser;
#[cfg(target_os = "android")]
use log::LevelFilter;
/// APatch cli
#[derive(Parser, Debug)]
#[command(author, version = defs::VERSION_CODE, about, long_about = None)]
struct Args {
#[arg(
short,
long,
value_name = "KEY",
help = "Super key for authentication root"
)]
superkey: Option<String>,
#[command(subcommand)]
command: Commands,
}
#[derive(clap::Subcommand, Debug)]
enum Commands {
/// Manage APatch modules
Module {
#[command(subcommand)]
command: Module,
},
/// Trigger `post-fs-data` event
PostFsData,
/// Trigger `service` event
Services,
/// Trigger `boot-complete` event
BootCompleted,
/// Start uid listener for synchronizing root list
UidListener,
/// SELinux policy Patch tool
Sepolicy {
#[command(subcommand)]
command: Sepolicy,
},
}
#[derive(clap::Subcommand, Debug)]
enum Module {
/// Install module <ZIP>
Install {
/// module zip file path
zip: String,
},
/// Uninstall module <id>
Uninstall {
/// module id
id: String,
},
/// enable module <id>
Enable {
/// module id
id: String,
},
/// disable module <id>
Disable {
// module id
id: String,
},
/// run action for module <id>
Action {
// module id
id: String,
},
/// module lua runner
Lua {
// module id
id: String,
// lua function
function: String,
},
/// list all modules
List,
}
#[derive(clap::Subcommand, Debug)]
enum Sepolicy {
/// Check if sepolicy statement is supported/valid
Check {
/// sepolicy statements
sepolicy: String,
},
}
pub fn run() -> Result<()> {
#[cfg(target_os = "android")]
android_logger::init_once(
Config::default()
.with_max_level(LevelFilter::Trace) // limit log level
.with_tag("APatchD")
.with_filter(
android_logger::FilterBuilder::new()
.filter_level(LevelFilter::Trace)
.filter_module("notify", LevelFilter::Warn)
.build(),
),
);
#[cfg(not(target_os = "android"))]
env_logger::init();
// the kernel executes su with argv[0] = "/system/bin/kp" or "/system/bin/su" or "su" or "kp" and replace it with us
let arg0 = std::env::args().next().unwrap_or_default();
if arg0.ends_with("kp") || arg0.ends_with("su") {
return crate::apd::root_shell();
}
let cli = Args::parse();
log::info!("command: {:?}", cli.command);
if let Some(ref _superkey) = cli.superkey {
supercall::privilege_apd_profile(&cli.superkey);
}
let result = match cli.command {
Commands::PostFsData => event::on_post_data_fs(cli.superkey),
Commands::BootCompleted => event::on_boot_completed(cli.superkey),
Commands::UidListener => event::start_uid_listener(),
Commands::Module { command } => {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
utils::switch_mnt_ns(1)?;
}
match command {
Module::Install { zip } => module::install_module(&zip),
Module::Uninstall { id } => module::uninstall_module(&id),
Module::Action { id } => module::run_action(&id),
Module::Lua { id, function } => {
lua::run_lua(&id, &function, false, true).map_err(|e| anyhow::anyhow!("{}", e))
}
Module::Enable { id } => module::enable_module(&id),
Module::Disable { id } => module::disable_module(&id),
Module::List => module::list_modules(),
}
}
Commands::Sepolicy { command } => match command {
Sepolicy::Check { sepolicy } => crate::sepolicy::check_rule(&sepolicy),
},
Commands::Services => event::on_services(cli.superkey),
};
if let Err(e) = &result {
log::error!("Error: {:?}", e);
}
result
}
================================================
FILE: apd/src/defs.rs
================================================
use const_format::concatcp;
pub const ADB_DIR: &str = "/data/adb/";
pub const WORKING_DIR: &str = concatcp!(ADB_DIR, "ap/");
pub const BINARY_DIR: &str = concatcp!(WORKING_DIR, "bin/");
pub const APATCH_LOG_FOLDER: &str = concatcp!(WORKING_DIR, "log/");
pub const AP_RC_PATH: &str = concatcp!(WORKING_DIR, ".aprc");
pub const GLOBAL_NAMESPACE_FILE: &str = concatcp!(ADB_DIR, ".global_namespace_enable");
pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "apd");
pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/");
// warning: this directory should not change, or you need to change the code in module_installer.sh!!!
pub const MODULE_UPDATE_DIR: &str = concatcp!(ADB_DIR, "modules_update/");
pub const TEMP_DIR: &str = "/debug_ramdisk";
pub const TEMP_DIR_LEGACY: &str = "/sbin";
pub const MODULE_WEB_DIR: &str = "webroot";
pub const MODULE_ACTION_SH: &str = "action.sh";
pub const DISABLE_FILE_NAME: &str = "disable";
pub const UPDATE_FILE_NAME: &str = "update";
pub const REMOVE_FILE_NAME: &str = "remove";
// Metamodule support
pub const METAMODULE_MOUNT_SCRIPT: &str = "metamount.sh";
pub const METAMODULE_METAINSTALL_SCRIPT: &str = "metainstall.sh";
pub const METAMODULE_METAUNINSTALL_SCRIPT: &str = "metauninstall.sh";
pub const METAMODULE_DIR: &str = concatcp!(ADB_DIR, "metamodule/");
pub const PTS_NAME: &str = "pts";
pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE"));
pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME"));
================================================
FILE: apd/src/event.rs
================================================
use std::{
env,
ffi::CStr,
fs,
os::unix::{fs::PermissionsExt, process::CommandExt},
path::{Path, PathBuf},
process::Command,
sync::{Arc, Mutex},
thread,
time::Duration,
};
use anyhow::{Context, Result};
use libc::SIGPWR;
use log::{info, warn};
use notify::{
Config, Event, EventKind, INotifyWatcher, RecursiveMode, Watcher,
event::{ModifyKind, RenameMode},
};
use signal_hook::{consts::signal::*, iterator::Signals};
use crate::{
assets, defs, lua, metamodule, module, restorecon, supercall,
supercall::{
fork_for_result, init_load_package_uid_config, init_load_su_path, refresh_ap_package_list,
},
utils::{self, switch_cgroups},
};
pub fn on_post_data_fs(superkey: Option<String>) -> Result<()> {
utils::umask(0);
use std::process::Stdio;
#[cfg(unix)]
init_load_package_uid_config(&superkey);
init_load_su_path(&superkey);
let args = ["/data/adb/ap/bin/magiskpolicy", "--magisk", "--live"];
fork_for_result("/data/adb/ap/bin/magiskpolicy", &args, &superkey);
info!("Re-privilege apd profile after injecting sepolicy");
supercall::privilege_apd_profile(&superkey);
if utils::has_magisk() {
warn!("Magisk detected, skip post-fs-data!");
return Ok(());
}
// Create log environment
if !Path::new(defs::APATCH_LOG_FOLDER).exists() {
fs::create_dir(defs::APATCH_LOG_FOLDER).expect("Failed to create log folder");
let permissions = fs::Permissions::from_mode(0o700);
fs::set_permissions(defs::APATCH_LOG_FOLDER, permissions)
.expect("Failed to set permissions");
}
let command_string = format!(
"rm -rf {}*.old.log; for file in {}*; do mv \"$file\" \"$file.old.log\"; done",
defs::APATCH_LOG_FOLDER,
defs::APATCH_LOG_FOLDER
);
let mut args = vec!["-c", &command_string];
// for all file to .old
let result = utils::run_command("sh", &args, None)?.wait()?;
if result.success() {
info!("Successfully deleted .old files.");
} else {
info!("Failed to delete .old files.");
}
let logcat_path = format!("{}logcat.log", defs::APATCH_LOG_FOLDER);
let dmesg_path = format!("{}dmesg.log", defs::APATCH_LOG_FOLDER);
let bootlog = fs::File::create(dmesg_path)?;
args = vec![
"-s",
"9",
"120s",
"logcat",
"-b",
"main,system,crash",
"-f",
&logcat_path,
"logcatcher-bootlog:S",
"&",
];
let _ = unsafe {
Command::new("timeout")
.process_group(0)
.pre_exec(|| {
switch_cgroups();
Ok(())
})
.args(args)
.spawn()
};
args = vec!["-s", "9", "120s", "dmesg", "-w"];
let _result = unsafe {
Command::new("timeout")
.process_group(0)
.pre_exec(|| {
switch_cgroups();
Ok(())
})
.args(args)
.stdout(Stdio::from(bootlog))
.spawn()
};
let key = "KERNELPATCH_VERSION";
match env::var(key) {
Ok(value) => println!("{}: {}", key, value),
Err(_) => println!("{} not found", key),
}
let key = "KERNEL_VERSION";
match env::var(key) {
Ok(value) => println!("{}: {}", key, value),
Err(_) => println!("{} not found", key),
}
let safe_mode = utils::is_safe_mode(superkey.clone());
if safe_mode {
// we should still mount modules.img to `/data/adb/modules` in safe mode
// becuase we may need to operate the module dir in safe mode
warn!("safe mode, skip common post-fs-data.d scripts");
if let Err(e) = module::disable_all_modules() {
warn!("disable all modules failed: {}", e);
}
} else {
// Then exec common post-fs-data scripts
if let Err(e) = module::exec_common_scripts("post-fs-data.d", true) {
warn!("exec common post-fs-data scripts failed: {}", e);
}
}
let module_update_dir = defs::MODULE_UPDATE_DIR; //save module place
let module_dir = defs::MODULE_DIR; // run modules place
let module_update_flag = Path::new(defs::WORKING_DIR).join(defs::UPDATE_FILE_NAME); // if update ,there will be renewed modules file
assets::ensure_binaries().with_context(|| "binary missing")?;
if Path::new(defs::MODULE_UPDATE_DIR).exists() {
module::handle_updated_modules()?;
fs::remove_dir_all(module_update_dir)?;
}
if safe_mode {
warn!("safe mode, skip post-fs-data scripts and disable all modules!");
if let Err(e) = module::disable_all_modules() {
warn!("disable all modules failed: {}", e);
}
return Ok(());
}
if let Err(e) = module::prune_modules() {
warn!("prune modules failed: {}", e);
}
if let Err(e) = restorecon::restorecon() {
warn!("restorecon failed: {}", e);
}
// load sepolicy.rule
if module::load_sepolicy_rule().is_err() {
warn!("load sepolicy.rule failed");
}
if let Err(e) = metamodule::exec_mount_script(module_dir) {
warn!("execute metamodule mount failed: {e}");
}
// exec modules post-fs-data scripts
// TODO: Add timeout
if let Err(e) = module::exec_stage_script("post-fs-data", true) {
warn!("exec post-fs-data scripts failed: {}", e);
}
if let Err(e) = lua::exec_stage_lua("post-fs-data", true, superkey.as_deref().unwrap_or("")) {
warn!("Failed to exec post-fs-data lua: {}", e);
}
// load system.prop
if let Err(e) = module::load_system_prop() {
warn!("load system.prop failed: {}", e);
}
info!("remove update flag");
let _ = fs::remove_file(module_update_flag);
run_stage("post-mount", superkey, true);
env::set_current_dir("/").with_context(|| "failed to chdir to /")?;
Ok(())
}
fn run_stage(stage: &str, superkey: Option<String>, block: bool) {
utils::umask(0);
if utils::has_magisk() {
warn!("Magisk detected, skip {stage}");
return;
}
if utils::is_safe_mode(superkey.clone()) {
warn!("safe mode, skip {stage} scripts");
if let Err(e) = module::disable_all_modules() {
warn!("disable all modules failed: {}", e);
}
return;
}
// execute metamodule stage script first (priority)
if let Err(e) = metamodule::exec_stage_script(stage, block) {
warn!("Failed to exec metamodule {stage} script: {e}");
}
if let Err(e) = module::exec_common_scripts(&format!("{stage}.d"), block) {
warn!("Failed to exec common {stage} scripts: {e}");
}
if let Err(e) = module::exec_stage_script(stage, block) {
warn!("Failed to exec {stage} scripts: {e}");
}
if let Err(e) = lua::exec_stage_lua(stage, block, superkey.as_deref().unwrap_or("")) {
warn!("Failed to exec {stage} lua: {e}");
}
}
pub fn on_services(superkey: Option<String>) -> Result<()> {
info!("on_services triggered!");
run_stage("service", superkey, false);
Ok(())
}
fn run_uid_monitor() {
info!("Trigger run_uid_monitor!");
let mut command = &mut Command::new("/data/adb/apd");
{
command = command.process_group(0);
command = unsafe {
command.pre_exec(|| {
// ignore the error?
switch_cgroups();
Ok(())
})
};
}
command = command.arg("uid-listener");
command
.spawn()
.map(|_| ())
.expect("[run_uid_monitor] Failed to run uid monitor");
}
pub fn on_boot_completed(superkey: Option<String>) -> Result<()> {
info!("on_boot_completed triggered!");
run_stage("boot-completed", superkey, false);
run_uid_monitor();
Ok(())
}
pub fn start_uid_listener() -> Result<()> {
info!("start_uid_listener triggered!");
println!("[start_uid_listener] Registering...");
// create inotify instance
const SYS_PACKAGES_LIST_TMP: &str = "/data/system/packages.list.tmp";
let sys_packages_list_tmp = PathBuf::from(&SYS_PACKAGES_LIST_TMP);
let dir: PathBuf = sys_packages_list_tmp.parent().unwrap().into();
let (tx, rx) = std::sync::mpsc::channel();
let tx_clone = tx.clone();
let mutex = Arc::new(Mutex::new(()));
{
let mutex_clone = mutex.clone();
thread::spawn(move || {
let mut signals = Signals::new(&[SIGTERM, SIGINT, SIGPWR]).unwrap();
for sig in signals.forever() {
log::warn!("[shutdown] Caught signal {sig}, refreshing package list...");
let skey = CStr::from_bytes_with_nul(b"su\0")
.expect("[shutdown_listener] CStr::from_bytes_with_nul failed");
refresh_ap_package_list(&skey, &mutex_clone);
break; // 执行一次后退出线程
}
});
}
let mut watcher = INotifyWatcher::new(
move |ev: notify::Result<Event>| match ev {
Ok(Event {
kind: EventKind::Modify(ModifyKind::Name(RenameMode::Both)),
paths,
..
}) => {
if paths.contains(&sys_packages_list_tmp) {
info!("[uid_monitor] System packages list changed, sending to tx...");
tx_clone.send(false).unwrap()
}
}
Err(err) => warn!("inotify error: {err}"),
_ => (),
},
Config::default(),
)?;
watcher.watch(dir.as_ref(), RecursiveMode::NonRecursive)?;
let mut debounce = false;
while let Ok(delayed) = rx.recv() {
if delayed {
debounce = false;
let skey = CStr::from_bytes_with_nul(b"su\0")
.expect("[start_uid_listener] CStr::from_bytes_with_nul failed");
refresh_ap_package_list(&skey, &mutex);
} else if !debounce {
thread::sleep(Duration::from_secs(1));
debounce = true;
tx.send(true)?;
}
}
Ok(())
}
================================================
FILE: apd/src/installer.sh
================================================
#!/system/bin/sh
############################################
# APatch Module installer script
# mostly from module_installer.sh
# and util_functions.sh in Magisk
############################################
umask 022
ui_print() {
if $BOOTMODE; then
echo "$1"
else
echo -e "ui_print $1\nui_print" >> /proc/self/fd/$OUTFD
fi
}
toupper() {
echo "$@" | tr '[:lower:]' '[:upper:]'
}
grep_cmdline() {
local REGEX="s/^$1=//p"
{ echo $(cat /proc/cmdline)$(sed -e 's/[^"]//g' -e 's/""//g' /proc/cmdline) | xargs -n 1; \
sed -e 's/ = /=/g' -e 's/, /,/g' -e 's/"//g' /proc/bootconfig; \
} 2>/dev/null | sed -n "$REGEX"
}
grep_prop() {
local REGEX="s/^$1=//p"
shift
local FILES=$@
[ -z "$FILES" ] && FILES='/system/build.prop'
cat $FILES 2>/dev/null | dos2unix | sed -n "$REGEX" | head -n 1
}
grep_get_prop() {
local result=$(grep_prop $@)
if [ -z "$result" ]; then
# Fallback to getprop
getprop "$1"
else
echo $result
fi
}
is_mounted() {
grep -q " $(readlink -f $1) " /proc/mounts 2>/dev/null
return $?
}
abort() {
ui_print "$1"
$BOOTMODE || recovery_cleanup
[ ! -z $MODPATH ] && rm -rf $MODPATH
rm -rf $TMPDIR
exit 1
}
print_title() {
local len line1len line2len bar
line1len=$(echo -n $1 | wc -c)
line2len=$(echo -n $2 | wc -c)
len=$line2len
[ $line1len -gt $line2len ] && len=$line1len
len=$((len + 2))
bar=$(printf "%${len}s" | tr ' ' '*')
ui_print "$bar"
ui_print " $1 "
[ "$2" ] && ui_print " $2 "
ui_print "$bar"
}
######################
# Environment Related
######################
setup_flashable() {
ensure_bb
$BOOTMODE && return
if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then
# We will have to manually find out OUTFD
for FD in `ls /proc/$$/fd`; do
if readlink /proc/$$/fd/$FD | grep -q pipe; then
if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then
OUTFD=$FD
break
fi
fi
done
fi
recovery_actions
}
ensure_bb() {
:
}
recovery_actions() {
:
}
recovery_cleanup() {
:
}
#######################
# Installation Related
#######################
# find_block [partname...]
find_block() {
local BLOCK DEV DEVICE DEVNAME PARTNAME UEVENT
for BLOCK in "$@"; do
DEVICE=`find /dev/block \( -type b -o -type c -o -type l \) -iname $BLOCK | head -n 1` 2>/dev/null
if [ ! -z $DEVICE ]; then
readlink -f $DEVICE
return 0
fi
done
# Fallback by parsing sysfs uevents
for UEVENT in /sys/dev/block/*/uevent; do
DEVNAME=`grep_prop DEVNAME $UEVENT`
PARTNAME=`grep_prop PARTNAME $UEVENT`
for BLOCK in "$@"; do
if [ "$(toupper $BLOCK)" = "$(toupper $PARTNAME)" ]; then
echo /dev/block/$DEVNAME
return 0
fi
done
done
# Look just in /dev in case we're dealing with MTD/NAND without /dev/block devices/links
for DEV in "$@"; do
DEVICE=`find /dev \( -type b -o -type c -o -type l \) -maxdepth 1 -iname $DEV | head -n 1` 2>/dev/null
if [ ! -z $DEVICE ]; then
readlink -f $DEVICE
return 0
fi
done
return 1
}
# setup_mntpoint <mountpoint>
setup_mntpoint() {
local POINT=$1
[ -L $POINT ] && mv -f $POINT ${POINT}_link
if [ ! -d $POINT ]; then
rm -f $POINT
mkdir -p $POINT
fi
}
# mount_name <partname(s)> <mountpoint> <flag>
mount_name() {
local PART=$1
local POINT=$2
local FLAG=$3
setup_mntpoint $POINT
is_mounted $POINT && return
# First try mounting with fstab
mount $FLAG $POINT 2>/dev/null
if ! is_mounted $POINT; then
local BLOCK=$(find_block $PART)
mount $FLAG $BLOCK $POINT || return
fi
ui_print "- Mounting $POINT"
}
# mount_ro_ensure <partname(s)> <mountpoint>
mount_ro_ensure() {
# We handle ro partitions only in recovery
$BOOTMODE && return
local PART=$1
local POINT=$2
mount_name "$PART" $POINT '-o ro'
is_mounted $POINT || abort "! Cannot mount $POINT"
}
mount_partitions() {
# Check A/B slot
SLOT=`grep_cmdline androidboot.slot_suffix`
if [ -z $SLOT ]; then
SLOT=`grep_cmdline androidboot.slot`
[ -z $SLOT ] || SLOT=_${SLOT}
fi
[ -z $SLOT ] || ui_print "- Current boot slot: $SLOT"
# Mount ro partitions
if is_mounted /system_root; then
umount /system 2&>/dev/null
umount /system_root 2&>/dev/null
fi
mount_ro_ensure "system$SLOT app$SLOT" /system
if [ -f /system/init -o -L /system/init ]; then
SYSTEM_ROOT=true
setup_mntpoint /system_root
if ! mount --move /system /system_root; then
umount /system
umount -l /system 2>/dev/null
mount_ro_ensure "system$SLOT app$SLOT" /system_root
fi
mount -o bind /system_root/system /system
else
SYSTEM_ROOT=false
grep ' / ' /proc/mounts | grep -qv 'rootfs' || grep -q ' /system_root ' /proc/mounts && SYSTEM_ROOT=true
fi
# /vendor is used only on some older devices for recovery AVBv1 signing so is not critical if fails
[ -L /system/vendor ] && mount_name vendor$SLOT /vendor '-o ro'
$SYSTEM_ROOT && ui_print "- Device is system-as-root"
# Mount sepolicy rules dir locations in recovery (best effort)
if ! $BOOTMODE; then
mount_name "cache cac" /cache
mount_name metadata /metadata
mount_name persist /persist
fi
}
api_level_arch_detect() {
API=$(grep_get_prop ro.build.version.sdk)
ABI=$(grep_get_prop ro.product.cpu.abi)
if [ "$ABI" = "x86" ]; then
ARCH=x86
ABI32=x86
IS64BIT=false
elif [ "$ABI" = "arm64-v8a" ]; then
ARCH=arm64
ABI32=armeabi-v7a
IS64BIT=true
elif [ "$ABI" = "x86_64" ]; then
ARCH=x64
ABI32=x86
IS64BIT=true
else
ARCH=arm
ABI=armeabi-v7a
ABI32=armeabi-v7a
IS64BIT=false
fi
}
#################
# Module Related
#################
set_perm() {
chown $2:$3 $1 || return 1
chmod $4 $1 || return 1
local CON=$5
[ -z $CON ] && CON=u:object_r:system_file:s0
chcon $CON $1 || return 1
}
set_perm_recursive() {
find $1 -type d 2>/dev/null | while read dir; do
set_perm $dir $2 $3 $4 $6
done
find $1 -type f -o -type l 2>/dev/null | while read file; do
set_perm $file $2 $3 $5 $6
done
}
mktouch() {
mkdir -p ${1%/*} 2>/dev/null
[ -z $2 ] && touch $1 || echo $2 > $1
chmod 644 $1
}
mark_remove() {
mkdir -p ${1%/*} 2>/dev/null
mknod $1 c 0 0
chmod 644 $1
}
mark_replace() {
# REPLACE must be directory!!!
# https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
mkdir -p $1 2>/dev/null
setfattr -n trusted.overlay.opaque -v y $1
chmod 644 $1
}
request_size_check() {
reqSizeM=`du -ms "$1" | cut -f1`
}
request_zip_size_check() {
reqSizeM=`unzip -l "$1" | tail -n 1 | awk '{ print int(($1 - 1) / 1048576 + 1) }'`
}
boot_actions() { return; }
# Require ZIPFILE to be set
is_legacy_script() {
unzip -l "$ZIPFILE" install.sh | grep -q install.sh
return $?
}
handle_partition() {
# if /system/vendor is a symlink, we need to move it out of $MODPATH/system, otherwise it will be overlayed
# if /system/vendor is a normal directory, it is ok to overlay it and we don't need to overlay it separately.
if [ ! -e $MODPATH/system/$1 ]; then
# no partition found
return;
fi
if [ -L "/system/$1" ] && [ "$(readlink -f /system/$1)" = "/$1" ]; then
ui_print "- Handle partition /$1"
# we create a symlink if module want to access $MODPATH/system/$1
# but it doesn't always work(ie. write it in post-fs-data.sh would fail because it is readonly)
mv -f $MODPATH/system/$1 $MODPATH/$1 && ln -sf ../$1 $MODPATH/system/$1
fi
}
# Require OUTFD, ZIPFILE to be set
install_module() {
rm -rf $TMPDIR
mkdir -p $TMPDIR
chcon u:object_r:system_file:s0 $TMPDIR
cd $TMPDIR
mount_partitions
api_level_arch_detect
# Setup busybox and binaries
if $BOOTMODE; then
boot_actions
else
recovery_actions
fi
# Extract prop file
unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2
[ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!"
local MODDIRNAME=modules
$BOOTMODE && MODDIRNAME=modules_update
local MODULEROOT=$NVBASE/$MODDIRNAME
MODID=`grep_prop id $TMPDIR/module.prop`
MODNAME=`grep_prop name $TMPDIR/module.prop`
MODAUTH=`grep_prop author $TMPDIR/module.prop`
MODPATH=$MODULEROOT/$MODID
# Create mod paths
rm -rf $MODPATH
mkdir -p $MODPATH
if is_legacy_script; then
unzip -oj "$ZIPFILE" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2
# Load install script
. $TMPDIR/install.sh
# Callbacks
print_modname
on_install
[ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh
$SKIPMOUNT && touch $MODPATH/skip_mount
$PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop
cp -af $TMPDIR/module.prop $MODPATH/module.prop
$POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh
$LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh
ui_print "- Setting permissions"
set_permissions
else
print_title "$MODNAME" "by $MODAUTH"
print_title "Powered by APatch"
unzip -o "$ZIPFILE" customize.sh -d $MODPATH >&2
if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then
ui_print "- Extracting module files"
unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2
# Default permissions
set_perm_recursive $MODPATH 0 0 0755 0644
set_perm_recursive $MODPATH/system/bin 0 2000 0755 0755
set_perm_recursive $MODPATH/system/xbin 0 2000 0755 0755
set_perm_recursive $MODPATH/system/system_ext/bin 0 2000 0755 0755
set_perm_recursive $MODPATH/system/vendor 0 2000 0755 0755 u:object_r:vendor_file:s0
fi
# Load customization script
[ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh
fi
# Handle replace folders
for TARGET in $REPLACE; do
ui_print "- Replace target: $TARGET"
mark_replace $MODPATH$TARGET
done
# Handle remove files
for TARGET in $REMOVE; do
ui_print "- Remove target: $TARGET"
mark_remove $MODPATH$TARGET
done
handle_partition vendor
handle_partition system_ext
handle_partition product
if $BOOTMODE; then
mktouch $NVBASE/modules/$MODID/update
rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null
rm -rf $NVBASE/modules/$MODID/disable 2>/dev/null
cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop
fi
# Remove stuff that doesn't belong to modules and clean up any empty directories
rm -rf \
$MODPATH/system/placeholder $MODPATH/customize.sh \
$MODPATH/README.md $MODPATH/.git*
rmdir -p $MODPATH 2>/dev/null
cd /
$BOOTMODE || recovery_cleanup
rm -rf $TMPDIR
ui_print "- Done"
}
##########
# Presets
##########
# Detect whether in boot mode
[ -z $BOOTMODE ] && ps | grep zygote | grep -qv grep && BOOTMODE=true
[ -z $BOOTMODE ] && ps -A 2>/dev/null | grep zygote | grep -qv grep && BOOTMODE=true
[ -z $BOOTMODE ] && BOOTMODE=false
NVBASE=/data/adb
TMPDIR=/dev/tmp
POSTFSDATAD=$NVBASE/post-fs-data.d
SERVICED=$NVBASE/service.d
# Some modules dependents on this
export MAGISK_VER=27.0
export MAGISK_VER_CODE=27000
================================================
FILE: apd/src/installer_bind.sh
================================================
#!/system/bin/sh
############################################
# APatch Module installer script
# mostly from module_installer.sh
# and util_functions.sh in Magisk
############################################
umask 022
ui_print() {
if $BOOTMODE; then
echo "$1"
else
echo -e "ui_print $1\nui_print" >> /proc/self/fd/$OUTFD
fi
}
toupper() {
echo "$@" | tr '[:lower:]' '[:upper:]'
}
grep_cmdline() {
local REGEX="s/^$1=//p"
{ echo $(cat /proc/cmdline)$(sed -e 's/[^"]//g' -e 's/""//g' /proc/cmdline) | xargs -n 1; \
sed -e 's/ = /=/g' -e 's/, /,/g' -e 's/"//g' /proc/bootconfig; \
} 2>/dev/null | sed -n "$REGEX"
}
grep_prop() {
local REGEX="s/$1=//p"
shift
local FILES=$@
[ -z "$FILES" ] && FILES='/system/build.prop'
cat $FILES 2>/dev/null | dos2unix | sed -n "$REGEX" | head -n 1 | xargs
}
grep_get_prop() {
local result=$(grep_prop $@)
if [ -z "$result" ]; then
# Fallback to getprop
getprop "$1"
else
echo $result
fi
}
is_mounted() {
grep -q " $(readlink -f $1) " /proc/mounts 2>/dev/null
return $?
}
abort() {
ui_print "$1"
$BOOTMODE || recovery_cleanup
[ ! -z $MODPATH ] && rm -rf $MODPATH
rm -rf $TMPDIR
exit 1
}
print_title() {
local len line1len line2len bar
line1len=$(echo -n $1 | wc -c)
line2len=$(echo -n $2 | wc -c)
len=$line2len
[ $line1len -gt $line2len ] && len=$line1len
len=$((len + 2))
bar=$(printf "%${len}s" | tr ' ' '*')
ui_print "$bar"
ui_print " $1 "
[ "$2" ] && ui_print " $2 "
ui_print "$bar"
}
check_sepolicy() {
/data/adb/apd sepolicy check "$1"
return $?
}
######################
# Environment Related
######################
setup_flashable() {
ensure_bb
$BOOTMODE && return
if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then
# We will have to manually find out OUTFD
for FD in /proc/$$/fd/*; do
if readlink /proc/$$/fd/$FD | grep -q pipe; then
if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then
OUTFD=$FD
break
fi
fi
done
fi
recovery_actions
}
ensure_bb() {
:
}
recovery_actions() {
:
}
recovery_cleanup() {
:
}
#######################
# Installation Related
#######################
# find_block [partname...]
find_block() {
local BLOCK DEV DEVICE DEVNAME PARTNAME UEVENT
for BLOCK in "$@"; do
DEVICE=`find /dev/block \( -type b -o -type c -o -type l \) -iname $BLOCK | head -n 1` 2>/dev/null
if [ ! -z $DEVICE ]; then
readlink -f $DEVICE
return 0
fi
done
# Fallback by parsing sysfs uevents
for UEVENT in /sys/dev/block/*/uevent; do
DEVNAME=`grep_prop DEVNAME $UEVENT`
PARTNAME=`grep_prop PARTNAME $UEVENT`
for BLOCK in "$@"; do
if [ "$(toupper $BLOCK)" = "$(toupper $PARTNAME)" ]; then
echo /dev/block/$DEVNAME
return 0
fi
done
done
# Look just in /dev in case we're dealing with MTD/NAND without /dev/block devices/links
for DEV in "$@"; do
DEVICE=`find /dev \( -type b -o -type c -o -type l \) -maxdepth 1 -iname $DEV | head -n 1` 2>/dev/null
if [ ! -z $DEVICE ]; then
readlink -f $DEVICE
return 0
fi
done
return 1
}
# setup_mntpoint <mountpoint>
setup_mntpoint() {
local POINT=$1
[ -L $POINT ] && mv -f $POINT ${POINT}_link
if [ ! -d $POINT ]; then
rm -f $POINT
mkdir -p $POINT
fi
}
# mount_name <partname(s)> <mountpoint> <flag>
mount_name() {
local PART=$1
local POINT=$2
local FLAG=$3
setup_mntpoint $POINT
is_mounted $POINT && return
# First try mounting with fstab
mount $FLAG $POINT 2>/dev/null
if ! is_mounted $POINT; then
local BLOCK=$(find_block $PART)
mount $FLAG $BLOCK $POINT || return
fi
ui_print "- Mounting $POINT"
}
# mount_ro_ensure <partname(s)> <mountpoint>
mount_ro_ensure() {
# We handle ro partitions only in recovery
$BOOTMODE && return
local PART=$1
local POINT=$2
mount_name "$PART" $POINT '-o ro'
is_mounted $POINT || abort "! Cannot mount $POINT"
}
mount_partitions() {
# Check A/B slot
SLOT=`grep_cmdline androidboot.slot_suffix`
if [ -z $SLOT ]; then
SLOT=`grep_cmdline androidboot.slot`
[ -z $SLOT ] || SLOT=_${SLOT}
fi
[ -z $SLOT ] || ui_print "- Current boot slot: $SLOT"
# Mount ro partitions
if is_mounted /system_root; then
umount /system 2&>/dev/null
umount /system_root 2&>/dev/null
fi
mount_ro_ensure "system$SLOT app$SLOT" /system
if [ -f /system/init -o -L /system/init ]; then
SYSTEM_ROOT=true
setup_mntpoint /system_root
if ! mount --move /system /system_root; then
umount /system
umount -l /system 2>/dev/null
mount_ro_ensure "system$SLOT app$SLOT" /system_root
fi
mount -o bind /system_root/system /system
else
SYSTEM_ROOT=false
grep ' / ' /proc/mounts | grep -qv 'rootfs' || grep -q ' /system_root ' /proc/mounts && SYSTEM_ROOT=true
fi
# /vendor is used only on some older devices for recovery AVBv1 signing so is not critical if fails
[ -L /system/vendor ] && mount_name vendor$SLOT /vendor '-o ro'
$SYSTEM_ROOT && ui_print "- Device is system-as-root"
# Mount sepolicy rules dir locations in recovery (best effort)
if ! $BOOTMODE; then
mount_name "cache cac" /cache
mount_name metadata /metadata
mount_name persist /persist
fi
}
api_level_arch_detect() {
API=$(grep_get_prop ro.build.version.sdk)
ABI=$(grep_get_prop ro.product.cpu.abi)
if [ "$ABI" = "x86" ]; then
ARCH=x86
ABI32=x86
IS64BIT=false
elif [ "$ABI" = "arm64-v8a" ]; then
ARCH=arm64
ABI32=armeabi-v7a
IS64BIT=true
elif [ "$ABI" = "x86_64" ]; then
ARCH=x64
ABI32=x86
IS64BIT=true
else
ARCH=arm
ABI=armeabi-v7a
ABI32=armeabi-v7a
IS64BIT=false
fi
}
#################
# Module Related
#################
set_perm() {
chown $2:$3 $1 || return 1
chmod $4 $1 || return 1
local CON=$5
[ -z $CON ] && CON=u:object_r:system_file:s0
chcon $CON $1 || return 1
}
set_perm_recursive() {
find $1 -type d 2>/dev/null | while read dir; do
set_perm $dir $2 $3 $4 $6
done
find $1 -type f -o -type l 2>/dev/null | while read file; do
set_perm $file $2 $3 $5 $6
done
}
mktouch() {
mkdir -p ${1%/*} 2>/dev/null
[ -z $2 ] && touch $1 || echo $2 > $1
chmod 644 $1
}
mark_remove() {
mkdir -p ${1%/*} 2>/dev/null
mknod $1 c 0 0
chmod 644 $1
}
mark_replace() {
# REPLACE must be directory!!!
# https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
mkdir -p $1 2>/dev/null
setfattr -n trusted.overlay.opaque -v y $1
chmod 644 $1
}
request_size_check() {
reqSizeM=`du -ms "$1" | cut -f1`
}
request_zip_size_check() {
reqSizeM=`unzip -l "$1" | tail -n 1 | awk '{ print int(($1 - 1) / 1048576 + 1) }'`
}
boot_actions() { return; }
# Require ZIPFILE to be set
is_legacy_script() {
unzip -l "$ZIPFILE" install.sh | grep -q install.sh
return $?
}
handle_partition() {
PARTITION="$1"
REQUIRE_SYMLINK="$2"
if [ ! -e "$MODPATH/system/$PARTITION" ]; then
# no partition found
return;
fi
if [ "$REQUIRE_SYMLINK" = "false" ] || [ -L "/system/$PARTITION" ] && [ "$(readlink -f "/system/$PARTITION")" = "/$PARTITION" ]; then
ui_print "- Handle partition /$PARTITION"
ln -sf "./system/$PARTITION" "$MODPATH/$PARTITION"
fi
}
# Require OUTFD, ZIPFILE to be set
install_module() {
rm -rf $TMPDIR
mkdir -p $TMPDIR
chcon u:object_r:system_file:s0 $TMPDIR
cd $TMPDIR
mount_partitions
api_level_arch_detect
# Setup busybox and binaries
if $BOOTMODE; then
boot_actions
else
recovery_actions
fi
# Extract prop file
unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2
[ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!"
local MODDIRNAME=modules
$BOOTMODE && MODDIRNAME=modules_update
local MODULEROOT=$NVBASE/$MODDIRNAME
MODID=`grep_prop id $TMPDIR/module.prop`
MODNAME=`grep_prop name $TMPDIR/module.prop`
MODAUTH=`grep_prop author $TMPDIR/module.prop`
MODPATH=$MODULEROOT/$MODID
# Create mod paths
rm -rf $MODPATH
mkdir -p $MODPATH
if is_legacy_script; then
unzip -oj "$ZIPFILE" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2
# Load install script
. $TMPDIR/install.sh
# Callbacks
print_modname
on_install
[ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh
$SKIPMOUNT && touch $MODPATH/skip_mount
$PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop
cp -af $TMPDIR/module.prop $MODPATH/module.prop
$POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh
$LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh
ui_print "- Setting permissions"
set_permissions
else
print_title "$MODNAME" "by $MODAUTH"
print_title "Powered by APatch"
unzip -o "$ZIPFILE" customize.sh -d $MODPATH >&2
if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then
ui_print "- Extracting module files"
unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2
# Default permissions
set_perm_recursive $MODPATH 0 0 0755 0644
set_perm_recursive $MODPATH/system/bin 0 2000 0755 0755
set_perm_recursive $MODPATH/system/xbin 0 2000 0755 0755
set_perm_recursive $MODPATH/system/system_ext/bin 0 2000 0755 0755
set_perm_recursive $MODPATH/system/vendor 0 2000 0755 0755 u:object_r:vendor_file:s0
fi
# Load customization script
[ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh
fi
handle_partition vendor true
handle_partition system_ext true
handle_partition product true
handle_partition odm false
# Handle replace folders
for TARGET in $REPLACE; do
ui_print "- Replace target: $TARGET"
mark_replace "$MODPATH$TARGET"
done
# Handle remove files
for TARGET in $REMOVE; do
ui_print "- Remove target: $TARGET"
mark_remove "$MODPATH$TARGET"
done
if $BOOTMODE; then
mktouch $NVBASE/modules/$MODID/update
rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null
rm -rf $NVBASE/modules/$MODID/disable 2>/dev/null
cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop
fi
# Remove stuff that doesn't belong to modules and clean up any empty directories
rm -rf \
$MODPATH/system/placeholder $MODPATH/customize.sh \
$MODPATH/README.md $MODPATH/.git*
rmdir -p $MODPATH 2>/dev/null
cd /
$BOOTMODE || recovery_cleanup
rm -rf $TMPDIR
ui_print "- Done"
}
##########
# Presets
##########
# Detect whether in boot mode
[ -z $BOOTMODE ] && ps | grep zygote | grep -qv grep && BOOTMODE=true
[ -z $BOOTMODE ] && ps -A 2>/dev/null | grep zygote | grep -qv grep && BOOTMODE=true
[ -z $BOOTMODE ] && BOOTMODE=false
NVBASE=/data/adb
TMPDIR=/dev/tmp
POSTFSDATAD=$NVBASE/post-fs-data.d
SERVICED=$NVBASE/service.d
# Some modules dependents on this
export MAGISK_VER=27.0
export MAGISK_VER_CODE=27000
================================================
FILE: apd/src/lua.rs
================================================
use crate::module::*;
use crate::utils::*;
use anyhow::Result;
use log::{info, warn};
use mlua::{Function, Lua, Result as LuaResult, Table};
use std::{fs, path::Path};
pub fn save_text<P: AsRef<Path>>(filename: P, content: &str) -> std::io::Result<()> {
let _ = ensure_dir_exists("/data/adb/config");
let path = format!("/data/adb/config/{}", filename.as_ref().display());
fs::write(&path, content)?;
Ok(())
}
pub fn load_text<P: AsRef<Path>>(filename: P) -> std::io::Result<String> {
let _ = ensure_dir_exists("/data/adb/config");
let path = format!("/data/adb/config/{}", filename.as_ref().display());
fs::read_to_string(path)
}
pub fn load_all_lua_modules(lua: &Lua) -> LuaResult<()> {
let modules_dir = Path::new("/data/adb/modules");
let modules: Table = match lua.globals().get("modules") {
Ok(t) => t,
Err(_) => {
let t = lua.create_table()?;
lua.globals().set("modules", t.clone())?;
t
}
};
if modules_dir.exists() {
for entry in
fs::read_dir(modules_dir).unwrap_or_else(|_| fs::read_dir("/dev/null").unwrap())
{
if let Ok(entry) = entry {
let path = entry.path();
if path.is_dir() {
let id = path.file_name().unwrap().to_string_lossy().to_string();
let package: Table = lua.globals().get("package")?;
let old_cpath: String = package.get("cpath")?;
let new_cpath = format!("{}/?.so;{}", path.to_string_lossy(), old_cpath);
package.set("cpath", new_cpath)?;
let lua_file = path.join(format!("{}.lua", id));
if lua_file.exists() {
match fs::read_to_string(&lua_file) {
Ok(code) => {
match lua
.load(&code)
.set_name(&*lua_file.to_string_lossy())
.eval::<Table>()
{
Ok(module) => {
modules.set(id.clone(), module.clone())?;
}
Err(e) => {
eprintln!(
"Failed to eval Lua {}: {}",
lua_file.display(),
e
);
}
}
}
Err(e) => {
eprintln!("Failed to read Lua {}: {}", lua_file.display(), e);
}
}
}
}
}
}
}
Ok(())
}
pub fn info_lua(lua: &Lua) -> LuaResult<Function> {
lua.create_function(|_, msg: String| {
info!("[Lua] {}", msg);
Ok(())
})
}
pub fn warn_lua(lua: &Lua) -> LuaResult<Function> {
lua.create_function(|_, msg: String| {
warn!("[Lua] {}", msg);
Ok(())
})
}
pub fn install_module_lua(lua: &Lua) -> LuaResult<Function> {
lua.create_function(|_, zip: String| {
install_module(&zip)
.map_err(|e| mlua::Error::external(format!("install_module failed: {}", e)))
})
}
pub fn save_text_lua(lua: &Lua) -> LuaResult<Function> {
lua.create_function(|_, (filename, content): (String, String)| {
save_text(&filename, &content)
.map_err(|e| mlua::Error::external(format!("save filed: {}", e)))?;
Ok(())
})
}
pub fn read_text_lua(lua: &Lua) -> LuaResult<Function> {
lua.create_function(|_, filename: String| {
let content = match load_text(&filename) {
Ok(s) => s,
Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => String::new(),
Err(e) => return Err(mlua::Error::external(format!("read failed: {}", e))),
};
Ok(content)
})
}
pub fn exec_stage_lua(stage: &str, wait: bool, superkey: &str) -> Result<()> {
let stage_safe = stage.replace('-', "_");
run_lua(&superkey, &stage_safe, true, wait).map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(())
}
pub fn run_lua(id: &str, function: &str, on_each_module: bool, _wait: bool) -> mlua::Result<()> {
let lua = unsafe { Lua::unsafe_new() };
let func = install_module_lua(&lua)?;
lua.globals().set("install_module", func)?;
lua.globals().set("info", info_lua(&lua)?)?;
lua.globals().set("warn", warn_lua(&lua)?)?;
lua.globals().set("setConfig", save_text_lua(&lua)?)?;
lua.globals().set("getConfig", read_text_lua(&lua)?)?;
load_all_lua_modules(&lua)?;
let modules: mlua::Table = lua.globals().get("modules")?;
if on_each_module {
for pair in modules.pairs::<String, mlua::Table>() {
let (_, module_table) = pair?;
if let Ok(func_obj) = module_table.get::<mlua::Function>(function) {
func_obj.call::<()>(id)?;
}
}
} else {
let module_table: mlua::Table = modules.get(id)?;
let func_obj: mlua::Function = module_table.get(function)?;
func_obj.call::<()>(())?;
}
Ok(())
}
================================================
FILE: apd/src/main.rs
================================================
mod apd;
mod assets;
mod cli;
mod defs;
mod event;
mod lua;
mod metamodule;
mod module;
mod package;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod pty;
mod restorecon;
mod sepolicy;
mod supercall;
mod utils;
fn main() -> anyhow::Result<()> {
cli::run()
}
================================================
FILE: apd/src/metamodule.rs
================================================
//! Metamodule management
//!
//! This module handles all metamodule-related functionality.
//! Metamodules are special modules that manage how regular modules are mounted
//! and provide hooks for module installation/uninstallation.
use std::{
collections::HashMap,
path::{Path, PathBuf},
process::Command,
};
use anyhow::{Context, Result, ensure};
use log::{info, warn};
use crate::{assets, defs, module::ModuleType::All};
/// Determine whether the provided module properties mark it as a metamodule
pub fn is_metamodule(props: &HashMap<String, String>) -> bool {
props.get("metamodule").is_some_and(|s| {
let trimmed = s.trim();
trimmed == "1" || trimmed.eq_ignore_ascii_case("true")
})
}
/// Get metamodule path if it exists
/// The metamodule is stored in /data/adb/modules/{id} with a symlink at /data/adb/metamodule
pub fn get_metamodule_path() -> Option<PathBuf> {
let path = Path::new(defs::METAMODULE_DIR);
// Check if symlink exists and resolve it
if path.is_symlink()
&& let Ok(target) = std::fs::read_link(path)
{
// If target is relative, resolve it
let resolved = if target.is_absolute() {
target
} else {
path.parent()?.join(target)
};
if resolved.exists() && resolved.is_dir() {
return Some(resolved);
}
warn!(
"Metamodule symlink points to non-existent path: {}",
resolved.display()
);
}
// Fallback: search for metamodule=1 in modules directory
let mut result = None;
let _ = crate::module::foreach_module(All, |module_path| {
if let Ok(props) = crate::module::read_module_prop(module_path)
&& is_metamodule(&props)
{
info!(
"Found metamodule in modules directory: {}",
module_path.display()
);
result = Some(module_path.to_path_buf());
}
Ok(())
});
result
}
/// Check if metamodule exists
pub fn has_metamodule() -> bool {
get_metamodule_path().is_some()
}
/// Check if it's safe to install a regular module
/// Returns Ok(()) if safe, Err(is_disabled) if blocked
/// - Err(true) means metamodule is disabled
/// - Err(false) means metamodule is in other unstable state
pub fn check_install_safety() -> Result<(), bool> {
// No metamodule → safe
let Some(metamodule_path) = get_metamodule_path() else {
return Ok(());
};
// No metainstall.sh → safe (uses default installer)
// The staged update directory may contain the latest scripts, so check both locations
let has_metainstall = metamodule_path
.join(defs::METAMODULE_METAINSTALL_SCRIPT)
.exists()
|| metamodule_path.file_name().is_some_and(|module_id| {
Path::new(defs::MODULE_UPDATE_DIR)
.join(module_id)
.join(defs::METAMODULE_METAINSTALL_SCRIPT)
.exists()
});
if !has_metainstall {
return Ok(());
}
// Check for marker files
let has_update = metamodule_path.join(defs::UPDATE_FILE_NAME).exists();
let has_remove = metamodule_path.join(defs::REMOVE_FILE_NAME).exists();
let has_disable = metamodule_path.join(defs::DISABLE_FILE_NAME).exists();
// Stable state (no markers) → safe
if !has_update && !has_remove && !has_disable {
return Ok(());
}
// Return true if disabled, false for other unstable states
Err(has_disable && !has_update && !has_remove)
}
/// Create or update the metamodule symlink
/// Points /data/adb/metamodule -> /data/adb/modules/{module_id}
pub fn ensure_symlink<P>(module_path: P) -> Result<()>
where
P: AsRef<Path>,
{
// METAMODULE_DIR might have trailing slash, so we need to trim it
let symlink_path = Path::new(defs::METAMODULE_DIR.trim_end_matches('/'));
let module_path = module_path.as_ref();
info!(
"Creating metamodule symlink: {} -> {}",
symlink_path.display(),
module_path.display()
);
// Remove existing symlink if it exists
if symlink_path.exists() || symlink_path.is_symlink() {
info!("Removing old metamodule symlink/path");
if symlink_path.is_symlink() {
std::fs::remove_file(symlink_path).with_context(|| "Failed to remove old symlink")?;
} else {
// Could be a directory, remove it
std::fs::remove_dir_all(symlink_path)
.with_context(|| "Failed to remove old directory")?;
}
}
// Create symlink
#[cfg(unix)]
std::os::unix::fs::symlink(module_path, symlink_path)
.with_context(|| format!("Failed to create symlink to {}", module_path.display()))?;
info!("Metamodule symlink created successfully");
Ok(())
}
/// Remove the metamodule symlink
pub fn remove_symlink() -> Result<()> {
let symlink_path = Path::new(defs::METAMODULE_DIR.trim_end_matches('/'));
if symlink_path.is_symlink() {
std::fs::remove_file(symlink_path)
.with_context(|| "Failed to remove metamodule symlink")?;
info!("Metamodule symlink removed");
}
Ok(())
}
/// Get the install script content, using metainstall.sh from metamodule if available
/// Returns the script content to be executed
pub fn get_install_script(
is_metamodule: bool,
installer_content: &str,
install_module_script: &str,
) -> Result<String> {
// Check if there's a metamodule with metainstall.sh
// Only apply this logic for regular modules (not when installing metamodule itself)
let install_script = if is_metamodule {
info!("Installing metamodule, using default installer");
install_module_script.to_string()
} else if let Some(metamodule_path) = get_metamodule_path() {
if metamodule_path.join(defs::DISABLE_FILE_NAME).exists() {
info!("Metamodule is disabled, using default installer");
install_module_script.to_string()
} else {
let metainstall_path = metamodule_path.join(defs::METAMODULE_METAINSTALL_SCRIPT);
if metainstall_path.exists() {
info!("Using metainstall.sh from metamodule");
let metamodule_content = std::fs::read_to_string(&metainstall_path)
.with_context(|| "Failed to read metamodule metainstall.sh")?;
format!("{installer_content}\n{metamodule_content}\nexit 0\n")
} else {
info!("Metamodule exists but has no metainstall.sh, using default installer");
install_module_script.to_string()
}
}
} else {
info!("No metamodule found, using default installer");
install_module_script.to_string()
};
Ok(install_script)
}
/// Check if metamodule script exists and is ready to execute
/// Returns None if metamodule doesn't exist, is disabled, or script is missing
/// Returns Some(script_path) if script is ready to execute
fn check_metamodule_script(script_name: &str) -> Option<PathBuf> {
// Check if metamodule exists
let metamodule_path = get_metamodule_path()?;
// Check if metamodule is disabled
if metamodule_path.join(defs::DISABLE_FILE_NAME).exists() {
info!("Metamodule is disabled, skipping {script_name}");
return None;
}
// Check if script exists
let script_path = metamodule_path.join(script_name);
if !script_path.exists() {
return None;
}
Some(script_path)
}
/// Execute metamodule's metauninstall.sh for a specific module
pub fn exec_metauninstall_script(module_id: &str) -> Result<()> {
let Some(metauninstall_path) = check_metamodule_script(defs::METAMODULE_METAUNINSTALL_SCRIPT)
else {
return Ok(());
};
info!("Executing metamodule metauninstall.sh for module: {module_id}",);
let result = Command::new(assets::BUSYBOX_PATH)
.args(["sh", metauninstall_path.to_str().unwrap()])
.current_dir(metauninstall_path.parent().unwrap())
.envs(crate::module::get_common_script_envs())
.env("MODULE_ID", module_id)
.status()?;
ensure!(
result.success(),
"Metamodule metauninstall.sh failed for module {module_id}: {:?}",
result
);
info!("Metamodule metauninstall.sh executed successfully for {module_id}",);
Ok(())
}
/// Execute metamodule mount script
pub fn exec_mount_script(module_dir: &str) -> Result<()> {
let Some(mount_script) = check_metamodule_script(defs::METAMODULE_MOUNT_SCRIPT) else {
return Ok(());
};
info!("Executing mount script for metamodule");
let result = Command::new(assets::BUSYBOX_PATH)
.args(["sh", mount_script.to_str().unwrap()])
.envs(crate::module::get_common_script_envs())
.env("MODULE_DIR", module_dir)
.status()?;
ensure!(
result.success(),
"Metamodule mount script failed with status: {:?}",
result
);
info!("Metamodule mount script executed successfully");
Ok(())
}
/// Execute metamodule script for a specific stage
pub fn exec_stage_script(stage: &str, block: bool) -> Result<()> {
let Some(script_path) = check_metamodule_script(&format!("{stage}.sh")) else {
return Ok(());
};
info!("Executing metamodule {stage}.sh");
crate::module::exec_script(&script_path, block)?;
info!("Metamodule {stage}.sh executed successfully");
Ok(())
}
================================================
FILE: apd/src/module.rs
================================================
#[cfg(unix)]
use std::os::unix::{prelude::PermissionsExt, process::CommandExt};
use std::{
collections::HashMap,
env::var as env_var,
fs::{self, remove_dir_all},
io::Cursor,
path::{Path, PathBuf},
process::Command,
str::FromStr,
};
use crate::lua;
use anyhow::{Context, Result, anyhow, bail, ensure};
use const_format::concatcp;
use is_executable::is_executable;
use java_properties::PropertiesIter;
use log::{info, warn};
use zip_extensions::zip_extract_file_to_memory;
#[allow(clippy::wildcard_imports)]
use crate::utils::*;
use crate::{
assets,
defs::{self, MODULE_DIR, MODULE_UPDATE_DIR},
metamodule, restorecon,
};
const INSTALLER_CONTENT: &str = include_str!("./installer.sh");
const INSTALL_MODULE_SCRIPT: &str = concatcp!(
INSTALLER_CONTENT,
"\n",
"install_module",
"\n",
"exit 0",
"\n"
);
#[derive(PartialEq, Eq)]
pub enum ModuleType {
All,
Active,
Updated,
}
fn exec_install_script(module_file: &str, is_metamodule: bool) -> Result<()> {
let realpath = std::fs::canonicalize(module_file)
.with_context(|| format!("realpath: {module_file} failed"))?;
// Get install script from metamodule module
let install_script =
metamodule::get_install_script(is_metamodule, INSTALLER_CONTENT, INSTALL_MODULE_SCRIPT)?;
let result = Command::new(assets::BUSYBOX_PATH)
.args(["sh", "-c", &install_script])
.envs(get_common_script_envs())
.env("OUTFD", "1")
.env("ZIPFILE", realpath)
.status()?;
ensure!(result.success(), "Failed to install module script");
Ok(())
}
pub fn handle_updated_modules() -> Result<()> {
let modules_root = Path::new(MODULE_DIR);
foreach_module(ModuleType::Updated, |updated_module| {
if !updated_module.is_dir() {
return Ok(());
}
if let Some(name) = updated_module.file_name() {
let module_dir = modules_root.join(name);
let mut disabled = false;
let mut removed = false;
if module_dir.exists() {
// If the old module is disabled, we need to also disable the new one
disabled = module_dir.join(defs::DISABLE_FILE_NAME).exists();
removed = module_dir.join(defs::REMOVE_FILE_NAME).exists();
remove_dir_all(&module_dir)?;
}
std::fs::rename(updated_module, &module_dir)?;
if removed {
let path = module_dir.join(defs::REMOVE_FILE_NAME);
if let Err(e) = ensure_file_exists(&path) {
warn!("Failed to create {}: {e}", path.display());
}
} else if disabled {
let path = module_dir.join(defs::DISABLE_FILE_NAME);
if let Err(e) = ensure_file_exists(&path) {
warn!("Failed to create {}: {e}", path.display());
}
}
}
Ok(())
})?;
Ok(())
}
/// Get common environment variables for script execution
pub fn get_common_script_envs() -> Vec<(&'static str, String)> {
vec![
("ASH_STANDALONE", "1".to_string()),
("APATCH", "true".to_string()),
("APATCH_VER", defs::VERSION_NAME.to_string()),
("APATCH_VER_CODE", defs::VERSION_CODE.to_string()),
(
"PATH",
format!(
"{}:{}",
env_var("PATH").unwrap_or_default(),
defs::BINARY_DIR.trim_end_matches('/')
),
),
]
}
// because we use something like A-B update
// we need to update the module state after the boot_completed
// if someone(such as the module) install a module before the boot_completed
// then it may cause some problems, just forbid it
fn ensure_boot_completed() -> Result<()> {
// ensure getprop sys.boot_completed == 1
if getprop("sys.boot_completed").as_deref() != Some("1") {
bail!("Android is Booting!");
}
Ok(())
}
fn mark_update() -> Result<()> {
ensure_file_exists(concatcp!(defs::WORKING_DIR, defs::UPDATE_FILE_NAME))
}
fn mark_module_state(module: &str, flag_file: &str, create_or_delete: bool) -> Result<()> {
let module_state_file = Path::new(defs::MODULE_DIR).join(module).join(flag_file);
if create_or_delete {
ensure_file_exists(module_state_file)
} else {
if module_state_file.exists() {
fs::remove_file(module_state_file)?;
}
Ok(())
}
}
pub fn foreach_module(
module_type: ModuleType,
mut f: impl FnMut(&Path) -> Result<()>,
) -> Result<()> {
let modules_dir = Path::new(match module_type {
ModuleType::Updated => MODULE_UPDATE_DIR,
_ => defs::MODULE_DIR,
});
let dir = std::fs::read_dir(modules_dir)?;
for entry in dir.flatten() {
let path = entry.path();
if !path.is_dir() {
warn!("{} is not a directory, skip", path.display());
continue;
}
if module_type == ModuleType::Active && path.join(defs::DISABLE_FILE_NAME).exists() {
info!("{} is disabled, skip", path.display());
continue;
}
if module_type == ModuleType::Active && path.join(defs::REMOVE_FILE_NAME).exists() {
warn!("{} is removed, skip", path.display());
continue;
}
f(&path)?;
}
Ok(())
}
fn foreach_active_module(f: impl FnMut(&Path) -> Result<()>) -> Result<()> {
foreach_module(ModuleType::Active, f)
}
pub fn load_sepolicy_rule() -> Result<()> {
foreach_active_module(|path| {
let rule_file = path.join("sepolicy.rule");
if !rule_file.exists() {
return Ok(());
}
info!("load policy: {}", &rule_file.display());
Command::new(assets::MAGISKPOLICY_PATH)
.arg("--live")
.arg("--apply")
.arg(&rule_file)
.status()
.with_context(|| format!("Failed to exec {}", rule_file.display()))?;
Ok(())
})?;
Ok(())
}
pub fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
info!("exec {}", path.as_ref().display());
let mut command = &mut Command::new(assets::BUSYBOX_PATH);
#[cfg(unix)]
{
command = command.process_group(0);
command = unsafe {
command.pre_exec(|| {
// ignore the error?
switch_cgroups();
Ok(())
})
};
}
command = command
.current_dir(path.as_ref().parent().unwrap())
.arg("sh")
.arg(path.as_ref())
.env("ASH_STANDALONE", "1")
.env("APATCH", "true")
.env("APATCH_VER", defs::VERSION_NAME)
.env("APATCH_VER_CODE", defs::VERSION_CODE)
.env(
"PATH",
format!(
"{}:{}",
env_var("PATH")?,
defs::BINARY_DIR.trim_end_matches('/')
),
);
let result = if wait {
command.status().map(|_| ())
} else {
command.spawn().map(|_| ())
};
result.map_err(|err| anyhow!("Failed to exec {}: {}", path.as_ref().display(), err))
}
pub fn exec_stage_script(stage: &str, block: bool) -> Result<()> {
foreach_active_module(|module| {
let script_path = module.join(format!("{stage}.sh"));
if !script_path.exists() {
return Ok(());
}
exec_script(&script_path, block)
})?;
Ok(())
}
pub fn exec_common_scripts(dir: &str, wait: bool) -> Result<()> {
let script_dir = Path::new(defs::ADB_DIR).join(dir);
if !script_dir.exists() {
info!("{} not exists, skip", script_dir.display());
return Ok(());
}
let dir = fs::read_dir(&script_dir)?;
for entry in dir.flatten() {
let path = entry.path();
if !is_executable(&path) {
warn!("{} is not executable, skip", path.display());
continue;
}
exec_script(path, wait)?;
}
Ok(())
}
pub fn load_system_prop() -> Result<()> {
foreach_active_module(|module| {
let system_prop = module.join("system.prop");
if !system_prop.exists() {
return Ok(());
}
info!("load {} system.prop", module.display());
// resetprop -n --file system.prop
Command::new(assets::RESETPROP_PATH)
.arg("-n")
.arg("--file")
.arg(&system_prop)
.status()
.with_context(|| format!("Failed to exec {}", system_prop.display()))?;
Ok(())
})?;
Ok(())
}
pub fn prune_modules() -> Result<()> {
foreach_module(ModuleType::All, |module| {
fs::remove_file(module.join(defs::UPDATE_FILE_NAME)).ok();
if !module.join(defs::REMOVE_FILE_NAME).exists() {
return Ok(());
}
info!("remove module: {}", module.display());
// Execute metamodule's metauninstall.sh first
let module_id = module.file_name().and_then(|n| n.to_str()).unwrap_or("");
// Check if this is a metamodule
let is_metamodule = read_module_prop(module)
.map(|props| metamodule::is_metamodule(&props))
.unwrap_or(false);
if is_metamodule {
info!("Removing metamodule symlink");
if let Err(e) = metamodule::remove_symlink() {
warn!("Failed to remove metamodule symlink: {e}");
}
} else if let Err(e) = metamodule::exec_metauninstall_script(module_id) {
warn!("Failed to exec metamodule uninstall for {module_id}: {e}",);
}
// Then execute module's own uninstall.sh
let uninstaller = module.join("uninstall.sh");
if uninstaller.exists()
&& let Err(e) = exec_script(uninstaller, true)
{
warn!("Failed to exec uninstaller: {e}");
}
// Finally remove the module directory
if let Err(e) = remove_dir_all(module) {
warn!("Failed to remove {}: {e}", module.display());
}
Ok(())
})?;
// collect remaining modules, if none, clean up metamodule record
let remaining_modules: Vec<_> = std::fs::read_dir(defs::MODULE_DIR)?
.filter_map(std::result::Result::ok)
.filter(|entry| entry.path().join("module.prop").exists())
.collect();
if remaining_modules.is_empty() {
info!("no remaining modules.");
}
Ok(())
}
fn _install_module(zip: &str) -> Result<()> {
ensure_boot_completed()?;
// print banner
println!(include_str!("banner"));
assets::ensure_binaries().with_context(|| "binary missing")?;
// first check if workding dir is usable
ensure_dir_exists(defs::WORKING_DIR).with_context(|| "Failed to create working dir")?;
ensure_dir_exists(defs::BINARY_DIR).with_context(|| "Failed to create bin dir")?;
// read the module_id from zip
let mut buffer: Vec<u8> = Vec::new();
let entry_path = PathBuf::from_str("module.prop")?;
let zip_path = PathBuf::from_str(zip)?;
let zip_path = zip_path.canonicalize()?;
zip_extract_file_to_memory(&zip_path, &entry_path, &mut buffer)?;
let mut module_prop = HashMap::new();
PropertiesIter::new_with_encoding(Cursor::new(buffer), encoding_rs::UTF_8).read_into(
|k, v| {
module_prop.insert(k, v);
},
)?;
info!("module prop: {:?}", module_prop);
let Some(module_id) = module_prop.get("id") else {
bail!("module id not found in module.prop!");
};
let module_id = module_id.trim();
// Check if this module is a metamodule
let is_metamodule = metamodule::is_metamodule(&module_prop);
// Check if module needs mounting (has system/ dir and no skip_mount file)
let needs_mount = {
let zip_file = fs::File::open(&zip_path)?;
let archive = zip::ZipArchive::new(zip_file)?;
let has_system = archive.file_names().any(|name| name.starts_with("system/"));
let has_skip_mount = archive.file_names().any(|name| name == "skip_mount");
has_system && !has_skip_mount
};
// Check if it's safe to install regular module
if !is_metamodule && needs_mount && let Err(is_disabled) = metamodule::check_install_safety() {
println!("\n❌ Installation Blocked");
println!("┌────────────────────────────────");
println!("│ A metamodule with custom installer is active");
println!("│");
if is_disabled {
println!("│ Current state: Disabled");
println!("│ Action required: Re-enable or uninstall it, then reboot");
} else {
println!("│ Current state: Pending changes");
println!("│ Action required: Reboot to apply changes first");
}
println!("└─────────────────────────────────\n");
bail!("Metamodule installation blocked");
}
let modules_dir = Path::new(defs::MODULE_DIR);
let modules_update_dir = Path::new(defs::MODULE_UPDATE_DIR);
if !Path::new(modules_dir).exists() {
fs::create_dir(modules_dir).expect("Failed to create modules folder");
let permissions = fs::Permissions::from_mode(0o700);
fs::set_permissions(modules_dir, permissions).expect("Failed to set permissions");
}
if is_metamodule {
info!("Installing metamodule: {module_id}");
// Check if there's already a metamodule installed
if metamodule::has_metamodule()
&& let Some(existing_path) = metamodule::get_metamodule_path()
{
let existing_id = read_module_prop(&existing_path)
.ok()
.and_then(|m| m.get("id").cloned())
.unwrap_or_else(|| "unknown".to_string());
if existing_id != module_id {
println!("\n❌ Installation Failed");
println!("┌────────────────────────────────");
println!("│ A metamodule is already installed");
println!("│ Current metamodule: {existing_id}");
println!("│");
println!("│ Only one metamodule can be active at a time.");
println!("│");
println!("│ To install this metamodule:");
println!("│ 1. Uninstall the current metamodule");
println!("│ 2. Reboot your device");
println!("│ 3. Install the new metamodule");
println!("└─────────────────────────────────\n");
bail!("Cannot install multiple metamodules");
}
}
}
let module_dir = format!("{}{}", modules_dir.display(), module_id);
let _module_update_dir = format!("{}{}", modules_update_dir.display(), module_id);
info!("module dir: {}", module_dir);
if !Path::new(&module_dir.clone()).exists() {
fs::create_dir(&module_dir.clone()).expect("Failed to create module folder");
let permissions = fs::Permissions::from_mode(0o700);
fs::set_permissions(module_dir.clone(), permissions).expect("Failed to set permissions");
}
// unzip the image and move it to modules_update/<id> dir
let file = fs::File::open(zip)?;
let mut archive = zip::ZipArchive::new(file)?;
archive.extract(&_module_update_dir)?;
println!("- Running module installer");
exec_install_script(zip, is_metamodule)?;
// set permission and selinux context for $MOD/system
let module_system_dir = PathBuf::from(module_dir.clone()).join("system");
if module_system_dir.exists() {
#[cfg(unix)]
fs::set_permissions(&module_system_dir, fs::Permissions::from_mode(0o755))?;
restorecon::restore_syscon(&module_system_dir)?;
}
// Create symlink for metamodule
if is_metamodule {
println!("- Creating metamodule symlink");
metamodule::ensure_symlink(&module_dir)?;
}
mark_update()?;
Ok(())
}
pub fn install_module(zip: &str) -> Result<()> {
let result = _install_module(zip);
result
}
pub fn _uninstall_module(id: &str, update_dir: &str) -> Result<()> {
let dir = Path::new(update_dir);
ensure!(dir.exists(), "No module installed");
// iterate the modules_update dir, find the module to be removed
let dir = fs::read_dir(dir)?;
for entry in dir.flatten() {
let path = entry.path();
let module_prop = path.join("module.prop");
if !module_prop.exists() {
continue;
}
let content = fs::read(module_prop)?;
let mut module_id: String = String::new();
PropertiesIter::new_with_encoding(Cursor::new(content), encoding_rs::UTF_8).read_into(
|k, v| {
if k.eq("id") {
module_id = v;
}
},
)?;
if module_id.eq(id) {
let remove_file = path.join(defs::REMOVE_FILE_NAME);
fs::File::create(remove_file).with_context(|| "Failed to create remove file.")?;
break;
}
}
// santity check
let target_module_path = format!("{update_dir}/{id}");
let target_module = Path::new(&target_module_path);
if target_module.exists() {
let remove_file = target_module.join(defs::REMOVE_FILE_NAME);
if !remove_file.exists() {
fs::File::create(remove_file).with_context(|| "Failed to create remove file.")?;
}
}
let _ = mark_module_state(id, defs::REMOVE_FILE_NAME, true);
Ok(())
}
pub fn uninstall_module(id: &str) -> Result<()> {
_uninstall_module(id, defs::MODULE_DIR)?;
mark_update()?;
Ok(())
}
/// Read module.prop from the given module path and return as a HashMap
pub fn read_module_prop(module_path: &Path) -> Result<HashMap<String, String>> {
let module_prop = module_path.join("module.prop");
ensure!(
module_prop.exists(),
"module.prop not found in {}",
module_path.display()
);
let content = std::fs::read(&module_prop)
.with_context(|| format!("Failed to read module.prop: {}", module_prop.display()))?;
let mut prop_map: HashMap<String, String> = HashMap::new();
PropertiesIter::new_with_encoding(Cursor::new(content), encoding_rs::UTF_8)
.read_into(|k, v| {
prop_map.insert(k, v);
})
.with_context(|| format!("Failed to parse module.prop: {}", module_prop.display()))?;
Ok(prop_map)
}
pub fn run_action(id: &str) -> Result<()> {
let action_script_path = format!("/data/adb/modules/{}/action.sh", id);
if Path::new(&action_script_path).exists() {
let _ = exec_script(&action_script_path, true);
} else {
//if no action.sh, try to run lua action
lua::run_lua(&id, "action", false, true).map_err(|e| anyhow::anyhow!("{}", e))?;
}
Ok(())
}
fn _change_module_state(module_dir: &str, mid: &str, enable: bool) -> Result<()> {
let src_module_path = format!("{module_dir}/{mid}");
let src_module = Path::new(&src_module_path);
ensure!(src_module.exists(), "module: {} not found!", mid);
let disable_path = src_module.join(defs::DISABLE_FILE_NAME);
if enable {
if disable_path.exists() {
fs::remove_file(&disable_path).with_context(|| {
format!("Failed to remove disable file: {}", &disable_path.display())
})?;
}
} else {
ensure_file_exists(disable_path)?;
}
let _ = mark_module_state(mid, defs::DISABLE_FILE_NAME, !enable);
Ok(())
}
pub fn _enable_module(id: &str, update_dir: &Path) -> Result<()> {
if let Some(module_dir_str) = update_dir.to_str() {
_change_module_state(module_dir_str, id, true)
} else {
info!("Enable module failed: Invalid path");
Err(anyhow::anyhow!("Invalid module directory"))
}
}
pub fn enable_module(id: &str) -> Result<()> {
let update_dir = Path::new(defs::MODULE_DIR);
_enable_module(id, update_dir)?;
Ok(())
}
pub fn _disable_module(id: &str, update_dir: &Path) -> Result<()> {
if let Some(module_dir_str) = update_dir.to_str() {
_change_module_state(module_dir_str, id, false)
} else {
info!("Disable module failed: Invalid path");
Err(anyhow::anyhow!("Invalid module directory"))
}
}
pub fn disable_module(id: &str) -> Result<()> {
let module_dir = Path::new(defs::MODULE_DIR);
_disable_module(id, module_dir)?;
Ok(())
}
pub fn _disable_all_modules(dir: &str) -> Result<()> {
let dir = fs::read_dir(dir)?;
for entry in dir.flatten() {
let path = entry.path();
let disable_flag = path.join(defs::DISABLE_FILE_NAME);
if let Err(e) = ensure_file_exists(disable_flag) {
warn!("Failed to disable module: {}: {}", path.display(), e);
}
}
Ok(())
}
pub fn disable_all_modules() -> Result<()> {
// Skip disabling modules since boot completed
if getprop("sys.boot_completed").as_deref() == Some("1") {
info!("System boot completed, no need to disable all modules");
return Ok(());
}
mark_update()?;
_disable_all_modules(defs::MODULE_DIR)?;
Ok(())
}
fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
// first check enabled modules
let dir = fs::read_dir(path);
let Ok(dir) = dir else {
return Vec::new();
};
let mut modules: Vec<HashMap<String, String>> = Vec::new();
for entry in dir.flatten() {
let path = entry.path();
info!("path: {}", path.display());
let module_prop = path.join("module.prop");
if !module_prop.exists() {
continue;
}
let content = fs::read(&module_prop);
let Ok(content) = content else {
warn!("Failed to read file: {}", module_prop.display());
continue;
};
let mut module_prop_map: HashMap<String, String> = HashMap::new();
let encoding = encoding_rs::UTF_8;
let result =
PropertiesIter::new_with_encoding(Cursor::new(content), encoding).read_into(|k, v| {
module_prop_map.insert(k, v);
});
if !module_prop_map.contains_key("id") || module_prop_map["id"].is_empty() {
match entry.file_name().to_str() {
Some(id) => {
info!("Use dir name as module id: {}", id);
module_prop_map.insert("id".to_owned(), id.to_owned());
}
_ => {
info!("Failed to get module id: {:?}", module_prop);
continue;
}
}
}
// Add enabled, update, remove flags
let enabled = !path.join(defs::DISABLE_FILE_NAME).exists();
let update = path.join(defs::UPDATE_FILE_NAME).exists();
let remove = path.join(defs::REMOVE_FILE_NAME).exists();
let web = path.join(defs::MODULE_WEB_DIR).exists();
let id = module_prop_map.get("id").map(|s| s.as_str()).unwrap_or("");
let id_lua_file = format!("{}.lua", id);
let action = path.join(defs::MODULE_ACTION_SH).exists() || path.join(&id_lua_file).exists();
module_prop_map.insert("enabled".to_owned(), enabled.to_string());
module_prop_map.insert("update".to_owned(), update.to_string());
module_prop_map.insert("remove".to_owned(), remove.to_string());
module_prop_map.insert("web".to_owned(), web.to_string());
module_prop_map.insert("action".to_owned(), action.to_string());
if result.is_err() {
warn!("Failed to parse module.prop: {}", module_prop.display());
continue;
}
modules.push(module_prop_map);
}
modules
}
pub fn list_modules() -> Result<()> {
let modules = _list_modules(defs::MODULE_DIR);
println!("{}", serde_json::to_string_pretty(&modules)?);
Ok(())
}
================================================
FILE: apd/src/package.rs
================================================
use std::{
fs::File,
io::{self, BufRead},
path::Path,
thread,
time::Duration,
};
use log::{info, warn};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Clone)]
pub struct PackageConfig {
pub pkg: String,
pub exclude: i32,
pub allow: i32,
pub uid: i32,
pub to_uid: i32,
pub sctx: String,
}
pub fn read_ap_package_config() -> Vec<PackageConfig> {
let max_retry = 5;
for _ in 0..max_retry {
let file = match File::open("/data/adb/ap/package_config") {
Ok(file) => file,
Err(e) => {
warn!("Error opening file: {}", e);
thread::sleep(Duration::from_secs(1));
continue;
}
};
let mut reader = csv::Reader::from_reader(file);
let mut package_configs = Vec::new();
let mut success = true;
for record in reader.deserialize() {
match record {
Ok(config) => package_configs.push(config),
Err(e) => {
warn!("Error deserializing record: {}", e);
success = false;
break;
}
}
}
if success {
return package_configs;
}
thread::sleep(Duration::from_secs(1));
}
Vec::new()
}
pub fn write_ap_package_config(package_configs: &[PackageConfig]) -> io::Result<()> {
let max_retry = 5;
for _ in 0..max_retry {
let temp_path = "/data/adb/ap/package_config.tmp";
let file = match File::create(temp_path) {
Ok(file) => file,
Err(e) => {
warn!("Error creating temp file: {}", e);
thread::sleep(Duration::from_secs(1));
continue;
}
};
let mut writer = csv::Writer::from_writer(file);
let mut success = true;
for config in package_configs {
if let Err(e) = writer.serialize(config) {
warn!("Error serializing record: {}", e);
success = false;
break;
}
}
if !success {
thread::sleep(Duration::from_secs(1));
continue;
}
if let Err(e) = writer.flush() {
warn!("Error flushing writer: {}", e);
thread::sleep(Duration::from_secs(1));
continue;
}
if let Err(e) = std::fs::rename(temp_path, "/data/adb/ap/package_config") {
warn!("Error renaming temp file: {}", e);
thread::sleep(Duration::from_secs(1));
continue;
}
return Ok(());
}
Err(io::Error::new(
io::ErrorKind::Other,
"Failed after max retries",
))
}
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
File::open(filename).map(|file| io::BufReader::new(file).lines())
}
pub fn synchronize_package_uid() -> io::Result<()> {
info!("[synchronize_package_uid] Start synchronizing root list with system packages...");
let max_retry = 5;
for _ in 0..max_retry {
match read_lines("/data/system/packages.list") {
Ok(lines) => {
let lines: Vec<_> = lines.filter_map(|line| line.ok()).collect();
let mut package_configs = read_ap_package_config();
let system_packages: Vec<String> = lines
.iter()
.filter_map(|line| line.split_whitespace().next())
.map(|pkg| pkg.to_string())
.collect();
let original_len = package_configs.len();
package_configs.retain(|config| system_packages.contains(&config.pkg));
let removed_count = original_len - package_configs.len();
if removed_count > 0 {
info!(
"Removed {} uninstalled package configurations",
removed_count
);
}
let mut updated = false;
for line in &lines {
let words: Vec<&str> = line.split_whitespace().collect();
if words.len() >= 2 {
let pkg_name = words[0];
if let Ok(uid) = words[1].parse::<i32>() {
for config in package_configs
.iter_mut()
.filter(|config| config.pkg == pkg_name)
{
if config.uid % 100000 != uid % 100000 {
let new_uid = config.uid / 100000 * 100000 + uid % 100000;
info!(
"Updating uid for package {}: {} -> {}",
pkg_name, config.uid, new_uid
);
config.uid = new_uid;
updated = true;
}
}
} else {
warn!("Error parsing uid: {}", words[1]);
}
}
}
if updated || removed_count > 0 {
write_ap_package_config(&package_configs)?;
}
return Ok(());
}
Err(e) => {
warn!("Error reading packages.list: {}", e);
thread::sleep(Duration::from_secs(1));
}
}
}
Err(io::Error::new(
io::ErrorKind::Other,
"Failed after max retries",
))
}
================================================
FILE: apd/src/pty.rs
================================================
use std::{
ffi::c_int,
fs::File,
io::{Read, Write, stderr, stdin, stdout},
mem::MaybeUninit,
os::fd::{AsFd, AsRawFd, OwnedFd, RawFd},
process::exit,
ptr::null_mut,
sync::Mutex,
thread,
};
use anyhow::{Ok, Result, bail};
use libc::{
__errno, EINTR, SIG_BLOCK, SIG_UNBLOCK, SIGWINCH, TIOCGWINSZ, TIOCSWINSZ, fork,
pthread_sigmask, sigaddset, sigemptyset, sigset_t, sigwait, waitpid, winsize,
};
use rustix::{
fs::{Mode, OFlags, open},
io::dup,
ioctl::{Getter, Opcode, ioctl, opcode},
process::setsid,
pty::{grantpt, unlockpt},
stdio::{dup2_stderr, dup2_stdin, dup2_stdout},
termios::{OptionalActions, Termios, isatty, tcgetattr, tcsetattr},
};
use crate::{defs::PTS_NAME, utils::get_tmp_path};
// https://github.com/topjohnwu/Magisk/blob/5627053b7481618adfdf8fa3569b48275589915b/native/src/core/su/pts.cpp
fn get_pty_num<F: AsFd>(fd: F) -> Result<u32> {
// TIOCGPTN: Get the PTY number
const TIOCGPTN: Opcode = opcode::read::<u32>(b'T', 0x30);
Ok(unsafe {
let tiocgptn = Getter::<TIOCGPTN, u32>::new();
ioctl(fd, tiocgptn)?
})
}
static OLD_STDIN: Mutex<Option<Termios>> = Mutex::new(None);
fn watch_sigwinch_async(slave: RawFd) {
let mut winch = MaybeUninit::<sigset_t>::uninit();
unsafe {
sigemptyset(winch.as_mut_ptr());
sigaddset(winch.as_mut_ptr(), SIGWINCH);
pthread_sigmask(SIG_BLOCK, winch.as_mut_ptr(), null_mut());
}
thread::spawn(move || unsafe {
let mut winch = MaybeUninit::<sigset_t>::uninit();
sigemptyset(winch.as_mut_ptr());
sigaddset(winch.as_mut_ptr(), SIGWINCH);
pthread_sigmask(SIG_UNBLOCK, winch.as_mut_ptr(), null_mut());
let mut sig: c_int = 0;
loop {
let mut w = MaybeUninit::<winsize>::uninit();
if libc::ioctl(1, TIOCGWINSZ, w.as_mut_ptr()) < 0 {
continue;
}
libc::ioctl(slave, TIOCSWINSZ, w.as_mut_ptr());
if sigwait(winch.as_mut_ptr(), &mut sig) != 0 {
break;
}
}
});
}
fn set_stdin_raw() -> rustix::io::Result<()> {
let mut termios = tcgetattr(stdin())?;
let mut guard = OLD_STDIN.lock().unwrap();
*guard = Some(termios.clone());
drop(guard);
termios.make_raw();
tcsetattr(stdin(), OptionalActions::Flush, &termios)
}
fn restore_stdin() -> Result<()> {
let mut guard = OLD_STDIN.lock().unwrap();
if let Some(original_termios) = guard.take() {
tcsetattr(stdin(), OptionalActions::Flush, &original_termios)?;
}
Ok(())
}
fn pump<R: Read, W: Write>(mut from: R, mut to: W) {
let mut buf = [0u8; 4096];
loop {
match from.read(&mut buf) {
Result::Ok(len) => {
if len == 0 {
return;
}
if to.write_all(&buf[0..len]).is_err() {
return;
}
if to.flush().is_err() {
return;
}
}
Err(_) => {
return;
}
}
}
}
fn pump_stdin_async(mut ptmx: File) {
let _ = set_stdin_raw();
thread::spawn(move || {
let mut stdin = stdin();
pump(&mut stdin, &mut ptmx);
});
}
fn pump_stdout_blocking(mut ptmx: File) {
let mut stdout = stdout();
pump(&mut ptmx, &mut stdout);
let _ = restore_stdin();
}
fn create_transfer(ptmx: OwnedFd) -> Result<()> {
let pid = unsafe { fork() };
match pid {
d if d < 0 => bail!("fork"),
0 => return Ok(()),
_ => {}
}
let ptmx_r = ptmx;
let ptmx_w = dup(&ptmx_r)?;
let ptmx_r = File::from(ptmx_r);
let ptmx_w = File::from(ptmx_w);
watch_sigwinch_async(ptmx_w.as_raw_fd());
pump_stdin_async(ptmx_r);
pump_stdout_blocking(ptmx_w);
let mut status: c_int = -1;
unsafe {
loop {
if waitpid(pid, &mut status, 0) == -1 && *__errno() != EINTR {
continue;
}
break;
}
}
exit(status)
}
pub fn prepare_pty() -> Result<()> {
let tty_in = isatty(stdin());
let tty_out = isatty(stdout());
let tty_err = isatty(stderr());
if !tty_in && !tty_out && !tty_err {
return Ok(());
}
let mut pts_path = format!("{}/{}", get_tmp_path(), PTS_NAME);
if !std::path::Path::new(&pts_path).exists() {
pts_path = "/dev/pts".to_string();
}
let ptmx_path = format!("{}/ptmx", pts_path);
let ptmx_fd = open(ptmx_path, OFlags::RDWR, Mode::empty())?;
grantpt(&ptmx_fd)?;
unlockpt(&ptmx_fd)?;
let pty_num = get_pty_num(&ptmx_fd)?;
create_transfer(ptmx_fd)?;
setsid()?;
let pty_fd = open(format!("{pts_path}/{pty_num}"), OFlags::RDWR, Mode::empty())?;
if tty_in {
dup2_stdin(&pty_fd)?;
}
if tty_out {
dup2_stdout(&pty_fd)?;
}
if tty_err {
dup2_stderr(&pty_fd)?;
}
Ok(())
}
================================================
FILE: apd/src/restorecon.rs
================================================
use std::path::Path;
use anyhow::Result;
#[cfg(any(target_os = "linux", target_os = "android"))]
use anyhow::{Context, Ok};
#[cfg(any(target_os = "linux", target_os = "android"))]
use extattr::{Flags as XattrFlags, lsetxattr};
use jwalk::{Parallelism::Serial, WalkDir};
use crate::defs;
pub const SYSTEM_CON: &str = "u:object_r:system_file:s0";
pub const ADB_CON: &str = "u:object_r:adb_data_file:s0";
pub const UNLABEL_CON: &str = "u:object_r:unlabeled:s0";
const SELINUX_XATTR: &str = "security.selinux";
pub fn lsetfilecon<P: AsRef<Path>>(path: P, con: &str) -> Result<()> {
#[cfg(any(target_os = "linux", target_os = "android"))]
lsetxattr(&path, SELINUX_XATTR, con, XattrFlags::empty()).with_context(|| {
format!(
"Failed to change SELinux context for {}",
path.as_ref().display()
)
})?;
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn lgetfilecon<P: AsRef<Path>>(path: P) -> Result<String> {
let con = extattr::lgetxattr(&path, SELINUX_XATTR).with_context(|| {
format!(
"Failed to get SELinux context for {}",
path.as_ref().display()
)
})?;
let con = String::from_utf8_lossy(&con);
Ok(con.to_string())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn setsyscon<P: AsRef<Path>>(path: P) -> Result<()> {
lsetfilecon(path, SYSTEM_CON)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn setsyscon<P: AsRef<Path>>(path: P) -> Result<()> {
unimplemented!()
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn lgetfilecon<P: AsRef<Path>>(path: P) -> Result<String> {
unimplemented!()
}
pub fn restore_syscon<P: AsRef<Path>>(dir: P) -> Result<()> {
for dir_entry in WalkDir::new(dir).parallelism(Serial) {
if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) {
setsyscon(&path)?;
}
}
Ok(())
}
fn restore_syscon_if_unlabeled<P: AsRef<Path>>(dir: P) -> Result<()> {
for dir_entry in WalkDir::new(dir).parallelism(Serial) {
if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) {
if let Result::Ok(con) = lgetfilecon(&path) {
if con == UNLABEL_CON || con.is_empty() {
lsetfilecon(&path, SYSTEM_CON)?;
}
}
}
}
Ok(())
}
pub fn restorecon() -> Result<()> {
lsetfilecon(defs::DAEMON_PATH, ADB_CON)?;
restore_syscon_if_unlabeled(defs::MODULE_DIR)?;
Ok(())
}
================================================
FILE: apd/src/sepolicy.rs
================================================
use std::{ffi, path::Path, vec};
use anyhow::{Result, bail};
use derive_new::new;
use nom::{
AsChar, IResult, Parser,
branch::alt,
bytes::complete::{tag, take_while, take_while_m_n, take_while1},
character::complete::{space0, space1},
combinator::map,
};
type SeObject<'a> = Vec<&'a str>;
fn is_sepolicy_char(c: char) -> bool {
c.is_alphanum() || c == '_' || c == '-'
}
fn parse_single_word(input: &str) -> IResult<&str, &str> {
take_while1(is_sepolicy_char).parse(input)
}
fn parse_bracket_objs(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, (_, words, _)) = (
tag("{"),
take_while_m_n(1, 100, |c: char| is_sepolicy_char(c) || c.is_whitespace()),
tag("}"),
)
.parse(input)?;
Ok((input, words.split_whitespace().collect()))
}
fn parse_single_obj(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, word) = take_while1(is_sepolicy_char).parse(input)?;
Ok((input, vec![word]))
}
fn parse_star(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, _) = tag("*").parse(input)?;
Ok((input, vec!["*"]))
}
// 1. a single sepolicy word
// 2. { obj1 obj2 obj3 ...}
// 3. *
fn parse_seobj(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, strs) = alt((parse_single_obj, parse_bracket_objs, parse_star)).parse(input)?;
Ok((input, strs))
}
fn parse_seobj_no_star(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, strs) = alt((parse_single_obj, parse_bracket_objs)).parse(input)?;
Ok((input, strs))
}
trait SeObjectParser<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self>
where
Self: Sized;
}
#[derive(Debug, PartialEq, Eq, new)]
struct NormalPerm<'a> {
op: &'a str,
source: SeObject<'a>,
target: SeObject<'a>,
class: SeObject<'a>,
perm: SeObject<'a>,
}
#[derive(Debug, PartialEq, Eq, new)]
struct XPerm<'a> {
op: &'a str,
source: SeObject<'a>,
target: SeObject<'a>,
class: SeObject<'a>,
operation: &'a str,
perm_set: &'a str,
}
#[derive(Debug, PartialEq, Eq, new)]
struct TypeState<'a> {
op: &'a str,
stype: SeObject<'a>,
}
#[derive(Debug, PartialEq, Eq, new)]
struct TypeAttr<'a> {
stype: SeObject<'a>,
sattr: SeObject<'a>,
}
#[derive(Debug, PartialEq, Eq, new)]
struct Type<'a> {
name: &'a str,
attrs: SeObject<'a>,
}
#[derive(Debug, PartialEq, Eq, new)]
struct Attr<'a> {
name: &'a str,
}
#[derive(Debug, PartialEq, Eq, new)]
struct TypeTransition<'a> {
source: &'a str,
target: &'a str,
class: &'a str,
default_type: &'a str,
object_name: Option<&'a str>,
}
#[derive(Debug, PartialEq, Eq, new)]
struct TypeChange<'a> {
op: &'a str,
source: &'a str,
target: &'a str,
class: &'a str,
default_type: &'a str,
}
#[derive(Debug, PartialEq, Eq, new)]
struct GenFsCon<'a> {
fs_name: &'a str,
partial_path: &'a str,
fs_context: &'a str,
}
#[derive(Debug)]
enum PolicyStatement<'a> {
// "allow *source_type *target_type *class *perm_set"
// "deny *source_type *target_type *class *perm_set"
// "auditallow *source_type *target_type *class *perm_set"
// "dontaudit *source_type *target_type *class *perm_set"
NormalPerm(NormalPerm<'a>),
// "allowxperm *source_type *target_type *class operation xperm_set"
// "auditallowxperm *source_type *target_type *class operation xperm_set"
// "dontauditxperm *source_type *target_type *class operation xperm_set"
XPerm(XPerm<'a>),
// "permissive ^type"
// "enforce ^type"
TypeState(TypeState<'a>),
// "type type_name ^(attribute)"
Type(Type<'a>),
// "typeattribute ^type ^attribute"
TypeAttr(TypeAttr<'a>),
// "attribute ^attribute"
Attr(Attr<'a>),
// "type_transition source_type target_type class default_type (object_name)"
TypeTransition(TypeTransition<'a>),
// "type_change source_type target_type class default_type"
// "type_member source_type target_type class default_type"
TypeChange(TypeChange<'a>),
// "genfscon fs_name partial_path fs_context"
GenFsCon(GenFsCon<'a>),
}
impl<'a> SeObjectParser<'a> for NormalPerm<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (input, op) = alt((
tag("allow"),
tag("deny"),
tag("auditallow"),
tag("dontaudit"),
))
.parse(input)?;
let (input, _) = space0(input)?;
let (input, source) = parse_seobj(input)?;
let (input, _) = space0(input)?;
let (input, target) = parse_seobj(input)?;
let (input, _) = space0(input)?;
let (input, class) = parse_seobj(input)?;
let (input, _) = space0(input)?;
let (input, perm) = parse_seobj(input)?;
Ok((input, NormalPerm::new(op, source, target, class, perm)))
}
}
impl<'a> SeObjectParser<'a> for XPerm<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (input, op) = alt((
tag("allowxperm"),
tag("auditallowxperm"),
tag("dontauditxperm"),
))
.parse(input)?;
let (input, _) = space0(input)?;
let (input, source) = parse_seobj(input)?;
let (input, _) = space0(input)?;
let (input, target) = parse_seobj(input)?;
let (input, _) = space0(input)?;
let (input, class) = parse_seobj(input)?;
let (input, _) = space0(input)?;
let (input, operation) = parse_single_word(input)?;
let (input, _) = space0(input)?;
let (input, perm_set) = parse_single_word(input)?;
Ok((
input,
XPerm::new(op, source, target, class, operation, perm_set),
))
}
}
impl<'a> SeObjectParser<'a> for TypeState<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (input, op) = alt((tag("permissive"), tag("enforce"))).parse(input)?;
let (input, _) = space1(input)?;
let (input, stype) = parse_seobj_no_star(input)?;
Ok((input, TypeState::new(op, stype)))
}
}
impl<'a> SeObjectParser<'a> for Type<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (input, _) = tag("type")(input)?;
let (input, _) = space1(input)?;
let (input, name) = parse_single_word(input)?;
if input.is_empty() {
return Ok((input, Type::new(name, vec!["domain"]))); // default to domain
}
let (input, _) = space1(input)?;
let (input, attrs) = parse_seobj_no_star(input)?;
Ok((input, Type::new(name, attrs)))
}
}
impl<'a> SeObjectParser<'a> for TypeAttr<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (input, _) = alt((tag("typeattribute"), tag("attradd"))).parse(input)?;
let (input, _) = space1(input)?;
let (input, stype) = parse_seobj_no_star(input)?;
let (input, _) = space1(input)?;
let (input, attr) = parse_seobj_no_star(input)?;
Ok((input, TypeAttr::new(stype, attr)))
}
}
impl<'a> SeObjectParser<'a> for Attr<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (input, _) = tag("attribute")(input)?;
let (input, _) = space1(input)?;
let (input, attr) = parse_single_word(input)?;
Ok((input, Attr::new(attr)))
}
}
impl<'a> SeObjectParser<'a> for TypeTransition<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (input, _) = alt((tag("type_transition"), tag("name_transition"))).parse(input)?;
let (input, _) = space1(input)?;
let (input, source) = parse_single_word(input)?;
let (input, _) = space1(input)?;
let (input, target) = parse_single_word(input)?;
let (input, _) = space1(input)?;
let (input, class) = parse_single_word(input)?;
let (input, _) = space1(input)?;
let (input, default) = parse_single_word(input)?;
if input.is_empty() {
return Ok((
input,
TypeTransition::new(source, target, class, default, None),
));
}
let (input, _) = space1(input)?;
let (input, object) = parse_single_word(input)?;
Ok((
input,
TypeTransition::new(source, target, class, default, Some(object)),
))
}
}
impl<'a> SeObjectParser<'a> for TypeChange<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (input, op) = alt((tag("type_change"), tag("type_member"))).parse(input)?;
let (input, _) = space1(input)?;
let (input, source) = parse_single_word(input)?;
let (input, _) = space1(input)?;
let (input, target) = parse_single_word(input)?;
let (input, _) = space1(input)?;
let (input, class) = parse_single_word(input)?;
let (input, _) = space1(input)?;
let (input, default) = parse_single_word(input)?;
Ok((input, TypeChange::new(op, source, target, class, default)))
}
}
impl<'a> SeObjectParser<'a> for GenFsCon<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self>
where
Self: Sized,
{
let (input, _) = tag("genfscon")(input)?;
let (input, _) = space1(input)?;
let (input, fs) = parse_single_word(input)?;
let (input, _) = space1(input)?;
let (input, path) = parse_single_word(input)?;
let (input, _) = space1(input)?;
let (input, context) = parse_single_word(input)?;
Ok((input, GenFsCon::new(fs, path, context)))
}
}
impl<'a> PolicyStatement<'a> {
fn parse(input: &'a str) -> IResult<&'a str, Self> {
let (input, _) = space0(input)?;
let (input, statement) = alt((
map(NormalPerm::parse, PolicyStatement::NormalPerm),
map(XPerm::parse, PolicyStatement::XPerm),
map(TypeState::parse, PolicyStatement::TypeState),
map(Type::parse, PolicyStatement::Type),
map(TypeAttr::parse, PolicyStatement::TypeAttr),
map(Attr::parse, PolicyStatement::Attr),
map(TypeTransition::parse, PolicyStatement::TypeTransition),
map(TypeChange::parse, PolicyStatement::TypeChange),
map(GenFsCon::parse, PolicyStatement::GenFsCon),
))
.parse(input)?;
let (input, _) = space0(input)?;
let (input, _) = take_while(|c| c == ';')(input)?;
let (input, _) = space0(input)?;
Ok((input, statement))
}
}
fn parse_sepolicy<'a, 'b>(input: &'b str, strict: bool) -> Result<Vec<PolicyStatement<'a>>>
where
'b: 'a,
{
let mut statements = vec![];
for line in input.split(['\n', ';']) {
let trimmed_line = line.trim();
if trimmed_line.is_empty() || trimmed_line.starts_with('#') {
continue;
}
if let Ok((_, statement)) = PolicyStatement::parse(trimmed_line) {
statements.push(statement);
} else if strict {
bail!("Failed to parse policy statement: {}", line)
}
}
Ok(statements)
}
const SEPOLICY_MAX_LEN: usize = 128;
const CMD_NORMAL_PERM: u32 = 1;
const CMD_XPERM: u32 = 2;
const CMD_TYPE_STATE: u32 = 3;
const CMD_TYPE: u32 = 4;
const CMD_TYPE_ATTR: u32 = 5;
const CMD_ATTR: u32 = 6;
const CMD_TYPE_TRANSITION: u32 = 7;
const CMD_TYPE_CHANGE: u32 = 8;
const CMD_GENFSCON: u32 = 9;
#[derive(Debug, Default)]
enum PolicyObject {
All, // for "*", stand for all objects, and is NULL in ffi
One([u8; SEPOLICY_MAX_LEN]),
#[default]
None,
}
impl TryFrom<&str> for PolicyObject {
type Error = anyhow::Error;
fn try_from(s: &str) -> Result<Self> {
anyhow::ensure!(s.len() <= SEPOLICY_MAX_LEN, "policy object too long");
if s == "*" {
return Ok(PolicyObject::All);
}
let mut buf = [0u8; SEPOLICY_MAX_LEN];
buf[..s.len()].copy_from_slice(s.as_bytes());
Ok(PolicyObject::One(buf))
}
}
/// atomic statement, such as: allow domain1 domain2:file1 read;
/// normal statement would be expanded to atomic statement, for example:
/// allow domain1 domain2:file1 { read write }; would be expanded to two atomic statement
/// allow domain1 domain2:file1 read;allow domain1 domain2:file1 write;
#[allow(clippy::too_many_arguments)]
#[derive(Debug, new)]
struct AtomicStatement {
cmd: u32,
subcmd: u32,
sepol1: PolicyObject,
sepol2: PolicyObject,
sepol3: PolicyObject,
sepol4: PolicyObject,
sepol5: PolicyObject,
sepol6: PolicyObject,
sepol7: PolicyObject,
}
impl<'a> TryFrom<&'a NormalPerm<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a NormalPerm<'a>) -> Result<Self> {
let mut result = vec![];
let subcmd = match perm.op {
"allow" => 1,
"deny" => 2,
"auditallow" => 3,
"dontaudit" => 4,
_ => 0,
};
for &s in &perm.source {
for &t in &perm.target {
for &c in &perm.class {
for &p in &perm.perm {
result.push(AtomicStatement {
cmd: CMD_NORMAL_PERM,
subcmd,
sepol1: s.try_into()?,
sepol2: t.try_into()?,
sepol3: c.try_into()?,
sepol4: p.try_into()?,
sepol5: PolicyObject::None,
sepol6: PolicyObject::None,
sepol7: PolicyObject::None,
});
}
}
}
}
Ok(result)
}
}
impl<'a> TryFrom<&'a XPerm<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a XPerm<'a>) -> Result<Self> {
let mut result = vec![];
let subcmd = match perm.op {
"allowxperm" => 1,
"auditallowxperm" => 2,
"dontauditxperm" => 3,
_ => 0,
};
for &s in &perm.source {
for &t in &perm.target {
for &c in &perm.class {
result.push(AtomicStatement {
cmd: CMD_XPERM,
subcmd,
sepol1: s.try_into()?,
sepol2: t.try_into()?,
sepol3: c.try_into()?,
sepol4: perm.operation.try_into()?,
sepol5: perm.perm_set.try_into()?,
sepol6: PolicyObject::None,
sepol7: PolicyObject::None,
});
}
}
}
Ok(result)
}
}
impl<'a> TryFrom<&'a TypeState<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a TypeState<'a>) -> Result<Self> {
let mut result = vec![];
let subcmd = match perm.op {
"permissive" => 1,
"enforcing" => 2,
_ => 0,
};
for &t in &perm.stype {
result.push(AtomicStatement {
cmd: CMD_TYPE_STATE,
subcmd,
sepol1: t.try_into()?,
sepol2: PolicyObject::None,
sepol3: PolicyObject::None,
sepol4: PolicyObject::None,
sepol5: PolicyObject::None,
sepol6: PolicyObject::None,
sepol7: PolicyObject::None,
});
}
Ok(result)
}
}
impl<'a> TryFrom<&'a Type<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a Type<'a>) -> Result<Self> {
let mut result = vec![];
for &attr in &perm.attrs {
result.push(AtomicStatement {
cmd: CMD_TYPE,
subcmd: 0,
sepol1: perm.name.try_into()?,
sepol2: attr.try_into()?,
sepol3: PolicyObject::None,
sepol4: PolicyObject::None,
sepol5: PolicyObject::None,
sepol6: PolicyObject::None,
sepol7: PolicyObject::None,
});
}
Ok(result)
}
}
impl<'a> TryFrom<&'a TypeAttr<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a TypeAttr<'a>) -> Result<Self> {
let mut result = vec![];
for &t in &perm.stype {
for &attr in &perm.sattr {
result.push(AtomicStatement {
cmd: CMD_TYPE_ATTR,
subcmd: 0,
sepol1: t.try_into()?,
sepol2: attr.try_into()?,
sepol3: PolicyObject::None,
sepol4: PolicyObject::None,
sepol5: PolicyObject::None,
sepol6: PolicyObject::None,
sepol7: PolicyObject::None,
});
}
}
Ok(result)
}
}
impl<'a> TryFrom<&'a Attr<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a Attr<'a>) -> Result<Self> {
let result = vec![AtomicStatement {
cmd: CMD_ATTR,
subcmd: 0,
sepol1: perm.name.try_into()?,
sepol2: PolicyObject::None,
sepol3: PolicyObject::None,
sepol4: PolicyObject::None,
sepol5: PolicyObject::None,
sepol6: PolicyObject::None,
sepol7: PolicyObject::None,
}];
Ok(result)
}
}
impl<'a> TryFrom<&'a TypeTransition<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a TypeTransition<'a>) -> Result<Self> {
let mut result = vec![];
let obj = match perm.object_name {
Some(obj) => obj.try_into()?,
None => PolicyObject::None,
};
result.push(AtomicStatement {
cmd: CMD_TYPE_TRANSITION,
subcmd: 0,
sepol1: perm.source.try_into()?,
sepol2: perm.target.try_into()?,
sepol3: perm.class.try_into()?,
sepol4: perm.default_type.try_into()?,
sepol5: obj,
sepol6: PolicyObject::None,
sepol7: PolicyObject::None,
});
Ok(result)
}
}
impl<'a> TryFrom<&'a TypeChange<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a TypeChange<'a>) -> Result<Self> {
let mut result = vec![];
let subcmd = match perm.op {
"type_change" => 1,
"type_member" => 2,
_ => 0,
};
result.push(AtomicStatement {
cmd: CMD_TYPE_CHANGE,
subcmd,
sepol1: perm.source.try_into()?,
sepol2: perm.target.try_into()?,
sepol3: perm.class.try_into()?,
sepol4: perm.default_type.try_into()?,
sepol5: PolicyObject::None,
sepol6: PolicyObject::None,
sepol7: PolicyObject::None,
});
Ok(result)
}
}
impl<'a> TryFrom<&'a GenFsCon<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a GenFsCon<'a>) -> Result<Self> {
let result = vec![AtomicStatement {
cmd: CMD_GENFSCON,
subcmd: 0,
sepol1: perm.fs_name.try_into()?,
sepol2: perm.partial_path.try_into()?,
sepol3: perm.fs_context.try_into()?,
sepol4: PolicyObject::None,
sepol5: PolicyObject::None,
sepol6: PolicyObject::None,
sepol7: PolicyObject::None,
}];
Ok(result)
}
}
impl<'a> TryFrom<&'a PolicyStatement<'a>> for Vec<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(value: &'a PolicyStatement) -> Result<Self> {
match value {
PolicyStatement::NormalPerm(perm) => perm.try_into(),
PolicyStatement::XPerm(perm) => perm.try_into(),
PolicyStatement::TypeState(perm) => perm.try_into(),
PolicyStatement::Type(perm) => perm.try_into(),
PolicyStatement::TypeAttr(perm) => perm.try_into(),
PolicyStatement::Attr(perm) => perm.try_into(),
PolicyStatement::TypeTransition(perm) => perm.try_into(),
PolicyStatement::TypeChange(perm) => perm.try_into(),
PolicyStatement::GenFsCon(perm) => perm.try_into(),
}
}
}
////////////////////////////////////////////////////////////////
/// for C FFI to call kernel interface
///////////////////////////////////////////////////////////////
#[derive(Debug)]
#[repr(C)]
struct FfiPolicy {
cmd: u32,
subcmd: u32,
sepol1: *const ffi::c_char,
sepol2: *const ffi::c_char,
sepol3: *const ffi::c_char,
sepol4: *const ffi::c_char,
sepol5: *const ffi::c_char,
sepol6: *const ffi::c_char,
sepol7: *const ffi::c_char,
}
fn to_c_ptr(pol: &PolicyObject) -> *const ffi::c_char {
match pol {
PolicyObject::None | PolicyObject::All => std::ptr::null(),
PolicyObject::One(s) => s.as_ptr().cast::<ffi::c_char>(),
}
}
impl From<AtomicStatement> for FfiPolicy {
fn from(policy: AtomicStatement) -> FfiPolicy {
FfiPolicy {
cmd: policy.cmd,
subcmd: policy.subcmd,
sepol1: to_c_ptr(&policy.sepol1),
sepol2: to_c_ptr(&policy.sepol2),
sepol3: to_c_ptr(&policy.sepol3),
sepol4: to_c_ptr(&policy.sepol4),
sepol5: to_c_ptr(&policy.sepol5),
sepol6: to_c_ptr(&policy.sepol6),
sepol7: to_c_ptr(&policy.sepol7),
}
}
}
pub fn check_rule(policy: &str) -> Result<()> {
let path = Path::new(policy);
let policy = if path.exists() {
std::fs::read_to_string(path)?
} else {
policy.to_string()
};
parse_sepolicy(policy.trim(), true)?;
Ok(())
}
================================================
FILE: apd/src/supercall.rs
================================================
use std::{
ffi::{CStr, CString},
fmt::Write,
fs::File,
io::{self, Read},
process,
process::exit,
ptr,
sync::{Arc, Mutex},
};
use errno::errno;
use libc::{EINVAL, c_int, c_long, c_void, execv, fork, pid_t, setenv, syscall, uid_t, wait};
use log::{error, info, warn};
use crate::package::{read_ap_package_config, synchronize_package_uid};
const MAJOR: c_long = 0;
const MINOR: c_long = 11;
const PATCH: c_long = 1;
const KSTORAGE_EXCLUDE_LIST_GROUP: i32 = 1;
const __NR_SUPERCALL: c_long = 45;
const SUPERCALL_KLOG: c_long = 0x1004;
const SUPERCALL_KERNELPATCH_VER: c_long = 0x1008;
const SUPERCALL_KERNEL_VER: c_long = 0x1009;
const SUPERCALL_SU: c_long = 0x1010;
const SUPERCALL_KSTORAGE_WRITE: c_long = 0x1041;
const SUPERCALL_SU_GRANT_UID: c_long = 0x1100;
const SUPERCALL_SU_REVOKE_UID: c_long = 0x1101;
const SUPERCALL_SU_NUMS: c_long = 0x1102;
const SUPERCALL_SU_LIST: c_long = 0x1103;
const SUPERCALL_SU_RESET_PATH: c_long = 0x1111;
const SUPERCALL_SU_GET_SAFEMODE: c_long = 0x1112;
const SUPERCALL_SCONTEXT_LEN: usize = 0x60;
#[repr(C)]
struct SuProfile {
uid: i32,
to_uid: i32,
scontext: [u8; SUPERCALL_SCONTEXT_LEN],
}
fn ver_and_cmd(cmd: c_long) -> c_long {
let version_code: u32 = ((MAJOR << 16) + (MINOR << 8) + PATCH).try_into().unwrap();
((version_code as c_long) << 32) | (0x1158 << 16) | (cmd & 0xFFFF)
}
fn sc_su_revoke_uid(key: &CStr, uid: uid_t) -> c_long {
if key.to_bytes().is_empty() {
return (-EINVAL).into();
}
unsafe {
syscall(
__NR_SUPERCALL,
key.as_ptr(),
ver_and_cmd(SUPERCALL_SU_REVOKE_UID),
uid,
) as c_long
}
}
fn sc_su_grant_uid(key: &CStr, profile: &SuProfile) -> c_long {
if key.to_bytes().is_empty() {
return (-EINVAL).into();
}
unsafe {
syscall(
__NR_SUPERCALL,
key.as_ptr(),
ver_and_cmd(SUPERCALL_SU_GRANT_UID),
profile,
) as c_long
}
}
fn sc_kstorage_write(
key: &CStr,
gid: i32,
did: i64,
data: *mut c_void,
offset: i32,
dlen: i32,
) -> c_long {
if key.to_bytes().is_empty() {
return (-EINVAL).into();
}
unsafe {
syscall(
__NR_SUPERCALL,
key.as_ptr(),
ver_and_cmd(SUPERCALL_KSTORAGE_WRITE),
gid as c_long,
did as c_long,
data,
(((offset as i64) << 32) | (dlen as i64)) as c_long,
) as c_long
}
}
fn sc_set_ap_mod_exclude(key: &CStr, uid: i64, exclude: i32) -> c_long {
sc_kstorage_write(
key,
KSTORAGE_EXCLUDE_LIST_GROUP,
uid,
&exclude as *const i32 as *mut c_void,
0,
size_of::<i32>() as i32,
)
}
pub fn sc_su_get_safemode(key: &CStr) -> c_long {
if key.to_bytes().is_empty() {
warn!("[sc_su_get_safemode] null superkey, tell apd we are not in safemode!");
return 0;
}
let key_ptr = key.as_ptr();
if key_ptr.is_null() {
warn!("[sc_su_get_safemode] superkey pointer is null!");
return 0;
}
unsafe {
syscall(
__NR_SUPERCALL,
key_ptr,
ver_and_cmd(SUPERCALL_SU_GET_SAFEMODE),
) as c_long
}
}
fn sc_su(key: &CStr, profile: &SuProfile) -> c_long {
if key.to_bytes().is_empty() {
return (-EINVAL).into();
}
unsafe {
syscall(
__NR_SUPERCALL,
key.as_ptr(),
ver_and_cmd(SUPERCALL_SU),
profile,
) as c_long
}
}
fn sc_su_reset_path(key: &CStr, path: &CStr) -> c_long {
if key.to_bytes().is_empty() || path.to_bytes().is_empty() {
return (-EINVAL).into();
}
unsafe {
syscall(
__NR_SUPERCALL,
key.as_ptr(),
ver_and_cmd(SUPERCALL_SU_RESET_PATH),
path.as_ptr(),
) as c_long
}
}
fn sc_kp_ver(key: &CStr) -> Result<u32, i32> {
if key.to_bytes().is_empty() {
return Err(-EINVAL);
}
let ret = unsafe {
syscall(
__NR_SUPERCALL,
key.as_ptr(),
ver_and_cmd(SUPERCALL_KERNELPATCH_VER),
)
};
Ok(ret as u32)
}
fn sc_k_ver(key: &CStr) -> Result<u32, i32> {
if key.to_bytes().is_empty() {
return Err(-EINVAL);
}
let ret = unsafe {
syscall(
__NR_SUPERCALL,
key.as_ptr(),
ver_and_cmd(SUPERCALL_KERNEL_VER),
)
};
Ok(ret as u32)
}
fn sc_klog(key: &CStr, msg: &CStr) -> c_long {
if key.to_bytes().is_empty() || msg.to_bytes().is_empty() {
return (-EINVAL).into();
}
unsafe {
syscall(
__NR_SUPERCALL,
key.as_ptr(),
ver_and_cmd(SUPERCALL_KLOG),
msg.as_ptr(),
) as c_long
}
}
fn sc_su_uid_nums(key: &CStr) -> c_long {
if key.to_bytes().is_empty() {
return (-EINVAL).into();
}
unsafe { syscall(__NR_SUPERCALL, key.as_ptr(), ver_and_cmd(SUPERCALL_SU_NUMS)) as c_long }
}
fn sc_su_allow_uids(key: &CStr, buf: &mut [uid_t]) -> c_long {
if key.to_bytes().is_empty() {
return (-EINVAL).into();
}
if buf.is_empty() {
return (-EINVAL).into();
}
unsafe {
syscall(
__NR_SUPERCALL,
key.as_ptr(),
ver_and_cmd(SUPERCALL_SU_LIST),
buf.as_mut_ptr(),
buf.len() as i32,
) as c_long
}
}
fn read_file_to_string(path: &str) -> io::Result<String> {
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn convert_string_to_u8_array(s: &str) -> [u8; SUPERCALL_SCONTEXT_LEN] {
let mut u8_array = [0u8; SUPERCALL_SCONTEXT_LEN];
let bytes = s.as_bytes();
let len = usize::min(SUPERCALL_SCONTEXT_LEN, bytes.len());
u8_array[..len].copy_from_slice(&bytes[..len]);
u8_array
}
fn convert_superkey(s: &Option<String>) -> Option<CString> {
s.as_ref().and_then(|s| CString::new(s.clone()).ok())
}
pub fn refresh_ap_package_list(skey: &CStr, mutex: &Arc<Mutex<()>>) {
let _lock = mutex.lock().unwrap();
let num = sc_su_uid_nums(skey);
if num < 0 {
error!("[refresh_su_list] Error getting number of UIDs: {}", num);
return;
}
let num = num as usize;
let mut uids = vec![0 as uid_t; num];
let n = sc_su_allow_uids(skey, &mut uids);
if n < 0 {
error!("[refresh_su_list] Error getting su list");
return;
}
for uid in &uids {
if *uid == 0 || *uid == 2000 {
warn!(
"[refresh_ap_package_list] Skip revoking critical uid: {}",
uid
);
continue;
}
info!(
"[refresh_ap_package_list] Revoking {} root permission...",
uid
);
let rc = sc_su_revoke_uid(skey, *uid);
if rc != 0 {
error!("[refresh_ap_package_list] Error revoking UID: {}", rc);
}
}
if let Err(e) = synchronize_package_uid() {
error!("Failed to synchronize package UIDs: {}", e);
}
let package_configs = read_ap_package_config();
for config in package_configs {
if config.allow == 1 && config.exclude == 0 {
let profile = SuProfile {
uid: config.uid,
to_uid: config.to_uid,
scontext: convert_string_to_u8_array(&config.sctx),
};
let result = sc_su_grant_uid(skey, &profile);
info!(
"[refresh_ap_package_list] Loading {}: result = {}",
config.pkg, result
);
}
if config.allow == 0 && config.exclude == 1 {
let result = sc_set_ap_mod_exclude(skey, config.uid as i64, 1);
info!(
"[refresh_ap_package_list] Loading exclude {}: result = {}",
config.pkg, result
);
}
}
}
pub fn privilege_apd_profile(superkey: &Option<String>) {
let key = convert_superkey(superkey);
let all_allow_ctx = "u:r:magisk:s0";
let profile = SuProfile {
uid: process::id().try_into().expect("PID conversion failed"),
to_uid: 0,
scontext: convert_string_to_u8_array(all_allow_ctx),
};
if let Some(ref key) = key {
let result = sc_su(key, &profile);
info!("[privilege_apd_profile] result = {}", result);
}
}
pub fn init_load_package_uid_config(superkey: &Option<String>) {
let package_configs = read_ap_package_config();
let key = convert_superkey(superkey);
for config in package_configs {
if config.allow == 1 && config.exclude == 0 {
match key {
Some(ref key) => {
let profile = SuProfile {
uid: config.uid,
to_uid: config.to_uid,
scontext: convert_string_to_u8_array(&config.sctx),
};
let result = sc_su_grant_uid(key, &profile);
info!("Processed {}: result = {}", config.pkg, result);
}
_ => {
warn!("Superkey is None, skipping config: {}", config.pkg);
}
}
}
if config.allow == 0 && config.exclude == 1 {
match key {
Some(ref key) => {
let result = sc_set_ap_mod_exclude(key, config.uid as i64, 1);
info!("Processed exclude {}: result = {}", config.pkg, result);
}
_ => {
warn!("Superkey is None, skipping config: {}", config.pkg);
}
}
}
}
}
pub fn init_load_su_path(superkey: &Option<String>) {
let su_path_file = "/data/adb/ap/su_path";
match read_file_to_string(su_path_file) {
Ok(su_path) => {
let superkey_cstr = convert_superkey(superkey);
match superkey_cstr {
Some(superkey_cstr) => match CString::new(su_path.trim()) {
Ok(su_path_cstr) => {
let result = sc_su_reset_path(&superkey_cstr, &su_path_cstr);
if result == 0 {
info!("suPath load successfully");
} else {
warn!("Failed to load su path, error code: {}", result);
}
}
Err(e) => {
warn!("Failed to convert su_path: {}", e);
}
},
_ => {
warn!("Superkey is None, skipping...");
}
}
}
Err(e) => {
warn!("Failed to read su_path file: {}", e);
}
}
}
fn set_env_var(key: &str, value: &str) {
let key_c = CString::new(key).expect("CString::new failed");
let value_c = CString::new(value).expect("CString::new failed");
unsafe {
setenv(key_c.as_ptr(), value_c.as_ptr(), 1);
}
}
fn log_kernel(key: &CStr, _fmt: &str, args: std::fmt::Arguments) -> c_long {
let mut buf = String::with_capacity(1024);
write!(&mut buf, "{}", args).expect("Error formatting string");
let c_buf = CString::new(buf).expect("CString::new failed");
sc_klog(key, &c_buf)
}
#[macro_export]
macro_rules! log_kernel {
($key:expr_2021, $fmt:expr_2021, $($arg:tt)*) => (
log_kernel($key, $fmt, std::format_args!($fmt, $($arg)*))
)
}
pub fn fork_for_result(exec: &str, argv: &[&str], key: &Option<String>) {
let mut cmd = String::new();
for arg in argv {
cmd.push_str(arg);
cmd.push(' ');
}
let superkey_cstr = convert_superkey(key);
match superkey_cstr {
Some(superkey_cstr) => {
unsafe {
let pid: pid_t = fork();
if pid < 0 {
log_kernel!(
&superkey_cstr,
"{} fork {} error: {}\n",
libc::getpid(),
exec,
-1
);
} else if pid == 0 {
set_env_var("KERNELPATCH", "true");
let kpver = format!("{:x}", sc_kp_ver(&superkey_cstr).unwrap_or(0));
set_env_var("KERNELPATCH_VERSION", kpver.as_str());
let kver = format!("{:x}", sc_k_ver(&superkey_cstr).unwrap_or(0));
set_env_var("KERNEL_VERSION", kver.as_str());
let c_exec = CString::new(exec).expect("CString::new failed");
let c_argv: Vec<CString> =
argv.iter().map(|&arg| CString::new(arg).unwrap()).collect();
let mut c_argv_ptrs: Vec<*const libc::c_char> =
c_argv.iter().map(|arg| arg.as_ptr()).collect();
c_argv_ptrs.push(ptr::null());
execv(c_exec.as_ptr(), c_argv_ptrs.as_ptr());
log_kernel!(
&superkey_cstr,
"{} exec {} error: {}\n",
libc::getpid(),
cmd,
CStr::from_ptr(libc::strerror(errno().0))
.to_string_lossy()
.into_owned()
);
exit(1); // execv only returns on error
} else {
let mut status: c_int = 0;
wait(&mut status);
log_kernel!(
&superkey_cstr,
"{} wait {} status: 0x{}\n",
libc::getpid(),
cmd,
status
);
}
}
}
_ => {
warn!("[fork_for_result] SuperKey convert failed!");
}
}
}
================================================
FILE: apd/src/utils.rs
================================================
#[allow(unused_imports)]
use std::fs::{Permissions, set_permissions};
#[cfg(unix)]
use std::os::unix::prelude::PermissionsExt;
use std::{
ffi::CString,
fs::{File, OpenOptions, create_dir_all, metadata},
io::{ErrorKind::AlreadyExists, Write},
path::Path,
process::{Command, Stdio},
};
use anyhow::{Context, Error, Ok, Result, bail};
use log::{info, warn};
use crate::{defs, supercall::sc_su_get_safemode};
pub fn ensure_file_exists<T: AsRef<Path>>(file: T) -> Result<()> {
match File::options().write(true).create_new(true).open(&file) {
Result::Ok(_) => Ok(()),
Err(err) => {
if err.kind() == AlreadyExists && file.as_ref().is_file() {
Ok(())
} else {
Err(Error::from(err))
.with_context(|| format!("{} is not a regular file", file.as_ref().display()))
}
}
}
}
pub fn ensure_dir_exists<T: AsRef<Path>>(dir: T) -> Result<()> {
let result = create_dir_all(&dir).map_err(Error::from);
if dir.as_ref().is_dir() {
result
} else if result.is_ok() {
bail!("{} is not a regular directory", dir.as_ref().display())
} else {
result
}
}
// todo: ensure
pub fn ensure_binary<T: AsRef<Path>>(path: T) -> Result<()> {
set_permissions(&path, Permissions::from_mode(0o755))?;
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn getprop(prop: &str) -> Option<String> {
android_properties::getprop(prop).value()
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn getprop(_prop: &str) -> Option<String> {
unimplemented!()
}
pub fn run_command(
command: &str,
args: &[&str],
stdout: Option<Stdio>,
) -> Result<std::process::Child> {
let mut command_builder = Command::new(command);
command_builder.args(args);
if let Some(out) = stdout {
command_builder.stdout(out);
}
let child = command_builder.spawn()?;
Ok(child)
}
pub fn is_safe_mode(superkey: Option<String>) -> bool {
let safemode = getprop("persist.sys.safemode")
.filter(|prop| prop == "1")
.is_some()
|| getprop("ro.sys.safemode")
.filter(|prop| prop == "1")
.is_some();
info!("safemode: {}", safemode);
if safemode {
return true;
}
let safemode = superkey
.as_ref()
.and_then(|key_str| CString::new(key_str.as_str()).ok())
.map_or_else(
|| {
warn!("[is_safe_mode] No valid superkey provided, assuming safemode as false.");
false
},
|cstr| sc_su_get_safemode(&cstr) == 1,
);
info!("kernel_safemode: {}", safemode);
safemode
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn switch_mnt_ns(pid: i32) -> Result<()> {
use std::os::fd::AsRawFd;
use anyhow::ensure;
let path = format!("/proc/{pid}/ns/mnt");
let fd = File::open(path)?;
let current_dir = std::env::current_dir();
let ret = unsafe { libc::setns(fd.as_raw_fd(), libc::CLONE_NEWNS) };
if let Result::Ok(current_dir) = current_dir {
let _ = std::env::set_current_dir(current_dir);
}
ensure!(ret == 0, "switch mnt ns failed");
Ok(())
}
fn switch_cgroup(grp: &str, pid: u32) {
let path = Path::new(grp).join("cgroup.procs");
if !path.exists() {
return;
}
let fp = OpenOptions::new().append(true).open(path);
if let Result::Ok(mut fp) = fp {
let _ = write!(fp, "{pid}");
}
}
pub fn switch_cgroups() {
let pid = std::process::id();
switch_cgroup("/acct", pid);
switch_cgroup("/dev/cg2_bpf", pid);
switch_cgroup("/sys/fs/cgroup", pid);
if getprop("ro.config.per_app_memcg")
.filter(|prop| prop == "false")
.is_none()
{
switch_cgroup("/dev/memcg/apps", pid);
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn umask(mask: u32) {
unsafe { libc::umask(mask) };
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn umask(_mask: u32) {
unimplemented!("umask is not supported on this platform")
}
pub fn has_magisk() -> bool {
which::which("magisk").is_ok()
}
pub fn get_tmp_path() -> &'static str {
if metadata(defs::TEMP_DIR_LEGACY).is_ok() {
return defs::TEMP_DIR_LEGACY;
}
if metadata(defs::TEMP_DIR).is_ok() {
return defs::TEMP_DIR;
}
""
}
================================================
FILE: app/.gitignore
================================================
/build
/release/
================================================
FILE: app/build.gradle.kts
================================================
@file:Suppress("UnstableApiUsage")
import com.android.build.gradle.tasks.PackageAndroidArtifact
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.net.URI
plugins {
alias(libs.plugins.agp.app)
alias(libs.plugins.kotlin.compose.compiler)
alias(libs.plugins.ksp)
alias(libs.plugins.lsplugin.apksign)
alias(libs.plugins.lsplugin.resopt)
id("kotlin-parcelize")
}
val androidCompileSdkVersion: Int by rootProject.extra
val androidCompileNdkVersion: String by rootProject.extra
val androidBuildToolsVersion: String by rootProject.extra
val androidMinSdkVersion: Int by rootProject.extra
val androidTargetSdkVersion: Int by rootProject.extra
val androidSourceCompatibility: JavaVersion by rootProject.extra
val androidTargetCompatibility: JavaVersion by rootProject.extra
val managerVersionCode: Int by rootProject.extra
val managerVersionName: Str
gitextract_f37ualu1/ ├── .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.toml │ ├── build.rs │ └── src/ │ ├── apd.rs │ ├── assets.rs │ ├── banner │ ├── cli.rs │ ├── defs.rs │ ├── event.rs │ ├── installer.sh │ ├── installer_bind.sh │ ├── lua.rs │ ├── main.rs │ ├── metamodule.rs │ ├── module.rs │ ├── package.rs │ ├── pty.rs │ ├── restorecon.rs │ ├── sepolicy.rs │ ├── supercall.rs │ └── utils.rs ├── app/ │ ├── .gitignore │ ├── build.gradle.kts │ ├── libs/ │ │ └── arm64-v8a/ │ │ └── .gitignore │ ├── 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 │ │ ├── jni_helper.hpp │ │ ├── supercall.h │ │ ├── type_traits.hpp │ │ ├── uapi/ │ │ │ └── scdefs.h │ │ └── version │ ├── 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 │ │ │ │ └── WarningCard.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/ │ │ │ ├── AppIconUtil.kt │ │ │ ├── Insets.kt │ │ │ ├── MimeUtil.java │ │ │ ├── MonetColorsProvider.kt │ │ │ ├── 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 │ │ └── 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 │ │ ├── play_circle.xml │ │ ├── settings.xml │ │ ├── telegram.xml │ │ ├── trash.xml │ │ ├── weblate.xml │ │ └── webui.xml │ ├── mipmap-anydpi/ │ │ └── ic_launcher.xml │ ├── resources.properties │ ├── values/ │ │ ├── arrays.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ ├── values-ab/ │ │ └── strings.xml │ ├── values-apc/ │ │ └── strings.xml │ ├── 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-da/ │ │ └── strings.xml │ ├── values-de/ │ │ └── strings.xml │ ├── values-el/ │ │ └── strings.xml │ ├── values-es/ │ │ └── strings.xml │ ├── values-fa/ │ │ └── strings.xml │ ├── values-fi/ │ │ └── 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-lv/ │ │ └── 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/ │ │ └── strings.xml │ ├── values-pt-rBR/ │ │ └── strings.xml │ ├── values-ro/ │ │ └── strings.xml │ ├── values-ru/ │ │ └── strings.xml │ ├── values-si/ │ │ └── strings.xml │ ├── values-sv/ │ │ └── strings.xml │ ├── values-ta/ │ │ └── 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 │ └── xml/ │ ├── backup_rules.xml │ ├── data_extraction_rules.xml │ ├── file_paths.xml │ └── network_security_config.xml ├── build.gradle.kts ├── docs/ │ ├── BG/ │ │ └── faq_bg.md │ ├── ar/ │ │ └── faq_ar.md │ ├── 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 │ └── short_description.txt ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── scripts/ │ ├── update_binary.sh │ └── update_script.sh └── settings.gradle.kts
SYMBOL INDEX (575 symbols across 27 files)
FILE: apd/build.rs
function get_git_version (line 7) | fn get_git_version() -> Result<(u32, String), std::io::Error> {
function main (line 36) | fn main() {
FILE: apd/src/apd.rs
function print_usage (line 17) | fn print_usage(opts: Options) {
function set_identity (line 22) | fn set_identity(uid: u32, gid: u32) {
function root_shell (line 31) | pub fn root_shell() -> Result<()> {
function root_shell (line 36) | pub fn root_shell() -> Result<()> {
function add_path_to_env (line 221) | fn add_path_to_env(path: &str) -> Result<()> {
FILE: apd/src/assets.rs
constant RESETPROP_PATH (line 6) | pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop");
constant BUSYBOX_PATH (line 7) | pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox");
constant MAGISKPOLICY_PATH (line 8) | pub const MAGISKPOLICY_PATH: &str = concatcp!(BINARY_DIR, "magiskpolicy");
function ensure_binaries (line 10) | pub fn ensure_binaries() -> Result<()> {
FILE: apd/src/cli.rs
type Args (line 12) | struct Args {
type Commands (line 25) | enum Commands {
type Module (line 52) | enum Module {
type Sepolicy (line 94) | enum Sepolicy {
function run (line 102) | pub fn run() -> Result<()> {
FILE: apd/src/defs.rs
constant ADB_DIR (line 3) | pub const ADB_DIR: &str = "/data/adb/";
constant WORKING_DIR (line 4) | pub const WORKING_DIR: &str = concatcp!(ADB_DIR, "ap/");
constant BINARY_DIR (line 5) | pub const BINARY_DIR: &str = concatcp!(WORKING_DIR, "bin/");
constant APATCH_LOG_FOLDER (line 6) | pub const APATCH_LOG_FOLDER: &str = concatcp!(WORKING_DIR, "log/");
constant AP_RC_PATH (line 8) | pub const AP_RC_PATH: &str = concatcp!(WORKING_DIR, ".aprc");
constant GLOBAL_NAMESPACE_FILE (line 9) | pub const GLOBAL_NAMESPACE_FILE: &str = concatcp!(ADB_DIR, ".global_name...
constant DAEMON_PATH (line 10) | pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "apd");
constant MODULE_DIR (line 12) | pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/");
constant MODULE_UPDATE_DIR (line 15) | pub const MODULE_UPDATE_DIR: &str = concatcp!(ADB_DIR, "modules_update/");
constant TEMP_DIR (line 17) | pub const TEMP_DIR: &str = "/debug_ramdisk";
constant TEMP_DIR_LEGACY (line 18) | pub const TEMP_DIR_LEGACY: &str = "/sbin";
constant MODULE_WEB_DIR (line 20) | pub const MODULE_WEB_DIR: &str = "webroot";
constant MODULE_ACTION_SH (line 21) | pub const MODULE_ACTION_SH: &str = "action.sh";
constant DISABLE_FILE_NAME (line 22) | pub const DISABLE_FILE_NAME: &str = "disable";
constant UPDATE_FILE_NAME (line 23) | pub const UPDATE_FILE_NAME: &str = "update";
constant REMOVE_FILE_NAME (line 24) | pub const REMOVE_FILE_NAME: &str = "remove";
constant METAMODULE_MOUNT_SCRIPT (line 27) | pub const METAMODULE_MOUNT_SCRIPT: &str = "metamount.sh";
constant METAMODULE_METAINSTALL_SCRIPT (line 28) | pub const METAMODULE_METAINSTALL_SCRIPT: &str = "metainstall.sh";
constant METAMODULE_METAUNINSTALL_SCRIPT (line 29) | pub const METAMODULE_METAUNINSTALL_SCRIPT: &str = "metauninstall.sh";
constant METAMODULE_DIR (line 30) | pub const METAMODULE_DIR: &str = concatcp!(ADB_DIR, "metamodule/");
constant PTS_NAME (line 32) | pub const PTS_NAME: &str = "pts";
constant VERSION_CODE (line 34) | pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/V...
constant VERSION_NAME (line 35) | pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/V...
FILE: apd/src/event.rs
function on_post_data_fs (line 30) | pub fn on_post_data_fs(superkey: Option<String>) -> Result<()> {
function run_stage (line 192) | fn run_stage(stage: &str, superkey: Option<String>, block: bool) {
function on_services (line 224) | pub fn on_services(superkey: Option<String>) -> Result<()> {
function run_uid_monitor (line 231) | fn run_uid_monitor() {
function on_boot_completed (line 253) | pub fn on_boot_completed(superkey: Option<String>) -> Result<()> {
function start_uid_listener (line 262) | pub fn start_uid_listener() -> Result<()> {
FILE: apd/src/lua.rs
function save_text (line 8) | pub fn save_text<P: AsRef<Path>>(filename: P, content: &str) -> std::io:...
function load_text (line 15) | pub fn load_text<P: AsRef<Path>>(filename: P) -> std::io::Result<String> {
function load_all_lua_modules (line 21) | pub fn load_all_lua_modules(lua: &Lua) -> LuaResult<()> {
function info_lua (line 81) | pub fn info_lua(lua: &Lua) -> LuaResult<Function> {
function warn_lua (line 88) | pub fn warn_lua(lua: &Lua) -> LuaResult<Function> {
function install_module_lua (line 95) | pub fn install_module_lua(lua: &Lua) -> LuaResult<Function> {
function save_text_lua (line 101) | pub fn save_text_lua(lua: &Lua) -> LuaResult<Function> {
function read_text_lua (line 108) | pub fn read_text_lua(lua: &Lua) -> LuaResult<Function> {
function exec_stage_lua (line 119) | pub fn exec_stage_lua(stage: &str, wait: bool, superkey: &str) -> Result...
function run_lua (line 125) | pub fn run_lua(id: &str, function: &str, on_each_module: bool, _wait: bo...
FILE: apd/src/main.rs
function main (line 16) | fn main() -> anyhow::Result<()> {
FILE: apd/src/metamodule.rs
function is_metamodule (line 19) | pub fn is_metamodule(props: &HashMap<String, String>) -> bool {
function get_metamodule_path (line 28) | pub fn get_metamodule_path() -> Option<PathBuf> {
function has_metamodule (line 70) | pub fn has_metamodule() -> bool {
function check_install_safety (line 78) | pub fn check_install_safety() -> Result<(), bool> {
function ensure_symlink (line 115) | pub fn ensure_symlink<P>(module_path: P) -> Result<()>
function remove_symlink (line 151) | pub fn remove_symlink() -> Result<()> {
function get_install_script (line 165) | pub fn get_install_script(
function check_metamodule_script (line 203) | fn check_metamodule_script(script_name: &str) -> Option<PathBuf> {
function exec_metauninstall_script (line 223) | pub fn exec_metauninstall_script(module_id: &str) -> Result<()> {
function exec_mount_script (line 249) | pub fn exec_mount_script(module_dir: &str) -> Result<()> {
function exec_stage_script (line 273) | pub fn exec_stage_script(stage: &str, block: bool) -> Result<()> {
FILE: apd/src/module.rs
constant INSTALLER_CONTENT (line 29) | const INSTALLER_CONTENT: &str = include_str!("./installer.sh");
constant INSTALL_MODULE_SCRIPT (line 30) | const INSTALL_MODULE_SCRIPT: &str = concatcp!(
type ModuleType (line 40) | pub enum ModuleType {
function exec_install_script (line 46) | fn exec_install_script(module_file: &str, is_metamodule: bool) -> Result...
function handle_updated_modules (line 64) | pub fn handle_updated_modules() -> Result<()> {
function get_common_script_envs (line 100) | pub fn get_common_script_envs() -> Vec<(&'static str, String)> {
function ensure_boot_completed (line 121) | fn ensure_boot_completed() -> Result<()> {
function mark_update (line 129) | fn mark_update() -> Result<()> {
function mark_module_state (line 133) | fn mark_module_state(module: &str, flag_file: &str, create_or_delete: bo...
function foreach_module (line 144) | pub fn foreach_module(
function foreach_active_module (line 175) | fn foreach_active_module(f: impl FnMut(&Path) -> Result<()>) -> Result<(...
function load_sepolicy_rule (line 179) | pub fn load_sepolicy_rule() -> Result<()> {
function exec_script (line 199) | pub fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
function exec_stage_script (line 239) | pub fn exec_stage_script(stage: &str, block: bool) -> Result<()> {
function exec_common_scripts (line 251) | pub fn exec_common_scripts(dir: &str, wait: bool) -> Result<()> {
function load_system_prop (line 273) | pub fn load_system_prop() -> Result<()> {
function prune_modules (line 295) | pub fn prune_modules() -> Result<()> {
function _install_module (line 350) | fn _install_module(zip: &str) -> Result<()> {
function install_module (line 482) | pub fn install_module(zip: &str) -> Result<()> {
function _uninstall_module (line 487) | pub fn _uninstall_module(id: &str, update_dir: &str) -> Result<()> {
function uninstall_module (line 528) | pub fn uninstall_module(id: &str) -> Result<()> {
function read_module_prop (line 535) | pub fn read_module_prop(module_path: &Path) -> Result<HashMap<String, St...
function run_action (line 556) | pub fn run_action(id: &str) -> Result<()> {
function _change_module_state (line 567) | fn _change_module_state(module_dir: &str, mid: &str, enable: bool) -> Re...
function _enable_module (line 588) | pub fn _enable_module(id: &str, update_dir: &Path) -> Result<()> {
function enable_module (line 597) | pub fn enable_module(id: &str) -> Result<()> {
function _disable_module (line 603) | pub fn _disable_module(id: &str, update_dir: &Path) -> Result<()> {
function disable_module (line 612) | pub fn disable_module(id: &str) -> Result<()> {
function _disable_all_modules (line 619) | pub fn _disable_all_modules(dir: &str) -> Result<()> {
function disable_all_modules (line 631) | pub fn disable_all_modules() -> Result<()> {
function _list_modules (line 642) | fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
function list_modules (line 708) | pub fn list_modules() -> Result<()> {
FILE: apd/src/package.rs
type PackageConfig (line 13) | pub struct PackageConfig {
function read_ap_package_config (line 22) | pub fn read_ap_package_config() -> Vec<PackageConfig> {
function write_ap_package_config (line 57) | pub fn write_ap_package_config(package_configs: &[PackageConfig]) -> io:...
function read_lines (line 105) | fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
function synchronize_package_uid (line 112) | pub fn synchronize_package_uid() -> io::Result<()> {
FILE: apd/src/pty.rs
function get_pty_num (line 32) | fn get_pty_num<F: AsFd>(fd: F) -> Result<u32> {
function watch_sigwinch_async (line 43) | fn watch_sigwinch_async(slave: RawFd) {
function set_stdin_raw (line 70) | fn set_stdin_raw() -> rustix::io::Result<()> {
function restore_stdin (line 81) | fn restore_stdin() -> Result<()> {
function pump (line 91) | fn pump<R: Read, W: Write>(mut from: R, mut to: W) {
function pump_stdin_async (line 113) | fn pump_stdin_async(mut ptmx: File) {
function pump_stdout_blocking (line 122) | fn pump_stdout_blocking(mut ptmx: File) {
function create_transfer (line 129) | fn create_transfer(ptmx: OwnedFd) -> Result<()> {
function prepare_pty (line 161) | pub fn prepare_pty() -> Result<()> {
FILE: apd/src/restorecon.rs
constant SYSTEM_CON (line 12) | pub const SYSTEM_CON: &str = "u:object_r:system_file:s0";
constant ADB_CON (line 13) | pub const ADB_CON: &str = "u:object_r:adb_data_file:s0";
constant UNLABEL_CON (line 14) | pub const UNLABEL_CON: &str = "u:object_r:unlabeled:s0";
constant SELINUX_XATTR (line 16) | const SELINUX_XATTR: &str = "security.selinux";
function lsetfilecon (line 18) | pub fn lsetfilecon<P: AsRef<Path>>(path: P, con: &str) -> Result<()> {
function lgetfilecon (line 30) | pub fn lgetfilecon<P: AsRef<Path>>(path: P) -> Result<String> {
function setsyscon (line 42) | pub fn setsyscon<P: AsRef<Path>>(path: P) -> Result<()> {
function setsyscon (line 47) | pub fn setsyscon<P: AsRef<Path>>(path: P) -> Result<()> {
function lgetfilecon (line 52) | pub fn lgetfilecon<P: AsRef<Path>>(path: P) -> Result<String> {
function restore_syscon (line 56) | pub fn restore_syscon<P: AsRef<Path>>(dir: P) -> Result<()> {
function restore_syscon_if_unlabeled (line 65) | fn restore_syscon_if_unlabeled<P: AsRef<Path>>(dir: P) -> Result<()> {
function restorecon (line 78) | pub fn restorecon() -> Result<()> {
FILE: apd/src/sepolicy.rs
type SeObject (line 13) | type SeObject<'a> = Vec<&'a str>;
function is_sepolicy_char (line 15) | fn is_sepolicy_char(c: char) -> bool {
function parse_single_word (line 19) | fn parse_single_word(input: &str) -> IResult<&str, &str> {
function parse_bracket_objs (line 23) | fn parse_bracket_objs(input: &str) -> IResult<&str, SeObject<'_>> {
function parse_single_obj (line 33) | fn parse_single_obj(input: &str) -> IResult<&str, SeObject<'_>> {
function parse_star (line 38) | fn parse_star(input: &str) -> IResult<&str, SeObject<'_>> {
function parse_seobj (line 46) | fn parse_seobj(input: &str) -> IResult<&str, SeObject<'_>> {
function parse_seobj_no_star (line 51) | fn parse_seobj_no_star(input: &str) -> IResult<&str, SeObject<'_>> {
type SeObjectParser (line 56) | trait SeObjectParser<'a> {
method parse (line 57) | fn parse(input: &'a str) -> IResult<&'a str, Self>
type NormalPerm (line 63) | struct NormalPerm<'a> {
type XPerm (line 72) | struct XPerm<'a> {
type TypeState (line 82) | struct TypeState<'a> {
type TypeAttr (line 88) | struct TypeAttr<'a> {
type Type (line 94) | struct Type<'a> {
type Attr (line 100) | struct Attr<'a> {
type TypeTransition (line 105) | struct TypeTransition<'a> {
type TypeChange (line 114) | struct TypeChange<'a> {
type GenFsCon (line 123) | struct GenFsCon<'a> {
type PolicyStatement (line 130) | enum PolicyStatement<'a> {
function parse (line 167) | fn parse(input: &'a str) -> IResult<&'a str, Self> {
function parse (line 189) | fn parse(input: &'a str) -> IResult<&'a str, Self> {
function parse (line 216) | fn parse(input: &'a str) -> IResult<&'a str, Self> {
function parse (line 227) | fn parse(input: &'a str) -> IResult<&'a str, Self> {
function parse (line 244) | fn parse(input: &'a str) -> IResult<&'a str, Self> {
function parse (line 256) | fn parse(input: &'a str) -> IResult<&'a str, Self> {
function parse (line 266) | fn parse(input: &'a str) -> IResult<&'a str, Self> {
function parse (line 295) | fn parse(input: &'a str) -> IResult<&'a str, Self> {
function parse (line 311) | fn parse(input: &'a str) -> IResult<&'a str, Self>
function parse (line 327) | fn parse(input: &'a str) -> IResult<&'a str, Self> {
function parse_sepolicy (line 348) | fn parse_sepolicy<'a, 'b>(input: &'b str, strict: bool) -> Result<Vec<Po...
constant SEPOLICY_MAX_LEN (line 368) | const SEPOLICY_MAX_LEN: usize = 128;
constant CMD_NORMAL_PERM (line 370) | const CMD_NORMAL_PERM: u32 = 1;
constant CMD_XPERM (line 371) | const CMD_XPERM: u32 = 2;
constant CMD_TYPE_STATE (line 372) | const CMD_TYPE_STATE: u32 = 3;
constant CMD_TYPE (line 373) | const CMD_TYPE: u32 = 4;
constant CMD_TYPE_ATTR (line 374) | const CMD_TYPE_ATTR: u32 = 5;
constant CMD_ATTR (line 375) | const CMD_ATTR: u32 = 6;
constant CMD_TYPE_TRANSITION (line 376) | const CMD_TYPE_TRANSITION: u32 = 7;
constant CMD_TYPE_CHANGE (line 377) | const CMD_TYPE_CHANGE: u32 = 8;
constant CMD_GENFSCON (line 378) | const CMD_GENFSCON: u32 = 9;
type PolicyObject (line 381) | enum PolicyObject {
type Error (line 389) | type Error = anyhow::Error;
method try_from (line 390) | fn try_from(s: &str) -> Result<Self> {
type AtomicStatement (line 407) | struct AtomicStatement {
type Error (line 420) | type Error = anyhow::Error;
function try_from (line 421) | fn try_from(perm: &'a NormalPerm<'a>) -> Result<Self> {
type Error (line 454) | type Error = anyhow::Error;
function try_from (line 455) | fn try_from(perm: &'a XPerm<'a>) -> Result<Self> {
type Error (line 485) | type Error = anyhow::Error;
function try_from (line 486) | fn try_from(perm: &'a TypeState<'a>) -> Result<Self> {
type Error (line 511) | type Error = anyhow::Error;
function try_from (line 512) | fn try_from(perm: &'a Type<'a>) -> Result<Self> {
type Error (line 532) | type Error = anyhow::Error;
function try_from (line 533) | fn try_from(perm: &'a TypeAttr<'a>) -> Result<Self> {
type Error (line 555) | type Error = anyhow::Error;
function try_from (line 556) | fn try_from(perm: &'a Attr<'a>) -> Result<Self> {
type Error (line 573) | type Error = anyhow::Error;
function try_from (line 574) | fn try_from(perm: &'a TypeTransition<'a>) -> Result<Self> {
type Error (line 596) | type Error = anyhow::Error;
function try_from (line 597) | fn try_from(perm: &'a TypeChange<'a>) -> Result<Self> {
type Error (line 620) | type Error = anyhow::Error;
function try_from (line 621) | fn try_from(perm: &'a GenFsCon<'a>) -> Result<Self> {
type Error (line 638) | type Error = anyhow::Error;
function try_from (line 639) | fn try_from(value: &'a PolicyStatement) -> Result<Self> {
type FfiPolicy (line 660) | struct FfiPolicy {
method from (line 680) | fn from(policy: AtomicStatement) -> FfiPolicy {
function to_c_ptr (line 672) | fn to_c_ptr(pol: &PolicyObject) -> *const ffi::c_char {
function check_rule (line 695) | pub fn check_rule(policy: &str) -> Result<()> {
FILE: apd/src/supercall.rs
constant MAJOR (line 18) | const MAJOR: c_long = 0;
constant MINOR (line 19) | const MINOR: c_long = 11;
constant PATCH (line 20) | const PATCH: c_long = 1;
constant KSTORAGE_EXCLUDE_LIST_GROUP (line 22) | const KSTORAGE_EXCLUDE_LIST_GROUP: i32 = 1;
constant __NR_SUPERCALL (line 24) | const __NR_SUPERCALL: c_long = 45;
constant SUPERCALL_KLOG (line 25) | const SUPERCALL_KLOG: c_long = 0x1004;
constant SUPERCALL_KERNELPATCH_VER (line 26) | const SUPERCALL_KERNELPATCH_VER: c_long = 0x1008;
constant SUPERCALL_KERNEL_VER (line 27) | const SUPERCALL_KERNEL_VER: c_long = 0x1009;
constant SUPERCALL_SU (line 28) | const SUPERCALL_SU: c_long = 0x1010;
constant SUPERCALL_KSTORAGE_WRITE (line 29) | const SUPERCALL_KSTORAGE_WRITE: c_long = 0x1041;
constant SUPERCALL_SU_GRANT_UID (line 30) | const SUPERCALL_SU_GRANT_UID: c_long = 0x1100;
constant SUPERCALL_SU_REVOKE_UID (line 31) | const SUPERCALL_SU_REVOKE_UID: c_long = 0x1101;
constant SUPERCALL_SU_NUMS (line 32) | const SUPERCALL_SU_NUMS: c_long = 0x1102;
constant SUPERCALL_SU_LIST (line 33) | const SUPERCALL_SU_LIST: c_long = 0x1103;
constant SUPERCALL_SU_RESET_PATH (line 34) | const SUPERCALL_SU_RESET_PATH: c_long = 0x1111;
constant SUPERCALL_SU_GET_SAFEMODE (line 35) | const SUPERCALL_SU_GET_SAFEMODE: c_long = 0x1112;
constant SUPERCALL_SCONTEXT_LEN (line 37) | const SUPERCALL_SCONTEXT_LEN: usize = 0x60;
type SuProfile (line 40) | struct SuProfile {
function ver_and_cmd (line 46) | fn ver_and_cmd(cmd: c_long) -> c_long {
function sc_su_revoke_uid (line 51) | fn sc_su_revoke_uid(key: &CStr, uid: uid_t) -> c_long {
function sc_su_grant_uid (line 65) | fn sc_su_grant_uid(key: &CStr, profile: &SuProfile) -> c_long {
function sc_kstorage_write (line 79) | fn sc_kstorage_write(
function sc_set_ap_mod_exclude (line 103) | fn sc_set_ap_mod_exclude(key: &CStr, uid: i64, exclude: i32) -> c_long {
function sc_su_get_safemode (line 114) | pub fn sc_su_get_safemode(key: &CStr) -> c_long {
function sc_su (line 135) | fn sc_su(key: &CStr, profile: &SuProfile) -> c_long {
function sc_su_reset_path (line 149) | fn sc_su_reset_path(key: &CStr, path: &CStr) -> c_long {
function sc_kp_ver (line 163) | fn sc_kp_ver(key: &CStr) -> Result<u32, i32> {
function sc_k_ver (line 177) | fn sc_k_ver(key: &CStr) -> Result<u32, i32> {
function sc_klog (line 191) | fn sc_klog(key: &CStr, msg: &CStr) -> c_long {
function sc_su_uid_nums (line 205) | fn sc_su_uid_nums(key: &CStr) -> c_long {
function sc_su_allow_uids (line 212) | fn sc_su_allow_uids(key: &CStr, buf: &mut [uid_t]) -> c_long {
function read_file_to_string (line 230) | fn read_file_to_string(path: &str) -> io::Result<String> {
function convert_string_to_u8_array (line 237) | fn convert_string_to_u8_array(s: &str) -> [u8; SUPERCALL_SCONTEXT_LEN] {
function convert_superkey (line 245) | fn convert_superkey(s: &Option<String>) -> Option<CString> {
function refresh_ap_package_list (line 249) | pub fn refresh_ap_package_list(skey: &CStr, mutex: &Arc<Mutex<()>>) {
function privilege_apd_profile (line 310) | pub fn privilege_apd_profile(superkey: &Option<String>) {
function init_load_package_uid_config (line 325) | pub fn init_load_package_uid_config(superkey: &Option<String>) {
function init_load_su_path (line 360) | pub fn init_load_su_path(superkey: &Option<String>) {
function set_env_var (line 392) | fn set_env_var(key: &str, value: &str) {
function log_kernel (line 400) | fn log_kernel(key: &CStr, _fmt: &str, args: std::fmt::Arguments) -> c_lo...
function fork_for_result (line 415) | pub fn fork_for_result(exec: &str, argv: &[&str], key: &Option<String>) {
FILE: apd/src/utils.rs
function ensure_file_exists (line 18) | pub fn ensure_file_exists<T: AsRef<Path>>(file: T) -> Result<()> {
function ensure_dir_exists (line 32) | pub fn ensure_dir_exists<T: AsRef<Path>>(dir: T) -> Result<()> {
function ensure_binary (line 44) | pub fn ensure_binary<T: AsRef<Path>>(path: T) -> Result<()> {
function getprop (line 50) | pub fn getprop(prop: &str) -> Option<String> {
function getprop (line 55) | pub fn getprop(_prop: &str) -> Option<String> {
function run_command (line 58) | pub fn run_command(
function is_safe_mode (line 71) | pub fn is_safe_mode(superkey: Option<String>) -> bool {
function switch_mnt_ns (line 97) | pub fn switch_mnt_ns(pid: i32) -> Result<()> {
function switch_cgroup (line 112) | fn switch_cgroup(grp: &str, pid: u32) {
function switch_cgroups (line 124) | pub fn switch_cgroups() {
function umask (line 139) | pub fn umask(mask: u32) {
function umask (line 144) | pub fn umask(_mask: u32) {
function has_magisk (line 148) | pub fn has_magisk() -> bool {
function get_tmp_path (line 151) | pub fn get_tmp_path() -> &'static str {
FILE: app/src/main/cpp/apjni.cpp
function jboolean (line 14) | jboolean nativeReady(JNIEnv *env, jobject /* this */, jstring super_key_...
function jlong (line 21) | jlong nativeKernelPatchVersion(JNIEnv *env, jobject /* this */, jstring ...
function jstring (line 29) | jstring nativeKernelPatchBuildTime(JNIEnv *env, jobject /* this */, jstr...
function jlong (line 39) | jlong nativeSu(JNIEnv *env, jobject /* this */, jstring super_key_jstr, ...
function jint (line 57) | jint nativeSetUidExclude(JNIEnv *env, jobject /* this */, jstring super_...
function jint (line 64) | jint nativeGetUidExclude(JNIEnv *env, jobject /* this */, jstring super_...
function jintArray (line 71) | jintArray nativeSuUids(JNIEnv *env, jobject /* this */, jstring super_ke...
function jobject (line 94) | jobject nativeSuProfile(JNIEnv *env, jobject /* this */, jstring super_k...
function jlong (line 118) | jlong nativeLoadKernelPatchModule(JNIEnv *env, jobject /* this */, jstri...
function jobject (line 132) | jobject nativeControlKernelPatchModule(JNIEnv *env, jobject /* this */, ...
function jlong (line 157) | jlong nativeUnloadKernelPatchModule(JNIEnv *env, jobject /* this */, jst...
function jlong (line 170) | jlong nativeKernelPatchModuleNum(JNIEnv *env, jobject /* this */, jstrin...
function jstring (line 182) | jstring nativeKernelPatchModuleList(JNIEnv *env, jobject /* this */, jst...
function jstring (line 196) | jstring nativeKernelPatchModuleInfo(JNIEnv *env, jobject /* this */, jst...
function jlong (line 210) | jlong nativeGrantSu(JNIEnv *env, jobject /* this */, jstring super_key_j...
function jlong (line 222) | jlong nativeRevokeSu(JNIEnv *env, jobject /* this */, jstring super_key_...
function jstring (line 229) | jstring nativeSuPath(JNIEnv *env, jobject /* this */, jstring super_key_...
function jboolean (line 242) | jboolean nativeResetSuPath(JNIEnv *env, jobject /* this */, jstring supe...
function JNIEXPORT (line 251) | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void * /*reserved*/) {
FILE: app/src/main/cpp/apjni.hpp
function ensureSuperKeyNonNull (line 21) | void ensureSuperKeyNonNull(jstring super_key_jstr) {
FILE: app/src/main/cpp/jni_helper.hpp
type lsplant (line 20) | namespace lsplant {
class ScopedLocalRef (line 25) | class ScopedLocalRef {
method ScopedLocalRef (line 29) | ScopedLocalRef(JNIEnv *env, T local_ref) : env_(env), local_ref_(nul...
method ScopedLocalRef (line 31) | ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_,...
method ScopedLocalRef (line 34) | ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.en...
method ScopedLocalRef (line 36) | explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, ...
method reset (line 40) | void reset(T ptr = nullptr) {
method T (line 49) | [[nodiscard]] T release() {
method T (line 55) | T get() const { return local_ref_; }
method clone (line 57) | ScopedLocalRef<T> clone() const {
method ScopedLocalRef (line 61) | ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
class JObjectArrayElement (line 80) | class JObjectArrayElement
method obtain (line 1028) | auto obtain() {
method JObjectArrayElement (line 1033) | explicit JObjectArrayElement(JNIEnv *env, jobjectArray array, int i,...
method JObjectArrayElement (line 1036) | JObjectArrayElement &operator++() {
method JObjectArrayElement (line 1042) | JObjectArrayElement &operator--() {
method JObjectArrayElement (line 1048) | JObjectArrayElement operator++(int) { return JObjectArrayElement(env...
method JObjectArrayElement (line 1050) | JObjectArrayElement operator--(int) { return JObjectArrayElement(env...
method JObjectArrayElement (line 1053) | JObjectArrayElement(JObjectArrayElement &&s)
method JObjectArrayElement (line 1060) | JObjectArrayElement &operator=(JObjectArrayElement &&s) {
method JObjectArrayElement (line 1065) | JObjectArrayElement &operator=(const JObjectArrayElement &s) {
method JObjectArrayElement (line 1071) | JObjectArrayElement &operator=(ScopedLocalRef<T> &&s) {
method JObjectArrayElement (line 1077) | JObjectArrayElement &operator=(const ScopedLocalRef<T> &s) {
method JObjectArrayElement (line 1082) | JObjectArrayElement &operator=(jobject s) {
method reset (line 1087) | void reset(jobject item) {
method clone (line 1092) | ScopedLocalRef<jobject> clone() const { return item_.clone(); }
method jobject (line 1094) | jobject get() const { return item_.get(); }
method jobject (line 1096) | jobject release() { return item_.release(); }
method jobject (line 1098) | jobject operator->() const { return item_.get(); }
method jobject (line 1100) | jobject operator*() const { return item_.get(); }
method JObjectArrayElement (line 1108) | JObjectArrayElement(const JObjectArrayElement &) = delete;
class ScopedLocalRef<T> (line 86) | class ScopedLocalRef<T>
class Iterator (line 849) | class Iterator {
method Iterator (line 851) | Iterator(JArrayUnderlyingType<T> *e) : e_(e) {}
method Iterator (line 857) | Iterator &operator++() { return ++e_, *this; }
method Iterator (line 858) | Iterator &operator--() { return --e_, *this; }
method Iterator (line 859) | Iterator operator++(int) { return Iterator(e_++); }
method Iterator (line 860) | Iterator operator--(int) { return Iterator(e_--); }
class ConstIterator (line 865) | class ConstIterator {
method ConstIterator (line 867) | ConstIterator(const JArrayUnderlyingType<T> *e) : e_(e) {}
method ConstIterator (line 873) | ConstIterator &operator++() { return ++e_, *this; }
method ConstIterator (line 874) | ConstIterator &operator--() { return --e_, *this; }
method ConstIterator (line 875) | ConstIterator operator++(int) { return ConstIterator(e_++); }
method ConstIterator (line 876) | ConstIterator operator--(int) { return ConstIterator(e_--); }
method begin (line 881) | auto begin() {
method end (line 886) | auto end() {
method begin (line 891) | const auto begin() const { return ConstIterator(elements_); }
method end (line 893) | auto end() const { return ConstIterator(elements_ + size_); }
method cbegin (line 895) | const auto cbegin() const { return ConstIterator(elements_); }
method cend (line 897) | auto cend() const { return ConstIterator(elements_ + size_); }
method ScopedLocalRef (line 901) | ScopedLocalRef(JNIEnv *env, T local_ref) noexcept : env_(env), local...
method ScopedLocalRef (line 905) | ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }
method ScopedLocalRef (line 908) | ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.en...
method ScopedLocalRef (line 910) | explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, ...
method reset (line 914) | void reset(T ptr = nullptr) {
method T (line 945) | [[nodiscard]] T release() {
method T (line 954) | T get() const { return local_ref_; }
method commit (line 963) | void commit() {
method ScopedLocalRef (line 973) | ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
method size (line 986) | size_t size() const { return size_; }
method ReleaseElements (line 996) | void ReleaseElements(jint mode) {
class JNIScopeFrame (line 88) | class JNIScopeFrame {
method JNIScopeFrame (line 94) | JNIScopeFrame(JNIEnv *env, jint size) : env_(env) { env_->PushLocalF...
class JNIMonitor (line 99) | class JNIMonitor {
method JNIMonitor (line 106) | JNIMonitor(JNIEnv *env, jobject obj) : env_(env), obj_(obj) { env_->...
function ClearException (line 124) | inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) {
function UnwrapScope (line 139) | [[maybe_unused]] inline auto UnwrapScope(T &&x) {
function WrapScope (line 151) | [[maybe_unused]] inline auto WrapScope(JNIEnv *env, T &&x) {
function WrapScope (line 159) | [[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple<T...> &&x,
function WrapScope (line 165) | [[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple<T...> &...
function JNI_NewStringUTF (line 170) | inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {
class JUTFString (line 174) | class JUTFString {
method JUTFString (line 176) | JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullpt...
method JUTFString (line 178) | JUTFString(const ScopedLocalRef<jstring> &jstr)
method JUTFString (line 181) | JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : en...
method get (line 194) | auto get() const { return cstr_; }
method JUTFString (line 200) | JUTFString(JUTFString &&other)
method JUTFString (line 207) | JUTFString &operator=(JUTFString &&other) {
method JUTFString (line 222) | JUTFString(const JUTFString &) = delete;
method JUTFString (line 224) | JUTFString &operator=(const JUTFString &) = delete;
function JNI_SafeInvoke (line 229) | [[maybe_unused]] inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::...
function JNI_FindClass (line 258) | [[maybe_unused]] inline auto JNI_FindClass(JNIEnv *env, std::string_vi...
function JNI_GetObjectClass (line 263) | [[maybe_unused]] inline auto JNI_GetObjectClass(JNIEnv *env, const Obj...
function JNI_GetFieldID (line 270) | [[maybe_unused]] inline auto JNI_GetFieldID(JNIEnv *env, Class &&clazz...
function JNI_GetObjectField (line 278) | [[maybe_unused]] inline auto JNI_GetObjectField(JNIEnv *env, Object &&...
function JNI_GetBooleanField (line 283) | [[maybe_unused]] inline auto JNI_GetBooleanField(JNIEnv *env, Object &...
function JNI_GetByteField (line 288) | [[maybe_unused]] inline auto JNI_GetByteField(JNIEnv *env, Object &&ob...
function JNI_GetCharField (line 293) | [[maybe_unused]] inline auto JNI_GetCharField(JNIEnv *env, Object &&ob...
function JNI_GetShortField (line 298) | [[maybe_unused]] inline auto JNI_GetShortField(JNIEnv *env, Object &&o...
function JNI_GetIntField (line 303) | [[maybe_unused]] inline auto JNI_GetIntField(JNIEnv *env, Object &&obj...
function JNI_GetLongField (line 308) | [[maybe_unused]] inline auto JNI_GetLongField(JNIEnv *env, Object &&ob...
function JNI_GetFloatField (line 313) | [[maybe_unused]] inline auto JNI_GetFloatField(JNIEnv *env, Object &&o...
function JNI_GetDoubleField (line 318) | [[maybe_unused]] inline auto JNI_GetDoubleField(JNIEnv *env, Object &&...
function JNI_SetObjectField (line 325) | [[maybe_unused]] inline auto JNI_SetObjectField(JNIEnv *env, Object &&...
function JNI_SetBooleanField (line 331) | [[maybe_unused]] inline auto JNI_SetBooleanField(JNIEnv *env, Object &...
function JNI_SetByteField (line 337) | [[maybe_unused]] inline auto JNI_SetByteField(JNIEnv *env, Object &&ob...
function JNI_SetCharField (line 343) | [[maybe_unused]] inline auto JNI_SetCharField(JNIEnv *env, Object &&ob...
function JNI_SetShortField (line 349) | [[maybe_unused]] inline auto JNI_SetShortField(JNIEnv *env, Object &&o...
function JNI_SetIntField (line 355) | [[maybe_unused]] inline auto JNI_SetIntField(JNIEnv *env, Object &&obj...
function JNI_SetLongField (line 361) | [[maybe_unused]] inline auto JNI_SetLongField(JNIEnv *env, Object &&ob...
function JNI_SetFloatField (line 367) | [[maybe_unused]] inline auto JNI_SetFloatField(JNIEnv *env, Object &&o...
function JNI_SetDoubleField (line 373) | [[maybe_unused]] inline auto JNI_SetDoubleField(JNIEnv *env, Object &&...
function JNI_GetStaticFieldID (line 381) | [[maybe_unused]] inline auto JNI_GetStaticFieldID(JNIEnv *env, Class &...
function JNI_GetStaticObjectField (line 389) | [[maybe_unused]] inline auto JNI_GetStaticObjectField(JNIEnv *env, Cla...
function JNI_GetStaticBooleanField (line 395) | [[maybe_unused]] inline auto JNI_GetStaticBooleanField(JNIEnv *env, Cl...
function JNI_GetStaticByteField (line 401) | [[maybe_unused]] inline auto JNI_GetStaticByteField(JNIEnv *env, Class...
function JNI_GetStaticCharField (line 406) | [[maybe_unused]] inline auto JNI_GetStaticCharField(JNIEnv *env, Class...
function JNI_GetStaticShortField (line 411) | [[maybe_unused]] inline auto JNI_GetStaticShortField(JNIEnv *env, Clas...
function JNI_GetStaticIntField (line 416) | [[maybe_unused]] inline auto JNI_GetStaticIntField(JNIEnv *env, Class ...
function JNI_GetStaticLongField (line 421) | [[maybe_unused]] inline auto JNI_GetStaticLongField(JNIEnv *env, Class...
function JNI_GetStaticFloatField (line 426) | [[maybe_unused]] inline auto JNI_GetStaticFloatField(JNIEnv *env, Clas...
function JNI_GetStaticDoubleField (line 431) | [[maybe_unused]] inline auto JNI_GetStaticDoubleField(JNIEnv *env, Cla...
function JNI_SetStaticObjectField (line 439) | [[maybe_unused]] inline auto JNI_SetStaticObjectField(JNIEnv *env, Cla...
function JNI_SetStaticBooleanField (line 446) | [[maybe_unused]] inline auto JNI_SetStaticBooleanField(JNIEnv *env, Cl...
function JNI_SetStaticByteField (line 453) | [[maybe_unused]] inline auto JNI_SetStaticByteField(JNIEnv *env, Class...
function JNI_SetStaticCharField (line 460) | [[maybe_unused]] inline auto JNI_SetStaticCharField(JNIEnv *env, Class...
function JNI_SetStaticShortField (line 467) | [[maybe_unused]] inline auto JNI_SetStaticShortField(JNIEnv *env, Clas...
function JNI_SetStaticIntField (line 474) | [[maybe_unused]] inline auto JNI_SetStaticIntField(JNIEnv *env, Class ...
function JNI_SetStaticLongField (line 481) | [[maybe_unused]] inline auto JNI_SetStaticLongField(JNIEnv *env, Class...
function JNI_SetStaticFloatField (line 488) | [[maybe_unused]] inline auto JNI_SetStaticFloatField(JNIEnv *env, Clas...
function JNI_SetStaticDoubleField (line 495) | [[maybe_unused]] inline auto JNI_SetStaticDoubleField(JNIEnv *env, Cla...
function JNI_ToReflectedMethod (line 502) | [[maybe_unused]] inline auto JNI_ToReflectedMethod(JNIEnv *env, Class ...
function JNI_ToReflectedField (line 509) | [[maybe_unused]] inline auto JNI_ToReflectedField(JNIEnv *env, Class &...
function JNI_GetMethodID (line 520) | [[maybe_unused]] inline auto JNI_GetMethodID(JNIEnv *env, Class &&claz...
function JNI_CallVoidMethod (line 526) | [[maybe_unused]] inline auto JNI_CallVoidMethod(JNIEnv *env, Object &&...
function JNI_CallObjectMethod (line 533) | [[maybe_unused]] inline auto JNI_CallObjectMethod(JNIEnv *env, Object ...
function JNI_CallBooleanMethod (line 540) | [[maybe_unused]] inline auto JNI_CallBooleanMethod(JNIEnv *env, Object...
function JNI_CallByteMethod (line 547) | [[maybe_unused]] inline auto JNI_CallByteMethod(JNIEnv *env, Object &&...
function JNI_CallCharMethod (line 554) | [[maybe_unused]] inline auto JNI_CallCharMethod(JNIEnv *env, Object &&...
function JNI_CallShortMethod (line 561) | [[maybe_unused]] inline auto JNI_CallShortMethod(JNIEnv *env, Object &...
function JNI_CallIntMethod (line 568) | [[maybe_unused]] inline auto JNI_CallIntMethod(JNIEnv *env, Object &&o...
function JNI_CallLongMethod (line 575) | [[maybe_unused]] inline auto JNI_CallLongMethod(JNIEnv *env, Object &&...
function JNI_CallFloatMethod (line 582) | [[maybe_unused]] inline auto JNI_CallFloatMethod(JNIEnv *env, Object &...
function JNI_CallDoubleMethod (line 589) | [[maybe_unused]] inline auto JNI_CallDoubleMethod(JNIEnv *env, Object ...
function JNI_GetStaticMethodID (line 598) | [[maybe_unused]] inline auto JNI_GetStaticMethodID(JNIEnv *env, Class ...
function JNI_CallStaticVoidMethod (line 604) | [[maybe_unused]] inline auto JNI_CallStaticVoidMethod(JNIEnv *env, Cla...
function JNI_CallStaticObjectMethod (line 611) | [[maybe_unused]] inline auto JNI_CallStaticObjectMethod(JNIEnv *env, C...
function JNI_CallStaticBooleanMethod (line 618) | [[maybe_unused]] inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, ...
function JNI_CallStaticByteMethod (line 625) | [[maybe_unused]] inline auto JNI_CallStaticByteMethod(JNIEnv *env, Cla...
function JNI_CallStaticCharMethod (line 632) | [[maybe_unused]] inline auto JNI_CallStaticCharMethod(JNIEnv *env, Cla...
function JNI_CallStaticShortMethod (line 639) | [[maybe_unused]] inline auto JNI_CallStaticShortMethod(JNIEnv *env, Cl...
function JNI_CallStaticIntMethod (line 646) | [[maybe_unused]] inline auto JNI_CallStaticIntMethod(JNIEnv *env, Clas...
function JNI_CallStaticLongMethod (line 653) | [[maybe_unused]] inline auto JNI_CallStaticLongMethod(JNIEnv *env, Cla...
function JNI_CallStaticFloatMethod (line 660) | [[maybe_unused]] inline auto JNI_CallStaticFloatMethod(JNIEnv *env, Cl...
function JNI_CallStaticDoubleMethod (line 667) | [[maybe_unused]] inline auto JNI_CallStaticDoubleMethod(JNIEnv *env, C...
function JNI_CallNonvirtualVoidMethod (line 676) | [[maybe_unused]] inline auto JNI_CallNonvirtualVoidMethod(JNIEnv *env,...
function JNI_CallNonvirtualObjectMethod (line 683) | [[maybe_unused]] inline auto JNI_CallNonvirtualObjectMethod(JNIEnv *en...
function JNI_CallNonvirtualBooleanMethod (line 691) | [[maybe_unused]] inline auto JNI_CallNonvirtualBooleanMethod(JNIEnv *e...
function JNI_CallNonvirtualByteMethod (line 699) | [[maybe_unused]] inline auto JNI_CallNonvirtualByteMethod(JNIEnv *env,...
function JNI_CallNonvirtualCharMethod (line 706) | [[maybe_unused]] inline auto JNI_CallNonvirtualCharMethod(JNIEnv *env,...
function JNI_CallNonvirtualShortMethod (line 713) | [[maybe_unused]] inline auto JNI_CallNonvirtualShortMethod(JNIEnv *env...
function JNI_CallNonvirtualIntMethod (line 720) | [[maybe_unused]] inline auto JNI_CallNonvirtualIntMethod(JNIEnv *env, ...
function JNI_CallNonvirtualLongMethod (line 727) | [[maybe_unused]] inline auto JNI_CallNonvirtualLongMethod(JNIEnv *env,...
function JNI_CallNonvirtualFloatMethod (line 734) | [[maybe_unused]] inline auto JNI_CallNonvirtualFloatMethod(JNIEnv *env...
function JNI_CallNonvirtualDoubleMethod (line 741) | [[maybe_unused]] inline auto JNI_CallNonvirtualDoubleMethod(JNIEnv *en...
function JNI_NewObject (line 749) | [[maybe_unused]] inline auto JNI_NewObject(JNIEnv *env, Class &&clazz,...
function JNI_NewDirectByteBuffer (line 756) | [[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, Args...
function JNI_RegisterNatives (line 761) | [[maybe_unused]] inline auto JNI_RegisterNatives(JNIEnv *env, Class &&...
function JNI_IsInstanceOf (line 767) | [[maybe_unused]] inline auto JNI_IsInstanceOf(JNIEnv *env, Object &&ob...
function JNI_IsSameObject (line 773) | [[maybe_unused]] inline auto JNI_IsSameObject(JNIEnv *env, Object1 &&a...
function JNI_NewGlobalRef (line 779) | [[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) {
function JNI_Cast (line 785) | [[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x)
function JNI_Cast (line 792) | [[maybe_unused]] inline auto JNI_Cast(JObjectArrayElement &&x) {
function JNI_NewDirectByteBuffer (line 796) | [[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, void...
type JArrayUnderlyingTypeHelper (line 801) | struct JArrayUnderlyingTypeHelper
type JArrayUnderlyingTypeHelper<jbooleanArray> (line 804) | struct JArrayUnderlyingTypeHelper<jbooleanArray> {
type JArrayUnderlyingTypeHelper<jbyteArray> (line 809) | struct JArrayUnderlyingTypeHelper<jbyteArray> {
type JArrayUnderlyingTypeHelper<jcharArray> (line 814) | struct JArrayUnderlyingTypeHelper<jcharArray> {
type JArrayUnderlyingTypeHelper<jshortArray> (line 819) | struct JArrayUnderlyingTypeHelper<jshortArray> {
type JArrayUnderlyingTypeHelper<jintArray> (line 824) | struct JArrayUnderlyingTypeHelper<jintArray> {
type JArrayUnderlyingTypeHelper<jlongArray> (line 829) | struct JArrayUnderlyingTypeHelper<jlongArray> {
type JArrayUnderlyingTypeHelper<jfloatArray> (line 834) | struct JArrayUnderlyingTypeHelper<jfloatArray> {
type JArrayUnderlyingTypeHelper<jdoubleArray> (line 839) | struct JArrayUnderlyingTypeHelper<jdoubleArray> {
class ScopedLocalRef<T> (line 847) | class ScopedLocalRef<T> {
class Iterator (line 849) | class Iterator {
method Iterator (line 851) | Iterator(JArrayUnderlyingType<T> *e) : e_(e) {}
method Iterator (line 857) | Iterator &operator++() { return ++e_, *this; }
method Iterator (line 858) | Iterator &operator--() { return --e_, *this; }
method Iterator (line 859) | Iterator operator++(int) { return Iterator(e_++); }
method Iterator (line 860) | Iterator operator--(int) { return Iterator(e_--); }
class ConstIterator (line 865) | class ConstIterator {
method ConstIterator (line 867) | ConstIterator(const JArrayUnderlyingType<T> *e) : e_(e) {}
method ConstIterator (line 873) | ConstIterator &operator++() { return ++e_, *this; }
method ConstIterator (line 874) | ConstIterator &operator--() { return --e_, *this; }
method ConstIterator (line 875) | ConstIterator operator++(int) { return ConstIterator(e_++); }
method ConstIterator (line 876) | ConstIterator operator--(int) { return ConstIterator(e_--); }
method begin (line 881) | auto begin() {
method end (line 886) | auto end() {
method begin (line 891) | const auto begin() const { return ConstIterator(elements_); }
method end (line 893) | auto end() const { return ConstIterator(elements_ + size_); }
method cbegin (line 895) | const auto cbegin() const { return ConstIterator(elements_); }
method cend (line 897) | auto cend() const { return ConstIterator(elements_ + size_); }
method ScopedLocalRef (line 901) | ScopedLocalRef(JNIEnv *env, T local_ref) noexcept : env_(env), local...
method ScopedLocalRef (line 905) | ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }
method ScopedLocalRef (line 908) | ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.en...
method ScopedLocalRef (line 910) | explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, ...
method reset (line 914) | void reset(T ptr = nullptr) {
method T (line 945) | [[nodiscard]] T release() {
method T (line 954) | T get() const { return local_ref_; }
method commit (line 963) | void commit() {
method ScopedLocalRef (line 973) | ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
method size (line 986) | size_t size() const { return size_; }
method ReleaseElements (line 996) | void ReleaseElements(jint mode) {
class JObjectArrayElement (line 1025) | class JObjectArrayElement {
method obtain (line 1028) | auto obtain() {
method JObjectArrayElement (line 1033) | explicit JObjectArrayElement(JNIEnv *env, jobjectArray array, int i,...
method JObjectArrayElement (line 1036) | JObjectArrayElement &operator++() {
method JObjectArrayElement (line 1042) | JObjectArrayElement &operator--() {
method JObjectArrayElement (line 1048) | JObjectArrayElement operator++(int) { return JObjectArrayElement(env...
method JObjectArrayElement (line 1050) | JObjectArrayElement operator--(int) { return JObjectArrayElement(env...
method JObjectArrayElement (line 1053) | JObjectArrayElement(JObjectArrayElement &&s)
method JObjectArrayElement (line 1060) | JObjectArrayElement &operator=(JObjectArrayElement &&s) {
method JObjectArrayElement (line 1065) | JObjectArrayElement &operator=(const JObjectArrayElement &s) {
method JObjectArrayElement (line 1071) | JObjectArrayElement &operator=(ScopedLocalRef<T> &&s) {
method JObjectArrayElement (line 1077) | JObjectArrayElement &operator=(const ScopedLocalRef<T> &s) {
method JObjectArrayElement (line 1082) | JObjectArrayElement &operator=(jobject s) {
method reset (line 1087) | void reset(jobject item) {
method clone (line 1092) | ScopedLocalRef<jobject> clone() const { return item_.clone(); }
method jobject (line 1094) | jobject get() const { return item_.get(); }
method jobject (line 1096) | jobject release() { return item_.release(); }
method jobject (line 1098) | jobject operator->() const { return item_.get(); }
method jobject (line 1100) | jobject operator*() const { return item_.get(); }
method JObjectArrayElement (line 1108) | JObjectArrayElement(const JObjectArrayElement &) = delete;
class ScopedLocalRef<jobjectArray> (line 1112) | class ScopedLocalRef<jobjectArray> {
class Iterator (line 1114) | class Iterator {
method Iterator (line 1117) | Iterator(JObjectArrayElement &&e) : e_(std::move(e)) {}
method Iterator (line 1118) | Iterator(JNIEnv *env, jobjectArray array, int i, size_t size) : e_...
method Iterator (line 1125) | Iterator &operator++() {
method Iterator (line 1130) | Iterator &operator--() {
method Iterator (line 1135) | Iterator operator++(int) { return Iterator(e_++); }
method Iterator (line 1137) | Iterator operator--(int) { return Iterator(e_--); }
class ConstIterator (line 1147) | class ConstIterator {
method obtain (line 1150) | auto obtain() {
method ConstIterator (line 1155) | ConstIterator(JNIEnv *env, jobjectArray array, int i, int size)
method ConstIterator (line 1163) | ConstIterator &operator++() {
method ConstIterator (line 1169) | ConstIterator &operator--() {
method ConstIterator (line 1175) | ConstIterator operator++(int) { return ConstIterator(env_, array_,...
method ConstIterator (line 1177) | ConstIterator operator--(int) { return ConstIterator(env_, array_,...
method begin (line 1191) | auto begin() { return Iterator(env_, local_ref_, 0, size_); }
method end (line 1193) | auto end() { return Iterator(env_, local_ref_, size_, size_); }
method begin (line 1195) | const auto begin() const { return ConstIterator(env_, local_ref_, 0,...
method end (line 1197) | auto end() const { return ConstIterator(env_, local_ref_, size_, siz...
method cbegin (line 1199) | const auto cbegin() const { return ConstIterator(env_, local_ref_, 0...
method cend (line 1201) | auto cend() const { return ConstIterator(env_, local_ref_, size_, si...
method ScopedLocalRef (line 1203) | ScopedLocalRef(JNIEnv *env, jobjectArray local_ref) noexcept : env_(...
method ScopedLocalRef (line 1207) | ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }
method ScopedLocalRef (line 1210) | ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept
method ScopedLocalRef (line 1213) | explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, ...
method reset (line 1217) | void reset(jobjectArray ptr = nullptr) {
method jobjectArray (line 1228) | [[nodiscard]] jobjectArray release() {
method jobjectArray (line 1235) | jobjectArray get() const { return local_ref_; }
method JObjectArrayElement (line 1237) | JObjectArrayElement operator[](size_t index) {
method ScopedLocalRef (line 1250) | ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
method size (line 1259) | size_t size() const { return size_; }
function JNI_GetArrayLength (line 1277) | [[maybe_unused]] inline auto JNI_GetArrayLength(JNIEnv *env, const Arr...
function JNI_NewObjectArray (line 1284) | [[maybe_unused]] inline auto JNI_NewObjectArray(JNIEnv *env, jsize len...
function JNI_NewBooleanArray (line 1289) | [[maybe_unused]] inline auto JNI_NewBooleanArray(JNIEnv *env, jsize le...
function JNI_NewByteArray (line 1293) | [[maybe_unused]] inline auto JNI_NewByteArray(JNIEnv *env, jsize len) {
function JNI_NewCharArray (line 1297) | [[maybe_unused]] inline auto JNI_NewCharArray(JNIEnv *env, jsize len) {
function JNI_NewShortArray (line 1301) | [[maybe_unused]] inline auto JNI_NewShortArray(JNIEnv *env, jsize len) {
function JNI_NewIntArray (line 1305) | [[maybe_unused]] inline auto JNI_NewIntArray(JNIEnv *env, jsize len) {
function JNI_NewLongArray (line 1309) | [[maybe_unused]] inline auto JNI_NewLongArray(JNIEnv *env, jsize len) {
function JNI_NewFloatArray (line 1313) | [[maybe_unused]] inline auto JNI_NewFloatArray(JNIEnv *env, jsize len) {
function JNI_NewDoubleArray (line 1317) | [[maybe_unused]] inline auto JNI_NewDoubleArray(JNIEnv *env, jsize len) {
function JNI_GetObjectFieldOf (line 1322) | [[maybe_unused]] inline auto JNI_GetObjectFieldOf(JNIEnv *env, Object ...
FILE: app/src/main/cpp/supercall.h
function ver_and_cmd (line 20) | static inline long ver_and_cmd(const char *key, long cmd)
function sc_hello (line 32) | static inline long sc_hello(const char *key)
function sc_ready (line 46) | static inline bool sc_ready(const char *key)
function sc_klog (line 58) | static inline long sc_klog(const char *key, const char *msg)
function sc_get_build_time (line 74) | static inline long sc_get_build_time(const char *key, const char *buildt...
function sc_kp_ver (line 88) | static inline uint32_t sc_kp_ver(const char *key)
function sc_k_ver (line 101) | static inline uint32_t sc_k_ver(const char *key)
function sc_su (line 116) | static inline long sc_su(const char *key, struct su_profile *profile)
function sc_su_task (line 133) | static inline long sc_su_task(const char *key, pid_t tid, struct su_prof...
function sc_kstorage_write (line 150) | static inline long sc_kstorage_write(const char *key, int gid, long did,...
function sc_kstorage_read (line 167) | static inline long sc_kstorage_read(const char *key, int gid, long did, ...
function sc_kstorage_list_ids (line 184) | static inline long sc_kstorage_list_ids(const char *key, int gid, long *...
function sc_kstorage_remove (line 200) | static inline long sc_kstorage_remove(const char *key, int gid, long did)
function sc_set_ap_mod_exclude (line 216) | static inline long sc_set_ap_mod_exclude(const char *key, uid_t uid, int...
function sc_get_ap_mod_exclude (line 234) | static inline int sc_get_ap_mod_exclude(const char *key, uid_t uid)
function sc_list_ap_mod_exclude (line 245) | static inline int sc_list_ap_mod_exclude(const char *key, uid_t *uids, i...
function sc_su_grant_uid (line 266) | static inline long sc_su_grant_uid(const char *key, struct su_profile *p...
function sc_su_revoke_uid (line 280) | static inline long sc_su_revoke_uid(const char *key, uid_t uid)
function sc_su_uid_nums (line 293) | static inline long sc_su_uid_nums(const char *key)
function sc_su_allow_uids (line 308) | static inline long sc_su_allow_uids(const char *key, uid_t *buf, int num)
function sc_su_uid_profile (line 324) | static inline long sc_su_uid_profile(const char *key, uid_t uid, struct ...
function sc_su_get_path (line 339) | static inline long sc_su_get_path(const char *key, char *out_path, int p...
function sc_su_reset_path (line 354) | static inline long sc_su_reset_path(const char *key, const char *path)
function sc_su_get_all_allow_sctx (line 370) | static inline long sc_su_get_all_allow_sctx(const char *key, char *out_s...
function sc_su_reset_all_allow_sctx (line 386) | static inline long sc_su_reset_all_allow_sctx(const char *key, const cha...
function sc_kpm_load (line 403) | static inline long sc_kpm_load(const char *key, const char *path, const ...
function sc_kpm_control (line 421) | static inline long sc_kpm_control(const char *key, const char *name, con...
function sc_kpm_unload (line 438) | static inline long sc_kpm_unload(const char *key, const char *name, void...
function sc_kpm_nums (line 452) | static inline long sc_kpm_nums(const char *key)
function sc_kpm_list (line 467) | static inline long sc_kpm_list(const char *key, char *names_buf, int buf...
function sc_kpm_info (line 484) | static inline long sc_kpm_info(const char *key, const char *name, char *...
function sc_skey_get (line 500) | static inline long sc_skey_get(const char *key, char *out_key, int outlen)
function sc_skey_set (line 515) | static inline long sc_skey_set(const char *key, const char *new_key)
function sc_skey_root_enable (line 530) | static inline long sc_skey_root_enable(const char *key, bool enable)
function sc_su_get_safemode (line 543) | static inline long sc_su_get_safemode(const char *key)
function sc_bootlog (line 550) | static inline long sc_bootlog(const char *key)
function sc_panic (line 556) | static inline long sc_panic(const char *key)
function __sc_test (line 562) | static inline long __sc_test(const char *key, long a1, long a2, long a3)
FILE: app/src/main/cpp/type_traits.hpp
type lsplant (line 5) | namespace lsplant {
type is_instance (line 7) | struct is_instance : public std::false_type {}
type is_instance<U<Ts...>, U> (line 10) | struct is_instance<U<Ts...>, U> : public std::true_type {}
FILE: app/src/main/cpp/uapi/scdefs.h
function hash_key (line 9) | static inline long hash_key(const char *key)
type kernel_storage (line 45) | struct kernel_storage
type su_profile (line 70) | struct su_profile
FILE: app/src/main/java/me/bmax/apatch/services/RootServices.java
class RootServices (line 23) | public class RootServices extends RootService {
method onBind (line 26) | @Override
method getUserIds (line 31) | List<Integer> getUserIds() {
method getInstalledPackagesAll (line 42) | ArrayList<PackageInfo> getInstalledPackagesAll(int flags) {
method getInstalledPackagesAsUser (line 51) | List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
class Stub (line 63) | class Stub extends IAPRootService.Stub {
method getPackages (line 64) | @Override
FILE: app/src/main/java/me/bmax/apatch/ui/webui/MimeUtil.java
class MimeUtil (line 21) | class MimeUtil {
method getMimeFromFileName (line 23) | public static String getMimeFromFileName(String fileName) {
method guessHardcodedMime (line 46) | private static String guessHardcodedMime(String fileName) {
FILE: app/src/main/java/me/bmax/apatch/ui/webui/SuFilePathHandler.java
class SuFilePathHandler (line 45) | public final class SuFilePathHandler implements WebViewAssetLoader.PathH...
type InsetsSupplier (line 70) | public interface InsetsSupplier {
method get (line 71) | @NonNull
type OnInsetsRequestedListener (line 75) | public interface OnInsetsRequestedListener {
method onInsetsRequested (line 76) | void onInsetsRequested(boolean enable);
method SuFilePathHandler (line 103) | public SuFilePathHandler(@NonNull Context context, @NonNull File direc...
method isAllowedInternalStorageDir (line 120) | private boolean isAllowedInternalStorageDir(@NonNull Context context) ...
method handle (line 150) | @Override
method getCanonicalDirPath (line 188) | public static String getCanonicalDirPath(@NonNull File file) throws IO...
method getCanonicalFileIfChild (line 194) | public static File getCanonicalFileIfChild(@NonNull File parent, @NonN...
method handleSvgzStream (line 204) | @NonNull
method openFile (line 210) | public static InputStream openFile(@NonNull File file, @NonNull Shell ...
method guessMimeType (line 225) | @NonNull
FILE: app/src/main/java/me/bmax/apatch/util/APatchKeyHelper.java
class APatchKeyHelper (line 20) | public class APatchKeyHelper {
method setSharedPreferences (line 43) | public static void setSharedPreferences(SharedPreferences sp) {
method generateSecretKey (line 47) | private static void generateSecretKey() {
method getRandomIV (line 72) | private static String getRandomIV() {
method encrypt (line 83) | private static String encrypt(String orig) {
method decrypt (line 99) | private static String decrypt(String encryptedData) {
method shouldSkipStoreSuperKey (line 115) | public static boolean shouldSkipStoreSuperKey() {
method clearConfigKey (line 119) | public static void clearConfigKey() {
method setShouldSkipStoreSuperKey (line 125) | public static void setShouldSkipStoreSuperKey(boolean should) {
method readSPSuperKey (line 130) | public static String readSPSuperKey() {
method writeSPSuperKey (line 143) | public static void writeSPSuperKey(String key) {
FILE: app/src/main/java/me/bmax/apatch/util/HanziToPinyin.java
class HanziToPinyin (line 35) | public class HanziToPinyin {
method HanziToPinyin (line 346) | protected HanziToPinyin(boolean hasChinaCollator) {
method getInstance (line 350) | public static HanziToPinyin getInstance() {
method doSelfValidation (line 384) | private static boolean doSelfValidation() {
method getToken (line 403) | private Token getToken(char character) {
method get (line 472) | public ArrayList<Token> get(final String input) {
method addToken (line 520) | private void addToken(
method toPinyinString (line 527) | public String toPinyinString(String string) {
class Token (line 539) | public static class Token {
method Token (line 561) | public Token() {
method Token (line 563) | public Token(int type, String source, String target) {
Condensed preview — 220 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,577K chars).
[
{
"path": ".gitattributes",
"chars": 639,
"preview": "# Set the default behavior, in case people don't have core.autocrlf set.\n* text eol=lf\n\n# Explicitly declare text files "
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 4054,
"preview": "name: Bug report | 反馈 Bug\ndescription: Report bugs or unexpected behavior | 报告错误或未预料的行为\nlabels: [bug]\n\nbody:\n - type: m"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 372,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Ask a question | 提问\n url: https://github.com/bmax121/APatch/disc"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 1174,
"preview": "---\nname: Feature request | 新特性请求\ndescription: Suggest an idea for this project | 提出建议\nlabels: [enhancement]\n\nbody:\n - "
},
{
"path": ".github/dependabot.yml",
"chars": 866,
"preview": "version: 2\nupdates:\n - package-ecosystem: gradle\n directory: \"/\"\n schedule:\n interval: daily\n target-bran"
},
{
"path": ".github/scripts/telegram_url.py",
"chars": 784,
"preview": "import json\nimport os\nimport sys\nimport urllib.parse\n\nurl = f'https://api.telegram.org/bot{os.environ[\"BOT_TOKEN\"]}'\nurl"
},
{
"path": ".github/workflows/build.yml",
"chars": 5629,
"preview": "name: Build Manager\n\non:\n push:\n tags: [ \"*\" ]\n branches: [ \"main\" ]\n paths:\n - '.github/workflows/build."
},
{
"path": ".gitignore",
"chars": 142,
"preview": "*.iml\n.gradle\nlocal.properties\n.idea\n.DS_Store\nbuild\ncaptures\n.cxx\nkey.jks\nkey.jks.base64.txt\n.vscode\n.kotlin\napp/src/ma"
},
{
"path": "LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "README.md",
"chars": 3679,
"preview": "<div align=\"center\">\n<a href=\"https://github.com/bmax121/APatch/releases/latest\"><img src=\"https://images.weserv.nl/?url"
},
{
"path": "apd/.gitignore",
"chars": 15,
"preview": "/target\n.cargo/"
},
{
"path": "apd/Cargo.toml",
"chars": 1731,
"preview": "[package]\nname = \"apd\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lan"
},
{
"path": "apd/build.rs",
"chars": 2143,
"preview": "use std::env;\nuse std::fs::File;\nuse std::io::Write;\nuse std::path::Path;\nuse std::process::Command;\n\nfn get_git_version"
},
{
"path": "apd/src/apd.rs",
"chars": 7091,
"preview": "#[cfg(unix)]\nuse std::os::unix::process::CommandExt;\nuse std::{env, ffi::CStr, path::PathBuf, process::Command};\n\nuse an"
},
{
"path": "apd/src/assets.rs",
"chars": 482,
"preview": "use anyhow::Result;\nuse const_format::concatcp;\n\nuse crate::{defs::BINARY_DIR, utils};\n\npub const RESETPROP_PATH: &str ="
},
{
"path": "apd/src/banner",
"chars": 175,
"preview": " _ ____ _ _ \n / \\ | _ \\ __ _| |_ ___| |__ \n / _ \\ | |_) / _` | __/ __| '_ \\ \n / ___ \\| __/ "
},
{
"path": "apd/src/cli.rs",
"chars": 4314,
"preview": "use crate::{defs, event, lua, module, supercall, utils};\n#[cfg(target_os = \"android\")]\nuse android_logger::Config;\nuse a"
},
{
"path": "apd/src/defs.rs",
"chars": 1520,
"preview": "use const_format::concatcp;\n\npub const ADB_DIR: &str = \"/data/adb/\";\npub const WORKING_DIR: &str = concatcp!(ADB_DIR, \"a"
},
{
"path": "apd/src/event.rs",
"chars": 10118,
"preview": "use std::{\n env,\n ffi::CStr,\n fs,\n os::unix::{fs::PermissionsExt, process::CommandExt},\n path::{Path, Pat"
},
{
"path": "apd/src/installer.sh",
"chars": 11129,
"preview": "#!/system/bin/sh\n############################################\n# APatch Module installer script\n# mostly from module_inst"
},
{
"path": "apd/src/installer_bind.sh",
"chars": 10953,
"preview": "#!/system/bin/sh\n############################################\n# APatch Module installer script\n# mostly from module_inst"
},
{
"path": "apd/src/lua.rs",
"chars": 5433,
"preview": "use crate::module::*;\nuse crate::utils::*;\nuse anyhow::Result;\nuse log::{info, warn};\nuse mlua::{Function, Lua, Result a"
},
{
"path": "apd/src/main.rs",
"chars": 273,
"preview": "mod apd;\nmod assets;\nmod cli;\nmod defs;\nmod event;\nmod lua;\nmod metamodule;\nmod module;\nmod package;\n#[cfg(any(target_os"
},
{
"path": "apd/src/metamodule.rs",
"chars": 9488,
"preview": "//! Metamodule management\n//!\n//! This module handles all metamodule-related functionality.\n//! Metamodules are special "
},
{
"path": "apd/src/module.rs",
"chars": 23860,
"preview": "#[cfg(unix)]\nuse std::os::unix::{prelude::PermissionsExt, process::CommandExt};\nuse std::{\n collections::HashMap,\n "
},
{
"path": "apd/src/package.rs",
"chars": 5774,
"preview": "use std::{\n fs::File,\n io::{self, BufRead},\n path::Path,\n thread,\n time::Duration,\n};\n\nuse log::{info, wa"
},
{
"path": "apd/src/pty.rs",
"chars": 5010,
"preview": "use std::{\n ffi::c_int,\n fs::File,\n io::{Read, Write, stderr, stdin, stdout},\n mem::MaybeUninit,\n os::fd:"
},
{
"path": "apd/src/restorecon.rs",
"chars": 2548,
"preview": "use std::path::Path;\n\nuse anyhow::Result;\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nuse anyhow::{Context, "
},
{
"path": "apd/src/sepolicy.rs",
"chars": 21913,
"preview": "use std::{ffi, path::Path, vec};\n\nuse anyhow::{Result, bail};\nuse derive_new::new;\nuse nom::{\n AsChar, IResult, Parse"
},
{
"path": "apd/src/supercall.rs",
"chars": 14053,
"preview": "use std::{\n ffi::{CStr, CString},\n fmt::Write,\n fs::File,\n io::{self, Read},\n process,\n process::exit,"
},
{
"path": "apd/src/utils.rs",
"chars": 4449,
"preview": "#[allow(unused_imports)]\nuse std::fs::{Permissions, set_permissions};\n#[cfg(unix)]\nuse std::os::unix::prelude::Permissio"
},
{
"path": "app/.gitignore",
"chars": 17,
"preview": "/build\n/release/\n"
},
{
"path": "app/build.gradle.kts",
"chars": 10903,
"preview": "@file:Suppress(\"UnstableApiUsage\")\n\nimport com.android.build.gradle.tasks.PackageAndroidArtifact\nimport org.jetbrains.ko"
},
{
"path": "app/libs/arm64-v8a/.gitignore",
"chars": 48,
"preview": "libkptools.so\nlibapjni.so\nlibkpatch.so\nlibapd.so"
},
{
"path": "app/proguard-rules.pro",
"chars": 1017,
"preview": "-dontwarn org.bouncycastle.jsse.BCSSLParameters\n-dontwarn org.bouncycastle.jsse.BCSSLSocket\n-dontwarn org.bouncycastle.j"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 2692,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "app/src/main/aidl/me/bmax/apatch/IAPRootService.aidl",
"chars": 226,
"preview": "// IAPRootService.aidl\npackage me.bmax.apatch;\n\nimport android.content.pm.PackageInfo;\nimport rikka.parcelablelist.Parce"
},
{
"path": "app/src/main/assets/.gitignore",
"chars": 11,
"preview": "kpimg\n*.kpm"
},
{
"path": "app/src/main/assets/InstallAP.sh",
"chars": 2844,
"preview": "#!/bin/sh\n# By SakuraKyuo\n\nOUTFD=/proc/self/fd/$2\n\nfunction ui_print() {\n echo -e \"ui_print $1\\nui_print\" >> $OUTFD\n}\n\n"
},
{
"path": "app/src/main/assets/UninstallAP.sh",
"chars": 1737,
"preview": "#!/bin/sh\n# By SakuraKyuo\n\nOUTFD=/proc/self/fd/$2\n\nfunction ui_print() {\n echo -e \"ui_print $1\\nui_print\" >> $OUTFD\n}\n\n"
},
{
"path": "app/src/main/assets/boot_extract.sh",
"chars": 310,
"preview": "#!/system/bin/sh\n\nARCH=$(getprop ro.product.cpu.abi)\n\nIS_INSTALL_NEXT_SLOT=$1\n\n# Load utility functions\n. ./util_functio"
},
{
"path": "app/src/main/assets/boot_patch.sh",
"chars": 2995,
"preview": "#!/system/bin/sh\n#######################################################################################\n# APatch Boot I"
},
{
"path": "app/src/main/assets/boot_unpatch.sh",
"chars": 1692,
"preview": "#!/system/bin/sh\n#######################################################################################\n# APatch Boot I"
},
{
"path": "app/src/main/assets/util_functions.sh",
"chars": 14435,
"preview": "#!/system/bin/sh\n#######################################################################################\n# Helper Functi"
},
{
"path": "app/src/main/cpp/CMakeLists.txt",
"chars": 1860,
"preview": "cmake_minimum_required(VERSION 3.28.0)\nproject(\"apjni\")\n\nfind_program(CCACHE ccache)\n\nif (CCACHE)\n set(CMAKE_CXX_"
},
{
"path": "app/src/main/cpp/apjni.cpp",
"chars": 12082,
"preview": "/* SPDX-License-Identifier: GPL-2.0-or-later */\n/* \n * Copyright (C) 2023 bmax121. All Rights Reserved.\n * Copyright (C)"
},
{
"path": "app/src/main/cpp/apjni.hpp",
"chars": 827,
"preview": "//\n// Created by GarfieldHan on 2024/6/11.\n//\n\n#ifndef APATCH_APJNI_HPP\n#define APATCH_APJNI_HPP\n\n#include <jni.h>\n#incl"
},
{
"path": "app/src/main/cpp/jni_helper.hpp",
"chars": 51076,
"preview": "#pragma once\n\n#include <android/log.h>\n#include <jni.h>\n\n#include <string>\n#include <string_view>\n\n#include \"type_traits"
},
{
"path": "app/src/main/cpp/supercall.h",
"chars": 15387,
"preview": "/* SPDX-License-Identifier: GPL-2.0-or-later */\n/* \n * Copyright (C) 2023 bmax121. All Rights Reserved.\n */\n\n#ifndef _KP"
},
{
"path": "app/src/main/cpp/type_traits.hpp",
"chars": 421,
"preview": "#pragma once\n\n#include <type_traits>\n\nnamespace lsplant {\ntemplate <class, template <class, class...> class>\nstruct is_i"
},
{
"path": "app/src/main/cpp/uapi/scdefs.h",
"chars": 2961,
"preview": "/* SPDX-License-Identifier: GPL-2.0-or-later */\n/* \n * Copyright (C) 2023 bmax121. All Rights Reserved.\n */\n\n#ifndef _KP"
},
{
"path": "app/src/main/cpp/version",
"chars": 49,
"preview": "#define MAJOR 0\n#define MINOR 13\n#define PATCH 0\n"
},
{
"path": "app/src/main/java/me/bmax/apatch/APatchApp.kt",
"chars": 12213,
"preview": "package me.bmax.apatch\n\nimport android.app.Application\nimport android.content.Context\nimport android.content.Intent\nimpo"
},
{
"path": "app/src/main/java/me/bmax/apatch/Natives.kt",
"chars": 4705,
"preview": "package me.bmax.apatch\n\nimport android.os.Parcelable\nimport androidx.annotation.Keep\nimport androidx.compose.runtime.Imm"
},
{
"path": "app/src/main/java/me/bmax/apatch/services/RootServices.java",
"chars": 2331,
"preview": "package me.bmax.apatch.services;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content."
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/CrashHandleActivity.kt",
"chars": 5647,
"preview": "package me.bmax.apatch.ui\n\nimport android.content.ClipData\nimport android.os.Build\nimport android.os.Bundle\nimport andro"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/MainActivity.kt",
"chars": 15011,
"preview": "package me.bmax.apatch.ui\n\nimport android.annotation.SuppressLint\nimport android.content.res.Configuration\nimport androi"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/WebUIActivity.kt",
"chars": 9669,
"preview": "package me.bmax.apatch.ui\n\nimport android.annotation.SuppressLint\nimport android.app.ActivityManager\nimport android.cont"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/component/Dialog.kt",
"chars": 16564,
"preview": "package me.bmax.apatch.ui.component\n\nimport android.graphics.text.LineBreaker\nimport android.os.Build\nimport android.os."
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/component/DropdownMenu.kt",
"chars": 502,
"preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.shape.CornerBasedShape\nimport androidx.compose.f"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/component/KeyEventBlocker.kt",
"chars": 837,
"preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.focusable\nimport androidx.compose.foundation.lay"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/component/ModuleCardComponents.kt",
"chars": 2372,
"preview": "package me.bmax.apatch.ui.component\n\nimport androidx.annotation.DrawableRes\nimport androidx.compose.foundation.Image\nimp"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/component/SearchBar.kt",
"chars": 13662,
"preview": "package me.bmax.apatch.ui.component\n\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.animation.core"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/component/SettingsItem.kt",
"chars": 2427,
"preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.LocalIndication\nimport androidx.compose.foundati"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/component/WarningCard.kt",
"chars": 3788,
"preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.lay"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/APM.kt",
"chars": 29224,
"preview": "package me.bmax.apatch.ui.screen\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Context\nimport android.co"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/AboutScreen.kt",
"chars": 7851,
"preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.layout.Arr"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/BottomBarDestination.kt",
"chars": 2336,
"preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.annotation.StringRes\nimport androidx.compose.material.icons.Icons\nimpo"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/ExecuteAPMAction.kt",
"chars": 5555,
"preview": "package me.bmax.apatch.ui.screen\n\nimport android.os.Environment\nimport androidx.compose.foundation.layout.Column\nimport "
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/Home.kt",
"chars": 40016,
"preview": "package me.bmax.apatch.ui.screen\n\nimport android.os.Build\nimport android.system.Os\nimport androidx.annotation.StringRes\n"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/Install.kt",
"chars": 10288,
"preview": "package me.bmax.apatch.ui.screen\n\nimport android.content.Context\nimport android.net.Uri\nimport android.os.Environment\nim"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/InstallModeSelect.kt",
"chars": 7189,
"preview": "package me.bmax.apatch.ui.screen\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.net.Uri\nimpor"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/KPM.kt",
"chars": 24052,
"preview": "package me.bmax.apatch.ui.screen\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Intent\nimport android.net"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/Patches.kt",
"chars": 25979,
"preview": "package me.bmax.apatch.ui.screen\n\nimport android.Manifest\nimport android.app.Activity\nimport android.content.Intent\nimpo"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/Settings.kt",
"chars": 30547,
"preview": "package me.bmax.apatch.ui.screen\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport an"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/screen/SuperUser.kt",
"chars": 10177,
"preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.foundatio"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/AmberTheme.kt",
"chars": 6604,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/BlueGreyTheme.kt",
"chars": 6610,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/BlueTheme.kt",
"chars": 6603,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/BrownTheme.kt",
"chars": 6604,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/CyanTheme.kt",
"chars": 6602,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/DeepOrangeTheme.kt",
"chars": 6615,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/DeepPurpleTheme.kt",
"chars": 6614,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/GreenTheme.kt",
"chars": 6604,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/IndigoTheme.kt",
"chars": 6606,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/LightBlueTheme.kt",
"chars": 6613,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/LightGreenTheme.kt",
"chars": 6614,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/LimeTheme.kt",
"chars": 6602,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/OrangeTheme.kt",
"chars": 6606,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/PinkTheme.kt",
"chars": 6602,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/PurpleTheme.kt",
"chars": 6606,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/RedTheme.kt",
"chars": 6600,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/SakuraTheme.kt",
"chars": 6606,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/TealTheme.kt",
"chars": 6603,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/Theme.kt",
"chars": 5947,
"preview": "package me.bmax.apatch.ui.theme\n\nimport android.os.Build\nimport androidx.activity.ComponentActivity\nimport androidx.acti"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/Type.kt",
"chars": 965,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontFami"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/theme/YellowTheme.kt",
"chars": 6606,
"preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.lig"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/APModuleViewModel.kt",
"chars": 6492,
"preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.os.SystemClock\nimport android.util.Log\nimport androidx.compose.runti"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/KPModel.kt",
"chars": 1452,
"preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.os.Parcelable\nimport androidx.annotation.Keep\nimport androidx.compos"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/KPModuleViewModel.kt",
"chars": 3687,
"preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.os.SystemClock\nimport android.util.Log\nimport androidx.compose.runti"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/PatchesViewModel.kt",
"chars": 22450,
"preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.ContentValues\nimport android.content.Context\nimport android."
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/SuperUserViewModel.kt",
"chars": 6030,
"preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android."
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/webui/AppIconUtil.kt",
"chars": 1629,
"preview": "package me.bmax.apatch.ui.webui\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.C"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/webui/Insets.kt",
"chars": 1825,
"preview": "package me.bmax.apatch.ui.webui\n\n/**\n * Insets data class from GitHub@MMRLApp/WebUI-X-Portable\n *\n * Data class represen"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/webui/MimeUtil.java",
"chars": 4346,
"preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/webui/MonetColorsProvider.kt",
"chars": 5146,
"preview": "package me.bmax.apatch.ui.webui\n\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.surfa"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/webui/SuFilePathHandler.java",
"chars": 9991,
"preview": "package me.bmax.apatch.ui.webui;\n\nimport android.content.Context;\nimport android.util.Log;\nimport android.webkit.WebReso"
},
{
"path": "app/src/main/java/me/bmax/apatch/ui/webui/WebViewInterface.kt",
"chars": 8252,
"preview": "package me.bmax.apatch.ui.webui\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.pm.Ap"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/APatchCli.kt",
"chars": 13912,
"preview": "package me.bmax.apatch.util\n\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.conten"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/APatchKeyHelper.java",
"chars": 5490,
"preview": "package me.bmax.apatch.util;\n\nimport android.content.SharedPreferences;\nimport android.security.keystore.KeyGenParameter"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/DeviceInfoUtils.kt",
"chars": 1665,
"preview": "package me.bmax.apatch.util\n\nimport android.util.Log\nimport androidx.compose.runtime.Composable\nimport androidx.compose."
},
{
"path": "app/src/main/java/me/bmax/apatch/util/Downloader.kt",
"chars": 5094,
"preview": "package me.bmax.apatch.util\n\nimport android.annotation.SuppressLint\nimport android.app.DownloadManager\nimport android.co"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/HanziToPinyin.java",
"chars": 27670,
"preview": "package me.bmax.apatch.util;\n/*\n * Copyright (C) 2009 The Android Open Source Project\n *\n * Licensed under the Apache Li"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/IOStreamUtils.kt",
"chars": 1076,
"preview": "package me.bmax.apatch.util\n\nimport android.content.ContentResolver\nimport android.net.Uri\nimport me.bmax.apatch.apApp\ni"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/LatestVersionInfo.kt",
"chars": 147,
"preview": "package me.bmax.apatch.util\n\ndata class LatestVersionInfo(\n val versionCode: Int = 0, val downloadUrl: String = \"\", v"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/LogEvent.kt",
"chars": 4324,
"preview": "package me.bmax.apatch.util\n\nimport android.content.Context\nimport android.os.Build\nimport android.system.Os\nimport com."
},
{
"path": "app/src/main/java/me/bmax/apatch/util/PkgConfig.kt",
"chars": 2858,
"preview": "package me.bmax.apatch.util\n\nimport android.os.Parcelable\nimport android.util.Log\nimport androidx.annotation.Keep\nimport"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/Version.kt",
"chars": 4869,
"preview": "package me.bmax.apatch.util\n\nimport android.util.Log\nimport androidx.core.content.pm.PackageInfoCompat\nimport me.bmax.ap"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/ui/APDialogBlurBehindUtils.kt",
"chars": 4656,
"preview": "package me.bmax.apatch.util.ui\n\nimport android.animation.ValueAnimator\nimport android.annotation.SuppressLint\nimport and"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/ui/CompositionProvider.kt",
"chars": 268,
"preview": "package me.bmax.apatch.util.ui\n\nimport androidx.compose.material3.SnackbarHostState\nimport androidx.compose.runtime.comp"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/ui/HyperlinkText.kt",
"chars": 2921,
"preview": "package me.bmax.apatch.util.ui\n\nimport androidx.compose.foundation.gestures.detectTapGestures\nimport androidx.compose.ma"
},
{
"path": "app/src/main/java/me/bmax/apatch/util/ui/NavigationBarsSpacer.kt",
"chars": 685,
"preview": "package me.bmax.apatch.util.ui\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout."
},
{
"path": "app/src/main/res/drawable/device_mobile_down.xml",
"chars": 1225,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/github.xml",
"chars": 1061,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "app/src/main/res/drawable/ic_launcher_background.xml",
"chars": 517,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n"
},
{
"path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
"chars": 14575,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n"
},
{
"path": "app/src/main/res/drawable/ic_launcher_monochrome.xml",
"chars": 14603,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n"
},
{
"path": "app/src/main/res/drawable/info_circle_filled.xml",
"chars": 862,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/launcher_splash.xml",
"chars": 212,
"preview": "<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item android:drawable=\"@color/ic_launcher_b"
},
{
"path": "app/src/main/res/drawable/package_import.xml",
"chars": 1394,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/play_circle.xml",
"chars": 644,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/settings.xml",
"chars": 1132,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/telegram.xml",
"chars": 1104,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/trash.xml",
"chars": 1231,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/weblate.xml",
"chars": 3721,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.com/aapt\"\n "
},
{
"path": "app/src/main/res/drawable/webui.xml",
"chars": 1414,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/mipmap-anydpi/ic_launcher.xml",
"chars": 337,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/resources.properties",
"chars": 23,
"preview": "unqualifiedResLocale=en"
},
{
"path": "app/src/main/res/values/arrays.xml",
"chars": 1605,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string-array name=\"languages\">\n <item>@string/system_defa"
},
{
"path": "app/src/main/res/values/ic_launcher_background.xml",
"chars": 120,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"ic_launcher_background\">#081B11</color>\n</resources>"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 15493,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n\n "
},
{
"path": "app/src/main/res/values/themes.xml",
"chars": 755,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <style name=\"Them"
},
{
"path": "app/src/main/res/values-ab/strings.xml",
"chars": 232,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"su_pkg_excluded_setting_summary\">\\t</string>\n <s"
},
{
"path": "app/src/main/res/values-apc/strings.xml",
"chars": 63,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
},
{
"path": "app/src/main/res/values-ar/strings.xml",
"chars": 14288,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-arq/strings.xml",
"chars": 62,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
},
{
"path": "app/src/main/res/values-az/strings.xml",
"chars": 15236,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-bn/strings.xml",
"chars": 15250,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-ca/strings.xml",
"chars": 7329,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"home\">Inici</string>\n <string name=\"failure\">Ha "
},
{
"path": "app/src/main/res/values-cs/strings.xml",
"chars": 14617,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"patch_warnning\">Při instalaci může dojít k problémů"
},
{
"path": "app/src/main/res/values-da/strings.xml",
"chars": 1564,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"home\">Hjem</string>\n <string name=\"failure\">Fejl"
},
{
"path": "app/src/main/res/values-de/strings.xml",
"chars": 15890,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-el/strings.xml",
"chars": 16344,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-es/strings.xml",
"chars": 16329,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"apm\">APModule</string>\n <string name=\"super_key\""
},
{
"path": "app/src/main/res/values-fa/strings.xml",
"chars": 14223,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"home\">خانه</string>\n <string name=\"success\">موفق"
},
{
"path": "app/src/main/res/values-fi/strings.xml",
"chars": 15010,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"settings\">Asetukset</string>\n <string name=\"kern"
},
{
"path": "app/src/main/res/values-fil/strings.xml",
"chars": 6211,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-fr/strings.xml",
"chars": 16174,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-gl/strings.xml",
"chars": 973,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-hr/strings.xml",
"chars": 13794,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-hu/strings.xml",
"chars": 10443,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-in/strings.xml",
"chars": 14824,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"patch_warnning\">Berisiko! Pastikan data anda telah "
},
{
"path": "app/src/main/res/values-it/strings.xml",
"chars": 15937,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"apm_reboot_to_apply\">Riavvia per rendere effettive "
},
{
"path": "app/src/main/res/values-ja/strings.xml",
"chars": 13270,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-jv/strings.xml",
"chars": 1271,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-ko/strings.xml",
"chars": 12524,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-lt/strings.xml",
"chars": 411,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"patch_warnning\">Diegimas rizikingas. Patikrinkite, "
},
{
"path": "app/src/main/res/values-lv/strings.xml",
"chars": 4292,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"success\">Veiksmīgi</string>\n <string name=\"patch"
},
{
"path": "app/src/main/res/values-ms/strings.xml",
"chars": 4694,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-nb-rNO/strings.xml",
"chars": 11188,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-night/themes.xml",
"chars": 127,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <style name=\"Theme.APatch.WebUI\" parent=\"Theme.APatch\" />\n\n</res"
},
{
"path": "app/src/main/res/values-nl/strings.xml",
"chars": 15840,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-pl/strings.xml",
"chars": 15734,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"apm\">Moduły AP</string>\n <string name=\"super_key"
},
{
"path": "app/src/main/res/values-pt/strings.xml",
"chars": 15617,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-pt-rBR/strings.xml",
"chars": 15540,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-ro/strings.xml",
"chars": 15689,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-ru/strings.xml",
"chars": 15646,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-si/strings.xml",
"chars": 14049,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-sv/strings.xml",
"chars": 12563,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"kpm_embed\">Bygg in</string>\n <string name=\"home\""
},
{
"path": "app/src/main/res/values-ta/strings.xml",
"chars": 15425,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"patch_item_extra_kpm_license\">உரிமம்:</string>\n "
},
{
"path": "app/src/main/res/values-th/strings.xml",
"chars": 14432,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"patch_warnning\">APatch</string>\n <string name=\"h"
},
{
"path": "app/src/main/res/values-tr/strings.xml",
"chars": 15479,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-uk/strings.xml",
"chars": 15611,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-vi/strings.xml",
"chars": 15305,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/values-zh-rCN/strings.xml",
"chars": 12629,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"apm\">系统模块</string>\n <string name=\"super_key\">超级密"
},
{
"path": "app/src/main/res/values-zh-rTW/strings.xml",
"chars": 12043,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">APatch</string>\n "
},
{
"path": "app/src/main/res/xml/backup_rules.xml",
"chars": 478,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n Sample backup rules file; uncomment and customize as necessary.\n See htt"
},
{
"path": "app/src/main/res/xml/data_extraction_rules.xml",
"chars": 551,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n Sample data extraction rules file; uncomment and customize as necessary.\n "
},
{
"path": "app/src/main/res/xml/file_paths.xml",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<paths>\n <cache-path name=\"cache\" path=\".\"/>\n <root-path name=\"root\" path="
},
{
"path": "app/src/main/res/xml/network_security_config.xml",
"chars": 337,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n <domain-config cleartextTrafficPermitted=\"true\">\n "
},
{
"path": "build.gradle.kts",
"chars": 1332,
"preview": "plugins {\n alias(libs.plugins.agp.app) apply false\n alias(libs.plugins.kotlin) apply false\n alias(libs.plugins."
},
{
"path": "docs/BG/faq_bg.md",
"chars": 2080,
"preview": "# ЧЗВ\n\n\n## Какво е APatch?\n\nAPatch е root решение, подобно на Magisk или KernelSU, което обединява най-доброто от двете."
},
{
"path": "docs/ar/faq_ar.md",
"chars": 2061,
"preview": "# الأسئلة الشائعة (FAQ)\n\n## ما هو أباتش؟\nأباتش هو حل روت شبيه بـ ماجيسك أو كيرنل إس يو، يجمع بين أفضل ما في كليهما. \nيس"
},
{
"path": "docs/az/faq_az.md",
"chars": 2378,
"preview": "# TSS\n\n\n## APatch nədir?\nAPatch, hər ikisinin ən yaxşısını birləşdirən Magisk və ya KernelSU-ya bənzər kök həllidir.\nO, "
},
{
"path": "docs/cn/ap_module.md",
"chars": 11398,
"preview": "# 模块(APM)开发指南 {#introduction}\n\nAPatch 提供了一个模块机制( AndroidPatch Module),它可以在保持系统分区完整性的同时达到修改系统分区的效果;这种机制通常被称之为 systemless。"
},
{
"path": "docs/cn/faq_cn.md",
"chars": 1214,
"preview": "# 常见问题解答\n\n## 什么是APatch?\n\nAPatch是一种类似于Magisk或KernelSU的root解决方案,但APatch提供更多功能。\nAPatch分别结合了Magisk方便易用的通过`boot.img`安装的方法,和Ke"
},
{
"path": "docs/cn_tw/faq_cn_tw.md",
"chars": 1249,
"preview": "# 〈常見問題集〉\n\n\n## 什麼是 APatch?\nAPatch 為一套汲取 Magisk、KernelSU 優勢於一身的 Root 解決方案。\n不僅保留 Magisk 自身便捷、修補 `boot.img` 即用的特性,也有 Kernel"
},
{
"path": "docs/de/faq_de.md",
"chars": 2441,
"preview": "# Häufig gestellte Fragen\n\n\n## Was ist APatch?\nAPatch ist eine Root-Lösung, ähnlich wie Magisk oder KernelSU, welche das"
},
{
"path": "docs/en/faq.md",
"chars": 2186,
"preview": "# FAQ\n\n\n## What is APatch?\nAPatch is a root solution similar to Magisk or KernelSU that unites the best of both.\nIt comb"
},
{
"path": "docs/es/faq_es.md",
"chars": 3098,
"preview": "# Preguntas frecuentes\n\n## ¿Qué es APatch?\nAPatch es una solución de root similar a Magisk o KernelSU que une lo mejor d"
}
]
// ... and 20 more files (download for full content)
About this extraction
This page contains the full source code of the bmax121/APatch GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 220 files (1.4 MB), approximately 404.1k tokens, and a symbol index with 575 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.