Full Code of matsuzaka-yuki/FolkPatch for AI

main f62ce3b9a7ce cached
309 files
3.7 MB
986.9k tokens
700 symbols
1 requests
Download .txt
Showing preview only (4,168K chars total). Download the full file or copy to clipboard to get everything.
Repository: matsuzaka-yuki/FolkPatch
Branch: main
Commit: f62ce3b9a7ce
Files: 309
Total size: 3.7 MB

Directory structure:
gitextract_v7dadxbv/

├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── actions/
│   │   └── setup-build-env/
│   │       └── action.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── CI_up.yml
│       └── build.yml
├── .gitignore
├── Build-Debug.bat
├── Build-Release.bat
├── LICENSE
├── README.md
├── README_EN.md
├── README_JA.md
├── apd/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── build.rs
│   └── src/
│       ├── apd.rs
│       ├── assets.rs
│       ├── banner
│       ├── cli.rs
│       ├── defs.rs
│       ├── event.rs
│       ├── install_jq.sh
│       ├── installer.sh
│       ├── installer_bind.sh
│       ├── lua.rs
│       ├── magic_mount.rs
│       ├── main.rs
│       ├── metamodule.rs
│       ├── module.rs
│       ├── module_config.rs
│       ├── package.rs
│       ├── pty.rs
│       ├── resetprop.rs
│       ├── restorecon.rs
│       ├── sepolicy.rs
│       ├── supercall.rs
│       └── utils.rs
├── app/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── libs/
│   │   └── arm64-v8a/
│   │       ├── .gitignore
│   │       ├── libkpatch.so.version
│   │       └── libkptools.so.version
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── aidl/
│           │   └── me/
│           │       └── bmax/
│           │           └── apatch/
│           │               └── IAPRootService.aidl
│           ├── assets/
│           │   ├── .gitignore
│           │   ├── InstallAP.sh
│           │   ├── UninstallAP.sh
│           │   ├── boot_extract.sh
│           │   ├── boot_flash.sh
│           │   ├── boot_patch.sh
│           │   ├── boot_unpatch.sh
│           │   ├── jq/
│           │   │   └── jq
│           │   ├── kpimg.version
│           │   └── util_functions.sh
│           ├── cpp/
│           │   ├── CMakeLists.txt
│           │   ├── apjni.cpp
│           │   ├── apjni.hpp
│           │   ├── jni_helper.hpp
│           │   ├── security.cpp
│           │   ├── security.hpp
│           │   ├── supercall.h
│           │   ├── type_traits.hpp
│           │   ├── uapi/
│           │   │   └── scdefs.h
│           │   └── version
│           ├── java/
│           │   └── me/
│           │       └── bmax/
│           │           └── apatch/
│           │               ├── APatchApp.kt
│           │               ├── Natives.kt
│           │               ├── data/
│           │               │   └── ScriptInfo.kt
│           │               ├── services/
│           │               │   └── RootServices.java
│           │               ├── ui/
│           │               │   ├── CrashHandleActivity.kt
│           │               │   ├── MainActivity.kt
│           │               │   ├── WebUIActivity.kt
│           │               │   ├── component/
│           │               │   │   ├── Dialog.kt
│           │               │   │   ├── DropdownMenu.kt
│           │               │   │   ├── ExpressiveCard.kt
│           │               │   │   ├── ExpressiveSwitch.kt
│           │               │   │   ├── FilePicker.kt
│           │               │   │   ├── KeyEventBlocker.kt
│           │               │   │   ├── KpmAutoLoadConfig.kt
│           │               │   │   ├── LoadingIndicator.kt
│           │               │   │   ├── ModuleCardComponents.kt
│           │               │   │   ├── SearchBar.kt
│           │               │   │   ├── SectionHeader.kt
│           │               │   │   ├── SegmentedControl.kt
│           │               │   │   ├── SettingsItem.kt
│           │               │   │   ├── SplicedColumnGroup.kt
│           │               │   │   ├── SplicedLazyColumn.kt
│           │               │   │   ├── ThemeColorPicker.kt
│           │               │   │   ├── ThemeModeSelector.kt
│           │               │   │   ├── ToggleSettingCard.kt
│           │               │   │   ├── TwoColumnGrid.kt
│           │               │   │   ├── UmountConfig.kt
│           │               │   │   ├── UpdateDialog.kt
│           │               │   │   ├── WarningCard.kt
│           │               │   │   └── chart/
│           │               │   │       ├── ChartUtils.kt
│           │               │   │       ├── ModulePieChart.kt
│           │               │   │       ├── StorageColumnChart.kt
│           │               │   │       ├── SystemAreaChart.kt
│           │               │   │       └── SystemLineChart.kt
│           │               │   ├── model/
│           │               │   │   └── ApiMarketplaceItem.kt
│           │               │   ├── screen/
│           │               │   │   ├── APM.kt
│           │               │   │   ├── AboutScreen.kt
│           │               │   │   ├── ApiMarketplace.kt
│           │               │   │   ├── ApmBulkInstallScreen.kt
│           │               │   │   ├── AppProfile.kt
│           │               │   │   ├── BannerApiService.kt
│           │               │   │   ├── BottomBarDestination.kt
│           │               │   │   ├── ExecuteAPMAction.kt
│           │               │   │   ├── Home.kt
│           │               │   │   ├── HomeCircle.kt
│           │               │   │   ├── HomeStats.kt
│           │               │   │   ├── HomeV2.kt
│           │               │   │   ├── HomeV3.kt
│           │               │   │   ├── HomeV4.kt
│           │               │   │   ├── Install.kt
│           │               │   │   ├── InstallModeSelect.kt
│           │               │   │   ├── KPM.kt
│           │               │   │   ├── KpmAutoLoadConfigScreen.kt
│           │               │   │   ├── MyThemesScreen.kt
│           │               │   │   ├── OnlineKPMScreen.kt
│           │               │   │   ├── OnlineModuleScreen.kt
│           │               │   │   ├── OnlineScriptScreen.kt
│           │               │   │   ├── Patches.kt
│           │               │   │   ├── ScriptExecutionLogScreen.kt
│           │               │   │   ├── ScriptLibrary.kt
│           │               │   │   ├── Settings.kt
│           │               │   │   ├── SuAuditLogScreen.kt
│           │               │   │   ├── SuperUser.kt
│           │               │   │   ├── ThemeStore.kt
│           │               │   │   └── settings/
│           │               │   │       ├── AppearanceSettings.kt
│           │               │   │       ├── AppearanceSettingsScreen.kt
│           │               │   │       ├── BackupSettings.kt
│           │               │   │       ├── BackupSettingsScreen.kt
│           │               │   │       ├── BehaviorSettings.kt
│           │               │   │       ├── BehaviorSettingsScreen.kt
│           │               │   │       ├── FunctionSettings.kt
│           │               │   │       ├── FunctionSettingsScreen.kt
│           │               │   │       ├── GeneralSettings.kt
│           │               │   │       ├── GeneralSettingsScreen.kt
│           │               │   │       ├── LanguagePickerScreen.kt
│           │               │   │       ├── ModuleSettings.kt
│           │               │   │       ├── ModuleSettingsScreen.kt
│           │               │   │       ├── MultimediaSettings.kt
│           │               │   │       ├── MultimediaSettingsScreen.kt
│           │               │   │       ├── SecuritySettings.kt
│           │               │   │       ├── SecuritySettingsScreen.kt
│           │               │   │       └── SettingsShared.kt
│           │               │   ├── theme/
│           │               │   │   ├── AmberTheme.kt
│           │               │   │   ├── BackgroundConfig.kt
│           │               │   │   ├── BackgroundLayer.kt
│           │               │   │   ├── BackupConfig.kt
│           │               │   │   ├── BlueGreyTheme.kt
│           │               │   │   ├── BlueTheme.kt
│           │               │   │   ├── BrownTheme.kt
│           │               │   │   ├── CardManage.kt
│           │               │   │   ├── CyanTheme.kt
│           │               │   │   ├── DeepOrangeTheme.kt
│           │               │   │   ├── DeepPurpleTheme.kt
│           │               │   │   ├── FontConfig.kt
│           │               │   │   ├── GreenTheme.kt
│           │               │   │   ├── IndigoTheme.kt
│           │               │   │   ├── InkWashTheme.kt
│           │               │   │   ├── LightBlueTheme.kt
│           │               │   │   ├── LightGreenTheme.kt
│           │               │   │   ├── LimeTheme.kt
│           │               │   │   ├── MusicConfig.kt
│           │               │   │   ├── OrangeTheme.kt
│           │               │   │   ├── PinkTheme.kt
│           │               │   │   ├── PurpleTheme.kt
│           │               │   │   ├── RedTheme.kt
│           │               │   │   ├── SakuraTheme.kt
│           │               │   │   ├── SoundEffectConfig.kt
│           │               │   │   ├── TealTheme.kt
│           │               │   │   ├── Theme.kt
│           │               │   │   ├── ThemeManager.kt
│           │               │   │   ├── Type.kt
│           │               │   │   ├── VibrationConfig.kt
│           │               │   │   └── YellowTheme.kt
│           │               │   ├── viewmodel/
│           │               │   │   ├── APModuleViewModel.kt
│           │               │   │   ├── ApiMarketplaceViewModel.kt
│           │               │   │   ├── DashboardViewModel.kt
│           │               │   │   ├── KPModel.kt
│           │               │   │   ├── KPModuleViewModel.kt
│           │               │   │   ├── OnlineKPMViewModel.kt
│           │               │   │   ├── OnlineModuleViewModel.kt
│           │               │   │   ├── OnlineScriptViewModel.kt
│           │               │   │   ├── PatchesViewModel.kt
│           │               │   │   ├── ScriptLibraryViewModel.kt
│           │               │   │   ├── SuperUserViewModel.kt
│           │               │   │   └── ThemeStoreViewModel.kt
│           │               │   └── webui/
│           │               │       ├── AppIconUtil.kt
│           │               │       ├── MimeUtil.java
│           │               │       ├── MonetColorsProvider.kt
│           │               │       ├── SuFilePathHandler.java
│           │               │       └── WebViewInterface.kt
│           │               └── util/
│           │                   ├── APatchCli.kt
│           │                   ├── APatchKeyHelper.java
│           │                   ├── AppData.kt
│           │                   ├── BackupLogManager.kt
│           │                   ├── BiometricUtils.kt
│           │                   ├── BulkInstallManager.kt
│           │                   ├── ComposePrefs.kt
│           │                   ├── DPIUtils.kt
│           │                   ├── DeviceInfoUtils.kt
│           │                   ├── Downloader.kt
│           │                   ├── FolkApiClient.kt
│           │                   ├── HanziToPinyin.java
│           │                   ├── HardwareMonitor.kt
│           │                   ├── IOStreamUtils.kt
│           │                   ├── LauncherIconUtils.kt
│           │                   ├── LogEvent.kt
│           │                   ├── ModuleBackupUtils.kt
│           │                   ├── ModuleShortcut.kt
│           │                   ├── MusicManager.kt
│           │                   ├── PermissionUtils.kt
│           │                   ├── PkgConfig.kt
│           │                   ├── SafeFileProvider.kt
│           │                   ├── SafeUriResolver.kt
│           │                   ├── ScriptLibraryManager.kt
│           │                   ├── SoundEffectManager.kt
│           │                   ├── SuAuditLog.kt
│           │                   ├── ThemeDownloader.kt
│           │                   ├── UpdateChecker.kt
│           │                   ├── Version.kt
│           │                   ├── VibrationManager.kt
│           │                   ├── WebDavUtils.kt
│           │                   └── ui/
│           │                       ├── APDialogBlurBehindUtils.kt
│           │                       ├── AnsiUtils.kt
│           │                       ├── CompositionProvider.kt
│           │                       ├── GlassEffectHelper.kt
│           │                       ├── HomeBottomSpacer.kt
│           │                       ├── HyperlinkText.kt
│           │                       ├── NavigationBarsSpacer.kt
│           │                       └── ToastExt.kt
│           └── res/
│               ├── drawable/
│               │   ├── device_mobile_down.xml
│               │   ├── github.xml
│               │   ├── ic_clear_background.xml
│               │   ├── ic_custom_background.xml
│               │   ├── ic_launcher_foreground_alt.xml
│               │   ├── ic_launcher_monochrome.xml
│               │   ├── ic_launcher_monochrome_alt.xml
│               │   ├── info_circle_filled.xml
│               │   ├── package_import.xml
│               │   ├── play_circle.xml
│               │   ├── settings.xml
│               │   ├── telegram.xml
│               │   ├── trash.xml
│               │   └── webui.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   ├── ic_launcher_alt.xml
│               │   ├── ic_launcher_alt_round.xml
│               │   └── ic_launcher_round.xml
│               ├── resources.properties
│               ├── values/
│               │   ├── arrays.xml
│               │   ├── colors.xml
│               │   ├── ic_launcher_background.xml
│               │   ├── strings.xml
│               │   └── themes.xml
│               ├── values-ar/
│               │   └── strings.xml
│               ├── values-es/
│               │   └── strings.xml
│               ├── values-in/
│               │   └── strings.xml
│               ├── values-ja/
│               │   └── strings.xml
│               ├── values-ko/
│               │   └── strings.xml
│               ├── values-mgl/
│               │   └── strings.xml
│               ├── values-night/
│               │   └── themes.xml
│               ├── values-pl/
│               │   └── strings.xml
│               ├── values-pt-rBR/
│               │   └── strings.xml
│               ├── values-ru/
│               │   └── strings.xml
│               ├── values-tr-rTR/
│               │   └── strings.xml
│               ├── values-vi/
│               │   └── strings.xml
│               ├── values-zh-rAG/
│               │   └── strings.xml
│               ├── values-zh-rAT/
│               │   └── strings.xml
│               ├── values-zh-rCK/
│               │   └── strings.xml
│               ├── values-zh-rCN/
│               │   └── strings.xml
│               ├── values-zh-rMC/
│               │   └── strings.xml
│               ├── values-zh-rTW/
│               │   └── strings.xml
│               ├── values-zh-rWC/
│               │   └── strings.xml
│               └── xml/
│                   ├── backup_rules.xml
│                   ├── data_extraction_rules.xml
│                   ├── file_paths.xml
│                   └── network_security_config.xml
├── auth.properties.template
├── build.gradle.kts
├── fastlane/
│   └── metadata/
│       └── android/
│           ├── en-US/
│           │   ├── full_description.txt
│           │   └── short_description.txt
│           └── pl-PL/
│               ├── full_description.txt
│               └── short_description.txt
├── fpd/
│   ├── Cargo.toml
│   └── src/
│       ├── main.rs
│       ├── prop_patch.rs
│       └── umount.rs
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── keystore.properties.template
├── local.properties.template
├── scripts/
│   ├── Build-Debug.sh
│   ├── Build-Release.sh
│   ├── init-wsl.sh
│   ├── setup-wsl.sh
│   ├── 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
*.wav 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.

        感谢给 FolkPatch 汇报问题!
        为了使我们更好地帮助你,请提供以下信息。
        为了防止重复汇报,标题请务必使用英文。

  - 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 | FolkPatch 版本
    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/FolkPatch
    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/actions/setup-build-env/action.yml
================================================
name: Setup Build Environment
description: Install all build dependencies (Java, Android SDK, Gradle, Rust toolchain)

runs:
  using: composite
  steps:
    - uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: '21'

    - uses: android-actions/setup-android@v3

    - uses: gradle/actions/setup-gradle@v3

    - uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        target: aarch64-linux-android
        override: true

    - name: Install cargo-ndk
      shell: bash
      run: cargo install cargo-ndk

    - name: Grant execute permission
      shell: bash
      run: chmod +x gradlew

    - uses: actions/cache@v4
      with:
        path: |
          ~/.gradle/caches
          ~/.gradle/wrapper
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: ${{ runner.os }}-gradle-


================================================
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/workflows/CI_up.yml
================================================
name: CI Upload to Telegram

on:
  workflow_call:
    inputs:
      artifact_names:
        required: true
        type: string
        description: 'Comma-separated artifact names to download'
      message:
        required: false
        type: string
        default: 'FolkPatch Build'
        description: 'Telegram message caption'

jobs:
  upload:
    name: Upload to Telegram
    runs-on: ubuntu-latest

    steps:
      - name: Check Telegram config
        id: tg_check
        run: |
          if [ -n "${{ secrets.TELEGRAM_BOT_TOKEN }}" ] && [ -n "${{ secrets.TELEGRAM_CHAT_ID }}" ]; then
            echo "enabled=true" >> "$GITHUB_OUTPUT"
          else
            echo "::notice::Telegram secrets not configured, skipping upload"
          fi

      - name: Checkout repository
        if: steps.tg_check.outputs.enabled == 'true'
        uses: actions/checkout@v5

      - name: Download artifacts
        if: steps.tg_check.outputs.enabled == 'true'
        uses: actions/download-artifact@v4
        with:
          pattern: folkpatch-*
          path: artifacts
          merge-multiple: true

      - name: Send to Telegram
        if: steps.tg_check.outputs.enabled == 'true'
        run: |
          FILES=$(find artifacts -type f 2>/dev/null | sort)
          FILE_COUNT=$(echo "$FILES" | grep -c . || true)
          
          if [ "$FILE_COUNT" -eq 0 ]; then
            echo "::error::No files found in artifacts"
            exit 1
          fi
          
          CAPTION="${{ inputs.message }}
          Branch: ${{ github.ref_name }}
          Commit: ${{ github.sha }}
          Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
          
          if [ "$FILE_COUNT" -eq 1 ]; then
            curl -sf -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument" \
              -F "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
              -F "document=@$FILES" \
              -F "caption=${CAPTION}"
          else
            FILE_ARR=()
            while IFS= read -r f; do
              FILE_ARR+=("$f")
            done <<< "$FILES"
            
            TOTAL=${#FILE_ARR[@]}
            MEDIA_JSON="["
            for i in "${!FILE_ARR[@]}"; do
              FNAME=$(basename "${FILE_ARR[$i]}")
              if [ $i -gt 0 ]; then
                MEDIA_JSON+=","
              fi
              if [ $((i + 1)) -eq $TOTAL ]; then
                MEDIA_JSON+="{\"type\":\"document\",\"media\":\"attach://$FNAME\",\"caption\":\"${CAPTION}\"}"
              else
                MEDIA_JSON+="{\"type\":\"document\",\"media\":\"attach://$FNAME\"}"
              fi
            done
            MEDIA_JSON+="]"
            
            ARGS=(-X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMediaGroup")
            ARGS+=(-F "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}")
            ARGS+=(-F "media=${MEDIA_JSON}")
            
            while IFS= read -r f; do
              FNAME=$(basename "$f")
              ARGS+=(-F "$FNAME=@$f")
            done <<< "$FILES"
            
            curl -sf "${ARGS[@]}"
          fi


================================================
FILE: .github/workflows/build.yml
================================================
name: Build FolkPatch

on:
  push:
    branches: [ main, develop ]
    paths: [ 'app/**', 'fpd/**', '.github/workflows/build.yml' ]
  pull_request:
    branches: [ main, develop ]
    paths: [ 'app/**', 'fpd/**', '.github/workflows/build.yml' ]
  workflow_dispatch:
    inputs:
      build_type:
        description: 'Build type'
        required: true
        default: 'both'
        type: choice
        options: [ both, debug, release ]

env:
  KEYSTORE_FILE: debug.keystore
  KEYSTORE_PASSWORD: android
  KEY_ALIAS: androiddebugkey
  KEY_PASSWORD: android

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      build_debug: ${{ steps.set.outputs.debug }}
      build_release: ${{ steps.set.outputs.release }}
      commit_msg: ${{ steps.msg.outputs.text }}
      commit_author: ${{ steps.msg.outputs.author }}
      artifact_list: ${{ steps.set.outputs.artifact_list }}

    steps:
      - id: set
        run: |
          TYPE="${{ inputs.build_type }}"
          [ "${{ github.event_name }}" != "workflow_dispatch" ] && TYPE="both"

          DEBUG="false"; RELEASE="false"; LIST=""
          [ "$TYPE" = "both" -o "$TYPE" = "debug" ] && { DEBUG="true"; LIST="folkpatch-debug-${{ github.sha }}"; }
          [ "$TYPE" = "both" -o "$TYPE" = "release" ] && {
            RELEASE="true"
            [ -n "$LIST" ] && LIST="$LIST," || true
            LIST="${LIST}folkpatch-release-${{ github.sha }}"
          }

          echo "debug=$DEBUG" >> "$GITHUB_OUTPUT"
          echo "release=$RELEASE" >> "$GITHUB_OUTPUT"
          echo "artifact_list=$LIST" >> "$GITHUB_OUTPUT"

      - id: msg
        run: |
          case "${{ github.event_name }}" in
            push)         TEXT="${{ github.event.head_commit.message }}"
                           AUTHOR="${{ github.event.head_commit.author.name }}" ;;
            pull_request) TEXT="PR: ${{ github.event.pull_request.title }}"
                           AUTHOR="${{ github.event.pull_request.user.login }}" ;;
            *)            TEXT="FolkPatch Build"
                           AUTHOR="${{ github.actor }}" ;;
          esac
          TEXT="${TEXT%%$'\n'*}"
          echo "text=$TEXT" >> "$GITHUB_OUTPUT"
          echo "author=$AUTHOR" >> "$GITHUB_OUTPUT"

  build_debug:
    name: Build Debug
    needs: prepare
    if: needs.prepare.outputs.build_debug == 'true'
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5
        with:
          submodules: recursive

      - uses: ./.github/actions/setup-build-env

      - run: ./gradlew assembleDebug --no-daemon

      - name: Rename APK
        run: |
          SHORT="${GITHUB_SHA::7}"
          APK=$(find app/build/outputs/apk/debug -name "*.apk" -type f | head -1)
          [ -n "$APK" ] && mv "$APK" "FolkPatch-Debug-${SHORT}.apk"

      - uses: actions/upload-artifact@v4
        with:
          name: folkpatch-debug-${{ github.sha }}
          path: FolkPatch-Debug-*.apk
          retention-days: 30

  build_release:
    name: Build Release
    needs: prepare
    if: needs.prepare.outputs.build_release == 'true'
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5
        with:
          submodules: recursive

      - uses: ./.github/actions/setup-build-env

      - name: Decode keystore
        run: |
          echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > FolkPatch.jks

      - name: Create keystore.properties
        run: |
          cat > keystore.properties << 'EOF'
          KEYSTORE_FILE=../FolkPatch.jks
          KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS=${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD=${{ secrets.KEY_PASSWORD }}
          EOF

      - run: ./gradlew assembleRelease --no-daemon

      - name: Debug APK location
        run: find app/build/outputs -name "*.apk" -type f || echo "No APK found"

      - name: Rename APK
        run: |
          SHORT="${GITHUB_SHA::7}"
          APK=$(find app/build/outputs/apk/release -name "*.apk" -type f | head -1)
          [ -n "$APK" ] && mv "$APK" "FolkPatch-Release-${SHORT}.apk"

      - uses: actions/upload-artifact@v4
        with:
          name: folkpatch-release-${{ github.sha }}
          path: FolkPatch-Release-*.apk
          retention-days: 30

  upload:
    name: Upload to Telegram
    needs: [prepare, build_debug, build_release]
    if: always() && needs.prepare.result == 'success' && (needs.build_debug.result == 'success' || needs.build_release.result == 'success') && github.actor != 'dependabot[bot]'
    runs-on: ubuntu-latest

    steps:
      - name: Check Telegram config
        id: tg_check
        run: |
          if [ -n "${{ secrets.TELEGRAM_BOT_TOKEN }}" ] && [ -n "${{ secrets.TELEGRAM_CHAT_ID }}" ]; then
            echo "enabled=true" >> "$GITHUB_OUTPUT"
          else
            echo "::notice::Telegram secrets not configured, skipping upload"
          fi

      - name: Download artifacts
        if: steps.tg_check.outputs.enabled == 'true'
        uses: actions/download-artifact@v4
        with:
          pattern: folkpatch-*
          path: artifacts
          merge-multiple: true

      - name: Send to Telegram
        if: steps.tg_check.outputs.enabled == 'true'
        run: |
          FILES=$(find artifacts -type f 2>/dev/null | sort)
          FILE_COUNT=$(echo "$FILES" | grep -c . || true)
          
          if [ "$FILE_COUNT" -eq 0 ]; then
            echo "::error::No files found in artifacts"
            exit 1
          fi
          
          CAPTION="${{ needs.prepare.outputs.commit_msg }}\nBranch: ${{ github.ref_name }}\nCommit: ${{ github.sha }}\nAuthor: ${{ needs.prepare.outputs.commit_author }}\nRun: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
          
          if [ "$FILE_COUNT" -eq 1 ]; then
            curl -sf -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument" \
              -F "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
              -F "document=@$FILES" \
              -F "caption=${CAPTION}"
          else
            FILE_ARR=()
            while IFS= read -r f; do
              FILE_ARR+=("$f")
            done <<< "$FILES"
            
            TOTAL=${#FILE_ARR[@]}
            MEDIA_JSON="["
            for i in "${!FILE_ARR[@]}"; do
              FNAME=$(basename "${FILE_ARR[$i]}")
              if [ $i -gt 0 ]; then
                MEDIA_JSON+=","
              fi
              if [ $((i + 1)) -eq $TOTAL ]; then
                MEDIA_JSON+="{\"type\":\"document\",\"media\":\"attach://$FNAME\",\"caption\":\"${CAPTION}\"}"
              else
                MEDIA_JSON+="{\"type\":\"document\",\"media\":\"attach://$FNAME\"}"
              fi
            done
            MEDIA_JSON+="]"
            
            ARGS=(-X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMediaGroup")
            ARGS+=(-F "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}")
            ARGS+=(-F "media=${MEDIA_JSON}")
            
            while IFS= read -r f; do
              FNAME=$(basename "$f")
              ARGS+=(-F "$FNAME=@$f")
            done <<< "$FILES"
            
            curl -sf "${ARGS[@]}"
          fi


================================================
FILE: .gitignore
================================================
*.iml
.gradle
.idea
.DS_Store
build
captures
.cxx
*.keystore
*.jks
keystore.properties
local.properties
auth.properties

# FolkPatch specific signing files
app/folkpatch-release*.jks
keystore.properties
.vscode
.kotlin
app/src/main/resources/
private
.claude/
.opencode

# Built native binaries
app/src/main/assets/Service/
fpd/target/
fpd/.cargo/

# demo project
demo
KernelPatch

================================================
FILE: Build-Debug.bat
================================================
@echo off
chcp 65001 > nul 2>&1
setlocal enabledelayedexpansion

echo [1/4] Entering apd directory...
cd /d apd
if errorlevel 1 (
    echo Error: Failed to enter apd directory, please check if the directory exists!
    pause
    exit /b 1
)

echo [2/4] Executing cargo clean...
cargo clean
if errorlevel 1 (
    echo Error: cargo clean execution failed!
    pause
    exit /b 1
)

echo [3/4] Returning to parent directory...
cd ..
if errorlevel 1 (
    echo Error: Failed to return to parent directory!
    pause
    exit /b 1
)

echo [4/4] Executing gradlew.bat assembleDebug...
.\gradlew.bat assembleDebug
if errorlevel 1 (
    echo Error: gradlew.bat assembleDebug execution failed!
    pause
    exit /b 1
)

echo.
echo All commands executed successfully!
pause
endlocal

================================================
FILE: Build-Release.bat
================================================
@echo off
chcp 65001 > nul 2>&1
setlocal enabledelayedexpansion

echo [1/4] Entering apd directory...
cd /d apd
if errorlevel 1 (
    echo Error: Failed to enter apd directory, please check if the directory exists!
    pause
    exit /b 1
)

echo [2/4] Executing cargo clean...
cargo clean
if errorlevel 1 (
    echo Error: cargo clean execution failed!
    pause
    exit /b 1
)

echo [3/4] Returning to parent directory...
cd ..
if errorlevel 1 (
    echo Error: Failed to return to parent directory!
    pause
    exit /b 1
)

echo [4/4] Executing gradlew.bat assembleRelease...
.\gradlew.bat assembleRelease
if errorlevel 1 (
    echo Error: gradlew.bat assembleDebug execution failed!
    pause
    exit /b 1
)

echo.
echo All commands executed successfully!
pause
endlocal

================================================
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">
<img src='logo.png' width='500px' alt="FolkPatch logo">

[![Latest Release](https://img.shields.io/github/v/release/matsuzaka-yuki/FolkPatch?label=Release&logo=github)](https://github.com/LyraVoid/FolkPatch/releases/latest)
[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/FolkPatch)
[![GitHub License](https://img.shields.io/github/license/matsuzaka-yuki/FolkPatch?logo=gnu)](/LICENSE)

</div>

🌏 **README 语言:** [**English**](./README_EN.md) / [**中文**](./README.md) / [**日本語**](./README_JA.md)

FolkPatch - 专注界面优化与功能扩展的Root管理工具

通过我们的综合文档快速开始。无论是安装使用、模块管理,还是自定义设置,文档涵盖了您成功使用FolkPatch所需的所有内容。

[📚 阅读完整文档](https://fp.mysqil.com/) →

<table>
  <tr>
    <td><img alt="" src="docs/1.png"></td>
    <td><img alt="" src="docs/2.png"></td>
    <td><img alt="" src="docs/3.png"></td>
  <tr>
  <tr>
    <td><img alt="" src="docs/4.png"></td>
    <td><img alt="" src="docs/5.png"></td>
    <td><img alt="" src="docs/6.png"></td>
  <tr>
</table>

---

## ✨ 介绍

### 🎨 核心功能
- [x] 基于 KernelPatch 的 Root 实现
- [x] 无需重新编译内核即可 Hook 内核函数

### 📱 前置要求

- **必须:** 基于 ARM64 架构且 Linux 内核版本 3.18 至 6.15 的 Android 设备

### 🎨 管理器的界面与设计
- [x] 全新的 UI 与交互体验优化
- [x] 个性化壁纸支持
- [x] 国际化支持
- [x] 动画性能与交互流畅度优化
- [x] 界面视觉细节与动态效果提升
- [x] 支持手动关闭自动更新检查,将版本升级的主导权交还给用户

### 📦 模块相关
- [x] APM: 类 Magisk 模块系统 , 支持批量刷入与全量备份
- [x] KPM: 内核模块系统(支持 inline-hook 与 syscall-table-hook) , 支持自动加载
- [x] 通过商店可以下载热门的 APM 或 KPM

### ⚡ 技术特性
- [x] 基于 [KernelPatch](https://github.com/bmax121/KernelPatch/)

## 🚀 下载安装

### 📦 使用指导

1. **下载安装:**
   从 [发布页面](https://github.com/LyraVoid/FolkPatch/releases/latest) 下载最新版安装包

2. **安装应用:**
   安装最新版安装包到你的 Android 设备

3. **开始使用:**
  阅读 https://fp.mysqil.com/

## 🙏 开源致谢

本项目基于以下开源项目:

- [KernelPatch](https://github.com/bmax121/KernelPatch/) - 核心组件
- [Magisk](https://github.com/topjohnwu/Magisk) - magiskpolicy
- [KernelSU](https://github.com/tiann/KernelSU) - 应用UI和类似Magisk的模块支持
- [Sukisu-Ultra](https://github.com/SukiSU-Ultra/SukiSU-Ultra) - 参考一些界面的设计
- [APatch](https://github.com/bmax121/APatch) - 上游分支

## 📄 许可证

- FolkPatch 遵循 [GNU General Public License v3 (GPL-3)](http://www.gnu.org/copyleft/gpl.html) 许可证开源 , 作为二改者或分发者 , 您需遵守以下标准:
- 若您修改了代码或在项目中集成了 FolkPatch 并向第三方分发 , 您的整个项目必须同样采用 GPLv3 协议开源
- 分发二进制文件时 , 必须主动提供或承诺提供完整且可读的源代码
- 严禁对软件授权本身收取许可费 , 您可以针对分发、技术支持或定制开发收费
- 分发行为即代表您授予所有用户使用该项目涉及的您的相关专利
- 本软件“按原样”提供 , 不含任何担保 , 原作者不对因使用本软件造成的任何损失负责
- 任何违反上述条款的行为将导致您的 GPLv3 授权自动终止 , 届时 , 您将失去分发 FolkPatch 的合法权利 , 原作者保留依法追究著作权侵权责任(包括但不限于申请停止侵权禁令、经济赔偿及下架违规项目)的权利
## 💬 社区交流

### FolkPatch讨论交流
- Telegram 频道: [@FolkPatch](https://t.me/FolkPatch)


================================================
FILE: README_EN.md
================================================
<div align="center">
<img src='logo.png' width='500px' alt="FolkPatch logo">

[![Latest Release](https://img.shields.io/github/v/release/matsuzaka-yuki/FolkPatch?label=Release&logo=github)](https://github.com/LyraVoid/FolkPatch/releases/latest)
[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/FolkPatch)
[![GitHub License](https://img.shields.io/github/license/matsuzaka-yuki/FolkPatch?logo=gnu)](/LICENSE)

</div>

🌏 **README Language:** [**English**](./README_EN.md) / [**中文**](./README.md) / [**日本語**](./README_JA.md)

FolkPatch - A Root management tool focused on interface optimization and feature extension

Get started quickly with our comprehensive documentation. Whether it's installation, module management, or custom settings, the documentation covers everything you need to successfully use FolkPatch.

[📚 Read Full Documentation](https://fp.mysqil.com/) →

<table>
  <tr>
    <td><img alt="" src="docs/1.png"></td>
    <td><img alt="" src="docs/2.png"></td>
    <td><img alt="" src="docs/3.png"></td>
  <tr>
  <tr>
    <td><img alt="" src="docs/4.png"></td>
    <td><img alt="" src="docs/5.png"></td>
    <td><img alt="" src="docs/6.png"></td>
  <tr>
</table>

---

## ✨ Introduction

### 🎨 Core Features
- [x] Root implementation based on KernelPatch
- [x] Hook kernel functions without recompiling the kernel

### 📱 Prerequisites

- **Required:** ARM64 architecture Android device with Linux kernel version 3.18 to 6.15

### 🎨 Manager Interface & Design
- [x] Brand new UI and interaction experience optimization
- [x] Personalized wallpaper support
- [x] Internationalization support
- [x] Animation performance and interaction fluency optimization
- [x] Interface visual details and dynamic effects enhancement
- [x] Support for manually disabling automatic update checks, giving users control over version upgrades

### 📦 Module Related
- [x] APM: Magisk-like module system, supports batch flashing and full backup
- [x] KPM: Kernel module system (supports inline-hook and syscall-table-hook), supports automatic loading
- [x] Download popular APM or KPM through the store

### ⚡ Technical Features
- [x] Based on [KernelPatch](https://github.com/bmax121/KernelPatch/)

## 🚀 Download & Install

### 📦 Installation Guide

1. **Download & Install:**
   Download the latest installation package from the [Releases page](https://github.com/LyraVoid/FolkPatch/releases/latest)

2. **Install App:**
   Install the latest installation package to your Android device

3. **Get Started:**
   Read https://fp.mysqil.com/

## 🙏 Open Source Credits

This project is based on the following open source projects:

- [KernelPatch](https://github.com/bmax121/KernelPatch/) - Core component
- [Magisk](https://github.com/topjohnwu/Magisk) - magiskpolicy
- [KernelSU](https://github.com/tiann/KernelSU) - App UI and Magisk-like module support
- [Sukisu-Ultra](https://github.com/SukiSU-Ultra/SukiSU-Ultra) - Referenced some interface designs
- [APatch](https://github.com/bmax121/APatch) - Upstream branch

## 📄 License

- FolkPatch is open sourced under the [GNU General Public License v3 (GPL-3)](http://www.gnu.org/copyleft/gpl.html) license. As a modifier or distributor, you must comply with the following standards:
- If you modify the code or integrate FolkPatch into your project and distribute it to a third party, your entire project must also be open sourced under the GPLv3 license
- When distributing binary files, you must actively provide or promise to provide complete and readable source code
- Strictly prohibit charging licensing fees for the software license itself. You may charge for distribution, technical support, or customized development
- Distribution implies that you grant all users the relevant patents involved in the use of the project
- This software is provided "as is", without any warranty. The original author is not responsible for any losses caused by using this software
- Any violation of the above terms will automatically terminate your GPLv3 license. At that time, you will lose the legal right to distribute FolkPatch. The original author reserves the right to pursue copyright infringement liability (including but not limited to applying for injunctions to stop infringement, economic compensation, and removing infringing projects)
## 💬 Community & Discussion

### FolkPatch Discussion & Communication
- Telegram Channel: [@FolkPatch](https://t.me/FolkPatch)


================================================
FILE: README_JA.md
================================================
<div align="center">
<img src='logo.png' width='500px' alt="FolkPatch logo">

[![Latest Release](https://img.shields.io/github/v/release/matsuzaka-yuki/FolkPatch?label=Release&logo=github)](https://github.com/LyraVoid/FolkPatch/releases/latest)
[![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/FolkPatch)
[![GitHub License](https://img.shields.io/github/license/matsuzaka-yuki/FolkPatch?logo=gnu)](/LICENSE)

</div>

🌏 **README の言語:** [**English**](./README_EN.md) / [**中文**](./README.md) / [**日本語**](./README_JA.md)

FolkPatch - インターフェースの最適化と拡張機能に重視した Root 管理ツール

包括的なドキュメントですぐに開始しましょう。インストール、モジュールの管理、カスタム設定など FolkPatch を快適に使用するための情報はドキュメントに網羅しています。

[📚 完全なドキュメントを読む](https://fp.mysqil.com/) →

<table>
  <tr>
    <td><img alt="" src="docs/1.png"></td>
    <td><img alt="" src="docs/2.png"></td>
    <td><img alt="" src="docs/3.png"></td>
  <tr>
  <tr>
    <td><img alt="" src="docs/4.png"></td>
    <td><img alt="" src="docs/5.png"></td>
    <td><img alt="" src="docs/6.png"></td>
  <tr>
</table>

---

## ✨ 紹介

### 🎨 コア機能
- [x] KernelPatch ベースの Root 実装
- [x] カーネルの再コンパイルなしでカーネル関数をフック可能

### 📱 前提条件

- **必須:** ARM64 アーキテクチャベースで Linux カーネルバージョン 3.18 から 6.15 の Android デバイス

### 🎨 マネージャーのインターフェースとデザイン
- [x] 全く新しい UI とインタラクションエクスペリエンスの最適化
- [x] パーソナライズされた壁紙サポート
- [x] 国際化サポート
- [x] アニメーションパフォーマンスとインタラクションの滑らかさの最適化
- [x] インターフェースの視覚的詳細と動的効果の向上
- [x] 自動更新チェックの手動無効化をサポートし、バージョンアップグレードの主導権をユーザーに返還

### 📦 モジュール関連
- [x] APM: Magisk ライクなモジュールシステム、一括フラッシュとフルバックアップをサポート
- [x] KPM: カーネルモジュールシステム(inline-hook と syscall-table-hook をサポート)、自動ロードをサポート
- [x] ストアから人気のある APM または KPM をダウンロード可能

### ⚡ 技術的特徴
- [x] [KernelPatch](https://github.com/bmax121/KernelPatch/) に基づいています

## 🚀 ダウンロードとインストール

### 📦 インストールガイド

1. **ダウンロードとインストール:**
   [リリースページ](https://github.com/LyraVoid/FolkPatch/releases/latest)から最新のインストールパッケージをダウンロード

2. **アプリをインストール:**
   最新のインストールパッケージをあなたの Android デバイスにインストール

3. **使用開始:**
   https://fp.mysqil.com/ を読んでください

## 🙏 オープンソースクレジット

このプロジェクトは以下のオープンソースプロジェクトに基づいています:

- [KernelPatch](https://github.com/bmax121/KernelPatch/) - コアコンポーネント
- [Magisk](https://github.com/topjohnwu/Magisk) - magiskpolicy
- [KernelSU](https://github.com/tiann/KernelSU) - アプリ UI と Magisk ライクなモジュールサポート
- [Sukisu-Ultra](https://github.com/SukiSU-Ultra/SukiSU-Ultra) - 一部のインターフェースデザインを参照
- [APatch](https://github.com/bmax121/APatch) - 上流ブランチ

## 📄 ライセンス

- FolkPatch は [GNU General Public License v3 (GPL-3)](http://www.gnu.org/copyleft/gpl.html) ライセンスの下でオープンソースされています。変更者または配布者として、以下の基準を遵守する必要があります:
- コードを変更した場合、またはプロジェクトに FolkPatch を統合して第三者に配布する場合、プロジェクト全体も GPLv3 ライセンスの下でオープンソースする必要があります
- バイナリファイルを配布する場合、完全かつ読み取り可能なソースコードを積極的に提供するか、提供することを約束する必要があります
- ソフトウェアライセンス自体に対するライセンス料の徴収を厳禁します。配布、技術サポート、カスタム開発に対して料金を請求できます
- 配布行為は、プロジェクトに関連するすべてのユーザーにあなたの関連特許の使用権を付与することを意味します
- 本ソフトウェアは「現状のまま」提供され、いかなる保証もありません。原作者は本ソフトウェアの使用による損失について責任を負いません
- 上記の条項に違反すると GPLv3 ライセンスは自動的に終了します。その際、FolkPatch を配布する正当な権利を失い、原作者は著作権侵害の責任を追求する権利(侵害停止命令の申請、経済的賠償、違反プロジェクトの削除を含むがこれらに限定されない)を留保します
## 💬 コミュニティとディスカッション

### FolkPatch ディスカッションとコミュニケーション
- Telegram チャンネル: [@FolkPatch](https://t.me/FolkPatch)


================================================
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"
regex-lite = "0.1.9"

[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" }
prop-rs-android = { git = "https://github.com/Kernel-SU/ksu_props" }
policy = {git = "https://github.com/AndroidPatch/ap_policy"}
[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, fs::File, io::Write, path::Path, process::Command};

fn get_git_version() -> Result<(u32, String), std::io::Error> {
    // Try to get version code from environment variable first
    let version_code: u32 = if let Ok(env_version_code) = env::var("APATCH_VERSION_CODE") {
        env_version_code.parse().map_err(|_| {
            std::io::Error::new(std::io::ErrorKind::Other, "Failed to parse {version_code}")
        })?
    } else {
        // Fallback to git-based calculation
        let output = Command::new("git")
            .args(["rev-list", "--count", "HEAD"])
            .output()?;

        let output = output.stdout;
        let git_count = String::from_utf8(output).expect("Failed to read git count stdout");
        let git_count: u32 = git_count.trim().parse().map_err(|_| {
            std::io::Error::new(std::io::ErrorKind::Other, "Failed to parse git count")
        })?;
        std::cmp::max(11000 + 200 + git_count, 10762) // For historical reasons and ensure minimum version
    };

    let version_name = if let Ok(env_version_name) = env::var("APATCH_VERSION_NAME") {
        env_version_name
    } else {
        "113005-Matsuzaka-yuki".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(FolkPatch)", 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 = {
            #[cfg(target_arch = "aarch64")]
            let pw = unsafe { libc::getpwnam(name.as_ptr()).as_ref() };
            #[cfg(target_arch = "x86_64")]
            let pw = unsafe { 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(BUSYBOX_PATH)?;
    let resetprop_link = RESETPROP_PATH;
    let _ = std::fs::remove_file(resetprop_link);
    std::os::unix::fs::symlink("/data/adb/apd", resetprop_link)?;

    let magiskpolicy_link = MAGISKPOLICY_PATH;
    let _ = std::fs::remove_file(magiskpolicy_link);
    std::os::unix::fs::symlink("/data/adb/apd", magiskpolicy_link)?;

    Ok(())
}


================================================
FILE: apd/src/banner
================================================
   ___  ___  __          ___  _   _____  ___        
  / __\/___\/ /   /\ /\ / _ \/_\ /__   \/ __\ /\  /\
 / _\ //  // /   / //_// /_)//_\\  / /\/ /   / /_/ /
/ /  / \_// /___/ __ \/ ___/  _  \/ / / /___/ __  / 
\/   \___/\____/\/  \/\/   \_/ \_/\/  \____/\/ /_/  
                                                    

================================================
FILE: apd/src/cli.rs
================================================
use crate::{defs, event, lua, module, module_config, supercall, utils};
#[cfg(target_os = "android")]
use android_logger::Config;
use anyhow::{Context, 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,

    /// Resetprop - Magisk-compatible system property tool
    Resetprop(crate::resetprop::Args),

    /// MagiskPolicy - SELinux Policy Patch Tool
    Sepolicy(crate::sepolicy::Args),
}

#[derive(clap::Subcommand, Debug)]
enum Module {
    /// Install module <ZIP>
    Install {
        /// module zip file path
        zip: String,
    },

    /// Uninstall module <id>
    Uninstall {
        /// module id
        id: String,
    },

    /// Undo uninstall module <id>
    UndoUninstall {
        /// 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,

    /// manage module configuration
    Config {
        /// target internal module name (resolved as internal.<name>)
        #[arg(long)]
        internal: Option<String>,
        #[command(subcommand)]
        command: ModuleConfigCmd,
    },
}

#[derive(clap::Subcommand, Debug)]
enum ModuleConfigCmd {
    /// Get a config value
    Get {
        /// config key
        key: String,
    },

    /// Set a config value
    Set {
        /// config key
        key: String,
        /// config value (omit to read from stdin)
        value: Option<String>,
        /// read value from stdin (default if value not provided)
        #[arg(long)]
        stdin: bool,
        /// use temporary config (cleared on reboot)
        #[arg(short, long)]
        temp: bool,
    },

    /// List all config entries
    List,

    /// Delete a config entry
    Delete {
        /// config key
        key: String,
        /// delete from temporary config
        #[arg(short, long)]
        temp: bool,
    },

    /// Clear all config entries
    Clear {
        /// clear temporary config
        #[arg(short, long)]
        temp: bool,
    },
}

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();
    }
    if arg0.ends_with("resetprop") {
        let all_args: Vec<String> = std::env::args().collect();
        crate::resetprop::resetprop_main(&all_args)
    }
    if arg0.ends_with("magiskpolicy") {
        let all_args: Vec<String> = std::env::args().collect();
        crate::sepolicy::policy_main(&all_args)
    }

    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::UndoUninstall { id } => module::undo_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(),
                Module::Config { internal, command } => {
                    let module_id = match internal {
                        Some(internal_name) => format!("internal.{internal_name}"),
                        None => std::env::var("AP_MODULE").map_err(|_| {
                            anyhow::anyhow!(
                                "This command must be run in the context of a module or passed --internal <name>"
                            )
                        })?,
                    };

                    match command {
                        ModuleConfigCmd::Get { key } => {
                            // Use merge_configs to respect priority (temp overrides persist)
                            let config = module_config::merge_configs(&module_id)?;
                            match config.get(&key) {
                                Some(value) => {
                                    println!("{value}");
                                    Ok(())
                                }
                                None => anyhow::bail!("Key '{key}' not found"),
                            }
                        }
                        ModuleConfigCmd::Set {
                            key,
                            value,
                            stdin,
                            temp,
                        } => {
                            // Validate key at CLI layer for better user experience
                            module_config::validate_config_key(&key)?;

                            // Read value from stdin or argument
                            let value_str = match value {
                                Some(v) if !stdin => v,
                                _ => {
                                    // Read from stdin
                                    use std::io::Read;
                                    let mut buffer = String::new();
                                    std::io::stdin()
                                        .read_to_string(&mut buffer)
                                        .context("Failed to read from stdin")?;
                                    buffer
                                }
                            };

                            // Validate value
                            module_config::validate_config_value(&value_str)?;

                            let config_type = if temp {
                                module_config::ConfigType::Temp
                            } else {
                                module_config::ConfigType::Persist
                            };
                            module_config::set_config_value(
                                &module_id,
                                &key,
                                &value_str,
                                config_type,
                            )
                        }
                        ModuleConfigCmd::List => {
                            let config = module_config::merge_configs(&module_id)?;
                            if config.is_empty() {
                                println!("No config entries found");
                            } else {
                                for (key, value) in config {
                                    println!("{key}={value}");
                                }
                            }
                            Ok(())
                        }
                        ModuleConfigCmd::Delete { key, temp } => {
                            let config_type = if temp {
                                module_config::ConfigType::Temp
                            } else {
                                module_config::ConfigType::Persist
                            };
                            module_config::delete_config_value(&module_id, &key, config_type)
                        }
                        ModuleConfigCmd::Clear { temp } => {
                            let config_type = if temp {
                                module_config::ConfigType::Temp
                            } else {
                                module_config::ConfigType::Persist
                            };
                            module_config::clear_config(&module_id, config_type)
                        }
                    }
                }
            }
        }

        Commands::Services => event::on_services(cli.superkey),

        Commands::Resetprop(resetprop_args) => crate::resetprop::execute(&resetprop_args)
            .inspect_err(|e| {
                if e.downcast_ref::<crate::resetprop::WaitTimeoutError>()
                    .is_some()
                {
                    std::process::exit(2);
                }
            }),

        Commands::Sepolicy(sepolicy_args) => crate::sepolicy::execute(&sepolicy_args),
    };

    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 MAGIC_MOUNT_FILE: &str = concatcp!(ADB_DIR, ".magic_mount_enable");
pub const HIDE_SERVICE_FILE: &str = concatcp!(ADB_DIR, ".hide_service_enable");
pub const HIDE_BINARY_PATH: &str = concatcp!(ADB_DIR, "fp/bin/fpd");
pub const UMOUNT_SERVICE_FILE: &str = concatcp!(ADB_DIR, ".umount_service_enable");
pub const UMOUNT_BINARY_PATH: &str = concatcp!(ADB_DIR, "fp/bin/fpd");
pub const UTS_SPOOF_ENABLE_FILE: &str = concatcp!(ADB_DIR, ".uts_spoof_enable");
pub const UTS_SPOOF_CONFIG_FILE: &str = concatcp!(ADB_DIR, ".uts_spoof_config");
pub const UTS_SPOOF_BOOT_PENDING: &str = concatcp!(ADB_DIR, ".uts_spoof_boot_pending");
pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "apd");
pub const AUTO_EXCLUDE_KNOWN_PACKAGES_FILE: &str = concatcp!(WORKING_DIR, "auto_exclude_known_packages");

pub const PATHHIDE_DIR: &str = concatcp!(ADB_DIR, "fp/pathhide/");
pub const PATHHIDE_ENABLE_FILE: &str = concatcp!(ADB_DIR, "fp/pathhide/enabled");
pub const PATHHIDE_PATHS_FILE: &str = concatcp!(ADB_DIR, "fp/pathhide/paths");
pub const PATHHIDE_UIDS_FILE: &str = concatcp!(ADB_DIR, "fp/pathhide/uids");
pub const PATHHIDE_UID_MODE_FILE: &str = concatcp!(ADB_DIR, "fp/pathhide/uid_mode");
pub const PATHHIDE_FILTER_SYSTEM_FILE: &str = concatcp!(ADB_DIR, "fp/pathhide/filter_system");

pub const NETISOLATE_DIR: &str = concatcp!(ADB_DIR, "fp/netisolate/");
pub const NETISOLATE_ENABLE_FILE: &str = concatcp!(ADB_DIR, "fp/netisolate/enabled");
pub const NETISOLATE_UIDS_FILE: &str = concatcp!(ADB_DIR, "fp/netisolate/uids");

pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/");
pub const AP_MAGIC_MOUNT_SOURCE: &str = concatcp!(WORKING_DIR, "magic_mount");

// 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 SKIP_MOUNT_FILE_NAME: &str = "skip_mount";
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 FP_KPMS_DIR: &str = concatcp!(ADB_DIR, "fp/kpms/");
pub const FP_KPMS_AUTOLOAD_DIR: &str = concatcp!(ADB_DIR, "fp/kpms/autoload/");
pub const KPM_AUTOLOAD_CONFIG: &str = concatcp!(ADB_DIR, "fp/kpms/kpm_autoload_config.json");

// Module config
pub const MODULE_CONFIG_DIR: &str = concatcp!(WORKING_DIR, "module_configs/");
pub const PERSIST_CONFIG_NAME: &str = "persist.config";
pub const TEMP_CONFIG_NAME: &str = "tmp.config";

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 crate::sepolicy::get_policy_main;
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 std::process::Stdio;
use std::{
    env,
    ffi::CStr,
    fs,
    os::unix::{fs::PermissionsExt, process::CommandExt},
    path::{Path, PathBuf},
    process::Command,
    sync::{Arc, Mutex},
    thread,
    time::Duration,
};

use crate::{
    assets, defs, lua, metamodule, module, restorecon, supercall,
    supercall::{init_load_su_path, refresh_ap_package_list},
    utils::{self, switch_cgroups},
};

pub fn report_kernel(superkey: Option<String>, event: &str, state: &str) -> Result<()> {
    let args = vec![
        superkey.unwrap_or_default(),
        "event".to_string(),
        event.to_string(),
        state.to_string(),
    ];
    let args_ref: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
    let _ = utils::run_command("truncate", &args_ref, None)?.wait()?;
    Ok(())
}

fn setup_fp_directories() -> Result<()> {
    utils::ensure_dir_with_perms(
        Path::new("/data/adb/fp/bin"),
        Path::new("/data/adb/fp"),
        0o755,
    )?;
    utils::ensure_dir_with_perms(
        Path::new(defs::FP_KPMS_AUTOLOAD_DIR),
        Path::new(defs::FP_KPMS_DIR),
        0o755,
    )?;
    Ok(())
}

fn setup_logging() -> Result<()> {
    let log_dir = Path::new(defs::APATCH_LOG_FOLDER);
    if !log_dir.exists() {
        fs::create_dir(log_dir).expect("Failed to create log folder");
        let permissions = fs::Permissions::from_mode(0o700);
        fs::set_permissions(log_dir, permissions).expect("Failed to set permissions");
    }

    let command_string = format!(
        "cd {}; rm -f *.last; [ -f dmesg.log ] && mv dmesg.log dmesg.last; [ -f logcat.log ] && mv logcat.log logcat.last; [ -f locat.log ] && mv locat.log logcat.last; rm -f *.log *.old.log",
        defs::APATCH_LOG_FOLDER
    );
    let result = utils::run_command("sh", &["-c", &command_string], None)?.wait()?;
    if result.success() {
        info!("Successfully rotated logs.");
    } else {
        info!("Failed to rotate logs.");
    }

    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)?;

    let _ = unsafe {
        Command::new("timeout")
            .process_group(0)
            .pre_exec(|| {
                switch_cgroups();
                Ok(())
            })
            .args(vec![
                "-s", "9", "45s", "logcat", "-b", "main,system,crash",
                "DrmLibFs:S", "-f", &logcat_path, "logcatcher-bootlog:S", "&",
            ])
            .spawn()
    };
    let _ = unsafe {
        Command::new("timeout")
            .process_group(0)
            .pre_exec(|| {
                switch_cgroups();
                Ok(())
            })
            .args(["-s", "9", "120s", "dmesg", "-w"])
            .stdout(Stdio::from(bootlog))
            .spawn()
    };

    Ok(())
}

fn disable_all_modules_safe() {
    if let Err(e) = module::disable_all_modules() {
        warn!("disable all modules failed: {e}");
    }
}

pub fn on_post_data_fs(superkey: Option<String>) -> Result<()> {
    utils::umask(0);
    report_kernel(superkey.clone(), "post-fs-data", "before")?;

    setup_fp_directories()?;

    supercall::autoload_kpm_modules(&superkey, "post-fs-data");

    init_load_su_path(&superkey);

    let mut sepol = get_policy_main(&["magiskpolicy".to_string(), "--live".to_string()])?;
    sepol.magisk_rules();
    sepol
        .to_file("/sys/fs/selinux/load")
        .context("Cannot apply policy")?;

    info!("Re-privilege apd profile after injecting sepolicy");
    supercall::privilege_apd_profile(&superkey);

    // Apply UTS namespace spoofing if configured
    supercall::apply_uts_spoof(&superkey);

    // Apply pathhide config if enabled
    supercall::apply_pathhide(&superkey);

    // Apply netisolate config if enabled
    supercall::apply_netisolate(&superkey);

    // Clear all temporary module configs early
    if let Err(e) = crate::module_config::clear_all_temp_configs() {
        warn!("clear temp configs failed: {e}");
    }

    if utils::has_magisk() {
        warn!("Magisk detected, skip post-fs-data!");
        report_kernel(superkey.clone(), "post-fs-data", "after")?;
        return Ok(());
    }

    setup_logging()?;

    for key in ["KERNELPATCH_VERSION", "KERNEL_VERSION"] {
        match env::var(key) {
            Ok(value) => println!("{key}: {value}"),
            Err(_) => println!("{key} not found"),
        }
    }

    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");
        disable_all_modules_safe();
    } 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!");
        disable_all_modules_safe();
        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 Path::new(defs::MAGIC_MOUNT_FILE).exists() {
        info!("Magic Mount mode enabled");
        if let Err(e) = crate::magic_mount::magic_mount() {
            log::error!("Magic Mount failed: {}", e);
        }
    } else {
        info!("Magic Mount disabled");
        if let Err(e) = metamodule::exec_mount_script(module_dir) {
            warn!("execute metamodule mount failed: {e}");
        }
    }

    // Execute Hide Service if enabled
    if Path::new(defs::HIDE_SERVICE_FILE).exists() {
        info!("Hide Service enabled, executing fpd -hide...");
        if Path::new(defs::HIDE_BINARY_PATH).exists() {
            let result = Command::new(defs::HIDE_BINARY_PATH).arg("-hide").status();
            match result {
                Ok(status) => {
                    if status.success() {
                        info!("fpd -hide executed successfully");
                    } else {
                        warn!("fpd -hide exited with status: {:?}", status.code());
                    }
                }
                Err(e) => {
                    warn!("Failed to execute fpd -hide: {}", e);
                }
            }
        } else {
            warn!(
                "fpd binary not found at {}, please copy it manually",
                defs::HIDE_BINARY_PATH
            );
        }
    } else {
        info!("Hide Service disabled");
    }

    // 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.clone(), true);

    report_kernel(superkey, "post-fs-data", "after")?;

    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");
        disable_all_modules_safe();
        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!");

    supercall::autoload_kpm_modules(&superkey, "service");

    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!");

    // Clear UTS spoof boot safety flag — boot completed successfully
    if Path::new(defs::UTS_SPOOF_BOOT_PENDING).exists() {
        let _ = std::fs::remove_file(defs::UTS_SPOOF_BOOT_PENDING);
        info!("UTS spoof boot safety flag cleared");
    }

    run_stage("boot-completed", superkey, false);

    // Execute Umount Service if enabled
    // Run at boot-completed (latest possible stage) to ensure all mount
    // points — including those created by system_server and Zygote hooks
    // (e.g. ReZygisk module.prop bind mounts) — are fully established.
    if Path::new(defs::UMOUNT_SERVICE_FILE).exists() {
        info!("Umount Service enabled, executing fpd -umount...");
        if Path::new(defs::UMOUNT_BINARY_PATH).exists() {
            let result = unsafe {
                Command::new(defs::UMOUNT_BINARY_PATH)
                    .arg("-umount")
                    .stdout(Stdio::piped())
                    .stderr(Stdio::piped())
                    .pre_exec(|| {
                        let _ = utils::switch_mnt_ns(1);
                        Ok(())
                    })
                    .output()
            };
            match result {
                Ok(output) => {
                    let stdout = String::from_utf8_lossy(&output.stdout);
                    let stderr = String::from_utf8_lossy(&output.stderr);
                    if output.status.success() {
                        info!("fpd -umount executed successfully");
                    } else {
                        warn!("fpd -umount exited with status: {:?}", output.status.code());
                    }
                    if !stdout.trim().is_empty() {
                        info!("fpd -umount stdout: {}", stdout.trim());
                    }
                    if !stderr.trim().is_empty() {
                        info!("fpd -umount stderr: {}", stderr.trim());
                    }
                }
                Err(e) => {
                    warn!("Failed to execute fpd -umount: {}", e);
                }
            }
        } else {
            warn!(
                "fpd binary not found at {}, please copy it manually",
                defs::UMOUNT_BINARY_PATH
            );
        }
    } else {
        info!("Umount Service disabled");
    }

    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 skey = CStr::from_bytes_with_nul(b"su\0")
            .expect("[start_uid_listener] CStr::from_bytes_with_nul failed");
        info!("[uid_monitor] Performing initial refresh on startup...");
        refresh_ap_package_list(&skey, &mutex);
    }

    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);
            report_kernel(None, "uid_listener", "package-list-updated").unwrap_or_else(|e| {
                warn!("Failed to report kernel about package list update: {e}");
            });
        } else if !debounce {
            thread::sleep(Duration::from_secs(1));
            debounce = true;
            tx.send(true)?;
        }
    }

    Ok(())
}


================================================
FILE: apd/src/install_jq.sh
================================================
#!/system/bin/sh
# Install jq binary to /data/adb/jq
# This script is called during APatch installation

JQ_DIR="/data/adb"
JQ_BIN="$JQ_DIR/jq"

# Check if jq already exists and is up to date
if [ -f "$JQ_BIN" ]; then
    # jq already installed, skip
    exit 0
fi

# Extract jq from assets
if [ -f "$APATCH_ASSETS_DIR/jq/jq" ]; then
    cp "$APATCH_ASSETS_DIR/jq/jq" "$JQ_BIN"
    chmod 755 "$JQ_BIN"
    echo "jq installed to $JQ_BIN"
else
    echo "jq binary not found in assets"
    exit 1
fi


================================================
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() {
  if [ ! -f "$NVBASE/jq" ]; then
    local apk_path=$(find /data/app -name "base.apk" -path "*/me.yuki.folk-*" 2>/dev/null | head -n 1)
    if [ -n "$apk_path" ] && [ -f "$apk_path" ]; then
      # Extract jq from APK assets
      mkdir -p /data/local/tmp/jq_extract
      if unzip -o "$apk_path" "jq/jq" -d /data/local/tmp/jq_extract >&2; then
        if [ -f "/data/local/tmp/jq_extract/jq/jq" ]; then
          cp /data/local/tmp/jq_extract/jq/jq "$NVBASE/jq"
          chmod 755 "$NVBASE/jq"
          rm -rf /data/local/tmp/jq_extract
        fi
      else
        rm -rf /data/local/tmp/jq_extract
      fi
    fi
  fi
  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"
        ln -sf "./system/$1" "$MODPATH/$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/magic_mount.rs
================================================
use std::{
    cmp::PartialEq,
    collections::{HashMap, hash_map::Entry},
    fs,
    fs::{DirEntry, FileType, create_dir, create_dir_all, read_dir, read_link},
    os::unix::fs::{FileTypeExt, symlink},
    path::{Path, PathBuf},
};

use anyhow::{Context, Result, bail};
use extattr::lgetxattr;
use rustix::{
    fs::{Gid, MetadataExt, Mode, Uid, chmod, chown},
    mount::{
        MountFlags, MountPropagationFlags, UnmountFlags, mount, mount_bind, mount_change,
        mount_move, unmount,
    },
};

use crate::{
    defs::{
        AP_MAGIC_MOUNT_SOURCE, DISABLE_FILE_NAME, MODULE_DIR, REMOVE_FILE_NAME,
        SKIP_MOUNT_FILE_NAME,
    },
    magic_mount::NodeFileType::{Directory, RegularFile, Symlink, Whiteout},
    restorecon::{lgetfilecon, lsetfilecon},
    utils::ensure_dir_exists,
};

const REPLACE_DIR_FILE_NAME: &str = ".replace";
const REPLACE_DIR_XATTR: &str = "trusted.overlay.opaque";

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
enum NodeFileType {
    RegularFile,
    Directory,
    Symlink,
    Whiteout,
}

impl NodeFileType {
    fn from_file_type(file_type: FileType) -> Self {
        if file_type.is_file() {
            RegularFile
        } else if file_type.is_dir() {
            Directory
        } else if file_type.is_symlink() {
            Symlink
        } else {
            Whiteout
        }
    }

    /// Check if mounting this node type over `real_path` requires a tmpfs overlay
    /// due to type mismatch or missing file.
    fn needs_tmpfs_vs_real(&self, real_path: &Path) -> bool {
        match self {
            Symlink => true,
            Whiteout => real_path.exists(),
            _ => match real_path.symlink_metadata() {
                Ok(metadata) => {
                    let real_type = Self::from_file_type(metadata.file_type());
                    real_type != *self || real_type == Symlink
                }
                Err(_) => true,
            },
        }
    }
}

#[derive(Debug, Clone)]
struct Node {
    name: String,
    file_type: NodeFileType,
    children: HashMap<String, Node>,
    // the module that owned this node
    module_path: Option<PathBuf>,
    replace: bool,
    skip: bool,
}

impl Node {
    fn collect_module_files<P>(&mut self, module_dir: P) -> Result<bool>
    where
        P: AsRef<Path>,
    {
        let dir = module_dir.as_ref();
        let mut has_file = false;
        for entry in dir.read_dir()?.flatten() {
            let name = entry.file_name().to_string_lossy().to_string();

            let node = match self.children.entry(name.clone()) {
                Entry::Occupied(o) => Some(o.into_mut()),
                Entry::Vacant(v) => Self::new_module(&name, &entry).map(|it| v.insert(it)),
            };

            if let Some(node) = node {
                has_file |= if node.file_type == NodeFileType::Directory {
                    node.collect_module_files(dir.join(&node.name))? || node.replace
                } else {
                    true
                }
            }
        }

        Ok(has_file)
    }

    fn dir_is_replace<P>(path: P) -> bool
    where
        P: AsRef<Path>,
    {
        if let Ok(v) = lgetxattr(&path, REPLACE_DIR_XATTR)
            && String::from_utf8_lossy(&v) == "y"
        {
            return true;
        }

        path.as_ref().join(REPLACE_DIR_FILE_NAME).exists()
    }

    fn new_root<T: ToString>(name: T) -> Self {
        Node {
            name: name.to_string(),
            file_type: Directory,
            children: Default::default(),
            module_path: None,
            replace: false,
            skip: false,
        }
    }

    fn new_module<S>(name: &S, entry: &DirEntry) -> Option<Self>
    where
        S: ToString,
    {
        if let Ok(metadata) = entry.metadata() {
            let path = entry.path();
            let file_type = if metadata.file_type().is_char_device() && metadata.rdev() == 0 {
                NodeFileType::Whiteout
            } else {
                NodeFileType::from_file_type(metadata.file_type())
            };
            let replace = file_type == NodeFileType::Directory && Self::dir_is_replace(&path);
            if replace {
                log::debug!("{} need replace", path.display());
            }
            return Some(Self {
                name: name.to_string(),
                file_type,
                children: HashMap::default(),
                module_path: Some(path),
                replace,
                skip: false,
            });
        }

        None
    }
}

fn collect_module_files() -> Result<Option<Node>> {
    let mut root = Node::new_root("");
    let mut system = Node::new_root("system");
    let module_root = Path::new(MODULE_DIR);
    let mut has_file = false;

    log::debug!("begin collect module files: {}", module_root.display());

    for entry in module_root.read_dir()?.flatten() {
        if !entry.file_type()?.is_dir() {
            continue;
        }

        let id = entry.file_name().to_str().unwrap().to_string();
        log::debug!("processing new module: {id}");

        let prop = entry.path().join("module.prop");
        if !prop.exists() {
            log::debug!("skipped module {id}, because not found module.prop");
            continue;
        }

        if entry.path().join(DISABLE_FILE_NAME).exists()
            || entry.path().join(REMOVE_FILE_NAME).exists()
            || entry.path().join(SKIP_MOUNT_FILE_NAME).exists()
        {
            log::debug!("skipped module {id}, due to disable/remove/skip_mount");
            continue;
        }

        let mod_system = entry.path().join("system");

        if !mod_system.is_dir() {
            continue;
        }

        log::debug!("collecting {}", entry.path().display());

        has_file |= system.collect_module_files(mod_system)?;
    }

    if has_file {
        const BUILTIN_PARTITIONS: [(&str, bool); 5] = [
            ("vendor", true),
            ("system_ext", true),
            ("product", true),
            ("odm", false),
            ("oem", false),
        ];

        for (partition, require_symlink) in BUILTIN_PARTITIONS {
            let path_of_root = Path::new("/").join(partition);
            let path_of_system = Path::new("/system").join(partition);
            if path_of_root.is_dir() && (!require_symlink || path_of_system.is_symlink()) {
                let name = partition.to_string();
                if let Some(node) = system.children.remove(&name) {
                    root.children.insert(name, node);
                }
            }
        }

        root.children.insert("system".to_string(), system);
        Ok(Some(root))
    } else {
        Ok(None)
    }
}

fn clone_symlink<Src: AsRef<Path>, Dst: AsRef<Path>>(src: Src, dst: Dst) -> Result<()> {
    let src_symlink = read_link(src.as_ref())?;
    symlink(&src_symlink, dst.as_ref())?;
    lsetfilecon(dst.as_ref(), lgetfilecon(src.as_ref())?.as_str())?;
    log::debug!(
        "clone symlink {} -> {}({})",
        dst.as_ref().display(),
        dst.as_ref().display(),
        src_symlink.display()
    );
    Ok(())
}

fn mount_mirror<P: AsRef<Path>, WP: AsRef<Path>>(
    path: P,
    work_dir_path: WP,
    entry: &DirEntry,
) -> Result<()> {
    let path = path.as_ref().join(entry.file_name());
    let work_dir_path = work_dir_path.as_ref().join(entry.file_name());
    let file_type = entry.file_type()?;

    if file_type.is_file() {
        log::debug!(
            "mount mirror file {} -> {}",
            path.display(),
            work_dir_path.display()
        );
        fs::File::create(&work_dir_path)?;
        mount_bind(&path, &work_dir_path)?;
    } else if file_type.is_dir() {
        log::debug!(
            "mount mirror dir {} -> {}",
            path.display(),
            work_dir_path.display()
        );
        create_dir(&work_dir_path)?;
        let metadata = entry.metadata()?;
        chmod(&work_dir_path, Mode::from_raw_mode(metadata.mode()))?;
        chown(
            &work_dir_path,
            Some(Uid::from_raw(metadata.uid())),
            Some(Gid::from_raw(metadata.gid())),
        )?;
        lsetfilecon(&work_dir_path, lgetfilecon(&path)?.as_str())?;
        for entry in read_dir(&path)?.flatten() {
            mount_mirror(&path, &work_dir_path, &entry)?;
        }
    } else if file_type.is_symlink() {
        log::debug!(
            "create mirror symlink {} -> {}",
            path.display(),
            work_dir_path.display()
        );
        clone_symlink(&path, &work_dir_path)?;
    }

    Ok(())
}

fn should_create_tmpfs(path: &Path, current: &mut Node, has_tmpfs: bool) -> bool {
    if has_tmpfs {
        return false;
    }
    if current.replace && current.module_path.is_some() {
        return true;
    }
    for (name, node) in &mut current.children {
        let real_path = path.join(name);
        if node.file_type.needs_tmpfs_vs_real(&real_path) {
            if current.module_path.is_none() {
                log::error!("cannot create tmpfs on {}, ignore: {name}", path.display());
                node.skip = true;
                continue;
            }
            return true;
        }
    }
    false
}

fn prepare_tmpfs_skeleton(
    path: &Path,
    work_dir_path: &Path,
    module_path: Option<&PathBuf>,
) -> Result<()> {
    log::debug!(
        "creating tmpfs skeleton for {} at {}",
        path.display(),
        work_dir_path.display()
    );
    create_dir_all(work_dir_path)?;
    let source: &Path = if path.exists() {
        path
    } else if let Some(mp) = module_path {
        mp
    } else {
        bail!("cannot mount root dir {}!", path.display());
    };
    let metadata = source.metadata()?;
    chmod(work_dir_path, Mode::from_raw_mode(metadata.mode()))?;
    chown(
        work_dir_path,
        Some(Uid::from_raw(metadata.uid())),
        Some(Gid::from_raw(metadata.gid())),
    )?;
    lsetfilecon(work_dir_path, lgetfilecon(source)?.as_str())?;
    Ok(())
}

fn handle_mount_result(result: Result<()>, path: &Path, name: &str, has_tmpfs: bool) -> Result<()> {
    if let Err(e) = result {
        if has_tmpfs {
            return Err(e);
        }
        log::error!("mount child {}/{} failed: {}", path.display(), name, e);
    }
    Ok(())
}

fn process_existing_entries(
    path: &Path,
    work_dir_path: &Path,
    children: &mut HashMap<String, Node>,
    has_tmpfs: bool,
) -> Result<()> {
    for entry in path.read_dir()?.flatten() {
        let name = entry.file_name().to_string_lossy().to_string();
        let result = if let Some(node) = children.remove(&name) {
            if node.skip {
                continue;
            }
            do_magic_mount(path, work_dir_path, node, has_tmpfs)
                .with_context(|| format!("magic mount {}/{name}", path.display()))
        } else if has_tmpfs {
            mount_mirror(path, work_dir_path, &entry)
                .with_context(|| format!("mount mirror {}/{name}", path.display()))
        } else {
            Ok(())
        };
        handle_mount_result(result, path, &name, has_tmpfs)?;
    }
    Ok(())
}

fn process_remaining_children(
    path: &Path,
    work_dir_path: &Path,
    children: HashMap<String, Node>,
    has_tmpfs: bool,
) -> Result<()> {
    for (name, node) in children {
        if node.skip {
            continue;
        }
        let result = do_magic_mount(path, work_dir_path, node, has_tmpfs)
            .with_context(|| format!("magic mount {}/{name}", path.display()));
        handle_mount_result(result, path, &name, has_tmpfs)?;
    }
    Ok(())
}

fn move_tmpfs_to_target(work_dir_path: &Path, target: &Path) -> Result<()> {
    log::debug!(
        "moving tmpfs {} -> {}",
        work_dir_path.display(),
        target.display()
    );
    mount_move(work_dir_path, target).context("move self")?;
    mount_change(target, MountPropagationFlags::PRIVATE).context("make self private")?;
    Ok(())
}

fn do_magic_mount<P: AsRef<Path>, WP: AsRef<Path>>(
    path: P,
    work_dir_path: WP,
    mut current: Node,
    has_tmpfs: bool,
) -> Result<()> {
    let path = path.as_ref().join(&current.name);
    let work_dir_path = work_dir_path.as_ref().join(&current.name);
    match current.file_type {
        RegularFile => {
            let target_path = if has_tmpfs {
                fs::File::create(&work_dir_path)?;
                &work_dir_path
            } else {
                &path
            };
            if let Some(module_path) = &current.module_path {
                log::debug!(
                    "mount module file {} -> {}",
                    module_path.display(),
                    work_dir_path.display()
                );
                mount_bind(module_path, target_path)?;
            } else {
                bail!("cannot mount root file {}!", path.display());
            }
        }
        Symlink => {
            if let Some(module_path) = &current.module_path {
                log::debug!(
                    "create module symlink {} -> {}",
                    module_path.display(),
                    work_dir_path.display()
                );
                clone_symlink(module_path, &work_dir_path)?;
            } else {
                bail!("cannot mount root symlink {}!", path.display());
            }
        }
        Directory => {
            let create_tmpfs = should_create_tmpfs(&path, &mut current, has_tmpfs);
            let has_tmpfs = has_tmpfs || create_tmpfs;

            if has_tmpfs {
                prepare_tmpfs_skeleton(&path, &work_dir_path, current.module_path.as_ref())?;
            }
            if create_tmpfs {
                log::debug!(
                    "creating tmpfs for {} at {}",
                    path.display(),
                    work_dir_path.display()
                );
                mount_bind(&work_dir_path, &work_dir_path).context("bind self")?;
            }
            if path.exists() && !current.replace {
                process_existing_entries(
                    &path,
                    &work_dir_path,
                    &mut current.children,
                    has_tmpfs,
                )?;
            }
            if current.replace {
                if current.module_path.is_none() {
                    bail!("dir {} is declared as replaced but it is root!", path.display());
                }
                log::debug!("dir {} is replaced", path.display());
            }
            process_remaining_children(&path, &work_dir_path, current.children, has_tmpfs)?;
            if create_tmpfs {
                move_tmpfs_to_target(&work_dir_path, &path)?;
            }
        }
        Whiteout => {
            log::debug!("file {} is removed", path.display());
        }
    }
    Ok(())
}

pub fn magic_mount() -> Result<()> {
    if let Some(root) = collect_module_files()? {
        log::debug!("collected: {:#?}", root);
        let tmp_dir = PathBuf::from(AP_MAGIC_MOUNT_SOURCE);
        ensure_dir_exists(&tmp_dir)?;
        mount("tmpfs", &tmp_dir, "tmpfs", MountFlags::empty(), None).context("mount tmp")?;
        mount_change(&tmp_dir, MountPropagationFlags::PRIVATE).context("make tmp private")?;
        let result = do_magic_mount("/", &tmp_dir, root, false);
        if let Err(e) = unmount(&tmp_dir, UnmountFlags::DETACH) {
            log::error!("failed to unmount tmp {}", e);
        }
        fs::remove_dir(tmp_dir).ok();
        result
    } else {
        log::info!("no modules to mount, skipping!");
        Ok(())
    }
}


================================================
FILE: apd/src/main.rs
================================================
mod apd;
mod assets;
mod cli;
mod defs;
mod event;
mod lua;
mod magic_mount;
mod metamodule;
mod module;
mod module_config;
mod package;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod pty;
mod resetprop;
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
================================================
use crate::sepolicy::get_policy_main;
use crate::{lua, module_config};
use anyhow::{Context, Result, anyhow, bail, ensure};
use const_format::concatcp;
use is_executable::is_executable;
use java_properties::PropertiesIter;
use log::{debug, info, warn};
#[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, Component},
    process::Command,
    str::FromStr,
};
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!(
                "/data/adb:{}:{}",
                defs::BINARY_DIR.trim_end_matches('/'),
                env_var("PATH").unwrap_or_default()
            ),
        ),
    ]
}

// 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());
        let mut _sepol = get_policy_main(&[
            "magiskpolicy".to_string(),
            "--live".to_string(),
            "--apply".to_string(),
            rule_file.display().to_string(),
        ])?;

        Ok(())
    })?;

    Ok(())
}

pub fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
    info!("exec {}", path.as_ref().display());

    let is_module_script = path.as_ref().starts_with(defs::MODULE_DIR);
    // Extract module_id from path if it matches /data/adb/modules/{id}/...
    let module_id = if is_module_script {
        path.as_ref()
            .strip_prefix(defs::MODULE_DIR)
            .ok()
            .and_then(|p| p.components().next())
            .and_then(|c| c.as_os_str().to_str())
            .map(ToString::to_string)
    } else {
        None
    };

    if is_module_script && module_id.is_none() {
        debug!(
            "Failed to extract module_id from script path '{}'. Script will run without AP_MODULE environment variable.",
            path.as_ref().display()
        );
    }

    let is_elf = fs::read(path.as_ref())
        .ok()
        .and_then(|bytes| bytes.get(..4).map(|b| b.to_vec()))
        .map_or(false, |magic| magic == [0x7f, b'E', b'L', b'F']);

    let mut command = Command::new(if is_elf {
        path.as_ref().as_os_str().to_owned()
    } else {
        assets::BUSYBOX_PATH.into()
    });
    #[cfg(unix)]
    {
        command.process_group(0);
        unsafe {
            command.pre_exec(|| {
                switch_cgroups();
                Ok(())
            });
        }
    }
    command
        .current_dir(path.as_ref().parent().unwrap())
        .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('/')
            ),
        );
    if !is_elf {
        command
            .arg("sh")
            .arg(path.as_ref())
            .env("ASH_STANDALONE", "1");
    }
    if let Some(id) = module_id {
        command.env("AP_MODULE", id);
    }

    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());

        crate::resetprop::load_system_prop_file(&system_prop)?;

        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}");
        }

        // Clear module configs before removing module directory
        if let Err(e) = module_config::clear_module_configs(module_id) {
            warn!("Failed to clear configs for {module_id}: {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 it's safe to install regular module
    if !is_metamodule && 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)?;
    
    // Set SELinux context for module root directory and special files
    // This is critical for .img files that need to be loop-mounted
    #[cfg(unix)]
    {
        let module_update_path = Path::new(&_module_update_dir);
        if module_update_path.exists() {
            // Set adb_data_file context for the module root directory
            restorecon::lsetfilecon(&_module_update_dir, restorecon::ADB_CON)?;
            
            // Process special files like .img that need proper permissions for mounting
            if let Ok(entries) = fs::read_dir(&_module_update_dir) {
                for entry in entries.flatten() {
                    let path = entry.path();
                    if let Some(extension) = path.extension() {
                        if extension == "img" {
                            // Set proper permissions for image files (readable by all)
                            fs::set_permissions(&path, fs::Permissions::from_mode(0o644))?;
                            // Set SELinux context to allow loop mounting
                            restorecon::lsetfilecon(&path, restorecon::ADB_CON)?;
                            info!("Set permissions and SELinux context for: {:?}", path);
                        }
                    }
                }
            }
        }
    }

    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(())
}

pub fn undo_uninstall_module(id: &str) -> Result<()> {
    let module_path = Path::new(defs::MODULE_DIR).join(id);
    ensure!(module_path.exists(), "Module {id} not found");

    // Remove the remove mark
    let remove_file = module_path.join(defs::REMOVE_FILE_NAME);
    if remove_file.exists() {
        fs::remove_file(&remove_file)
            .with_context(|| format!("Failed to delete remove file for module '{id}'"))?;
        info!("Removed the remove mark for module {id}");
    }

    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(())
}

// Resolve a module icon path to an absolute on-disk path
fn resolve_module_icon_path(
    module_prop_map: &mut HashMap<String, String>,
    key: &str,
    module_path: &Path,
) {
    let module_id = module_prop_map.get("id").map(|s| s.as_str()).unwrap_or("");

    if let Some(icon_value) = module_prop_map.get(key).map(|v| v.trim()).filter(|v| !v.is_empty()) {
        let path = Path::new(icon_value);

        if path.is_absolute() || path.components().any(|c| matches!(c, Component::ParentDir)) {
            log::warn!("Rejected {} (invalid path) for module {}: {}", key, module_id, icon_value);
            return;
        }

        let candidate = module_path.join(path);

        if candidate.exists() && candidate.is_file() {
            if let Some(full_path) = candidate.to_str() {
                module_prop_map.insert(key.to_string(), full_path.to_string());
            }
        } else {
            log::debug!("{} not found for module {}: {}", key, module_id, candidate.display());
        }
    }
}

fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
    // Load all module configs once to minimize I/O overhead
    let all_configs = match module_config::get_all_module_configs() {
        Ok(configs) => configs,
        Err(e) => {
            warn!("Failed to load module configs: {e}");
            HashMap::new()
        }
    };

    // 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;

        if PropertiesIter::new_with_encoding(Cursor::new(content), encoding)
            .read_into(|k, v| {
                module_prop_map.insert(k, v);
            })
            .is_err()
        {
            warn!("Failed to parse module.prop: {}", module_prop.display());
            continue;
        }

        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());

        // Resolve and validate module icon paths for action and webui icons
        resolve_module_icon_path(&mut module_prop_map, "actionIcon", &path);
        resolve_module_icon_path(&mut module_prop_map, "webuiIcon", &path);

        // Apply module config overrides and extract managed features
        if let Some(module_id) = module_prop_map.get("id")
            && let Some(config) = all_configs.get(module_id.as_str())
        {
            // Apply override.description
            if let Some(desc) = config.get("override.description") {
                module_prop_map.insert("description".to_owned(), desc.clone());
            }
        }

        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/module_config.rs
================================================
use std::{
    collections::HashMap,
    fs::{self, File},
    io::{Read, Write},
    path::{Path, PathBuf},
};

use anyhow::{Context, Result, bail};
use log::{debug, warn};

use crate::{defs, utils::ensure_dir_exists};

#[allow(clippy::unreadable_literal)]
const MODULE_CONFIG_MAGIC: u32 = 0x4150544D; // "APTM"
const MODULE_CONFIG_VERSION: u32 = 1;

// Validation limits
pub const MAX_CONFIG_KEY_LEN: usize = 256;
pub const MAX_CONFIG_VALUE_LEN: usize = 1024 * 1024; // 1MB
pub const MAX_CONFIG_COUNT: usize = 32;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigType {
    Persist,
    Temp,
}

impl ConfigType {
    const fn filename(self) -> &'static str {
        match self {
            Self::Persist => defs::PERSIST_CONFIG_NAME,
            Self::Temp => defs::TEMP_CONFIG_NAME,
        }
    }
}

/// Validate config key
/// Uses the same validation rules as module_id: ^[a-zA-Z][a-zA-Z0-9._-]+$
/// - Must start with a letter (a-zA-Z)
/// - Followed by one or more alphanumeric, dot, underscore, or hyphen characters
/// - Minimum length: 2 characters
pub fn validate_config_key(key: &str) -> Result<()> {
    if key.is_empty() {
        bail!("Config key cannot be empty");
    }

    if key.len() > MAX_CONFIG_KEY_LEN {
        bail!(
            "Config key too long: {} bytes (max: {MAX_CONFIG_KEY_LEN})",
            key.len(),
        );
    }

    // Use same pattern as module_id for consistency
    let re = regex_lite::Regex::new(r"^[a-zA-Z][a-zA-Z0-9._-]+$")?;
    if !re.is_match(key) {
        bail!("Invalid config key: '{key}'. Must match /^[a-zA-Z][a-zA-Z0-9._-]+$/");
    }

    Ok(())
}

/// Validate config value
/// Only enforces maximum length - no character restrictions
/// Values are stored in binary format with length prefix, so any UTF-8 data is safe
pub fn validate_config_value(value: &str) -> Result<()> {
    if value.len() > MAX_CONFIG_VALUE_LEN {
        bail!(
            "Config value too long: {} bytes (max: {})",
            value.len(),
            MAX_CONFIG_VALUE_LEN
        );
    }

    // No character restrictions - binary storage format handles all UTF-8 safely
    Ok(())
}

/// Validate config count
fn validate_config_count(config: &HashMap<String, String>) -> Result<()> {
    if config.len() > MAX_CONFIG_COUNT {
        bail!(
            "Too many config entries: {} (max: {MAX_CONFIG_COUNT})",
            config.len(),
        );
    }
    Ok(())
}

/// Get the config directory path for a module
fn get_config_dir(module_id: &str) -> PathBuf {
    Path::new(defs::MODULE_CONFIG_DIR).join(module_id)
}

/// Get the config file path for a module
fn get_config_path(module_id: &str, config_type: ConfigType) -> PathBuf {
    get_config_dir(module_id).join(config_type.filename())
}

/// Ensure the config directory exists
fn ensure_config_dir(module_id: &str) -> Result<PathBuf> {
    let dir = get_config_dir(module_id);
    ensure_dir_exists(&dir)?;
    Ok(dir)
}

/// Load config from binary file
pub fn load_config(module_id: &str, config_type: ConfigType) -> Result<HashMap<String, String>> {
    let config_path = get_config_path(module_id, config_type);

    if !config_path.exists() {
        debug!("Config file not found: {}", config_path.display());
        return Ok(HashMap::new());
    }

    let mut file = File::open(&config_path)
        .with_context(|| format!("Failed to open config file: {}", config_path.display()))?;

    // Read magic
    let mut magic_buf = [0u8; 4];
    file.read_exact(&mut magic_buf)
        .with_context(|| "Failed to read magic")?;
    let magic = u32::from_le_bytes(magic_buf);

    if magic != MODULE_CONFIG_MAGIC {
        bail!("Invalid config magic: expected 0x{MODULE_CONFIG_MAGIC:08x}, got 0x{magic:08x}");
    }

    // Read version
    let mut version_buf = [0u8; 4];
    file.read_exact(&mut version_buf)
        .with_context(|| "Failed to read version")?;
    let version = u32::from_le_bytes(version_buf);

    if version != MODULE_CONFIG_VERSION {
        bail!("Unsupported config version: expected {MODULE_CONFIG_VERSION}, got {version}");
    }

    // Read count
    let mut count_buf = [0u8; 4];
    file.read_exact(&mut count_buf)
        .with_context(|| "Failed to read count")?;
    let count = u32::from_le_bytes(count_buf);

    // Read entries
    let mut config = HashMap::new();
    for i in 0..count {
        // Read key length
        let mut key_len_buf = [0u8; 4];
        file.read_exact(&mut key_len_buf)
            .with_context(|| format!("Failed to read key length for entry {i}"))?;
        let key_len = u32::from_le_bytes(key_len_buf) as usize;

        // Read key data
        let mut key_buf = vec![0u8; key_len];
        file.read_exact(&mut key_buf)
            .with_context(|| format!("Failed to read key data for entry {i}"))?;
        let key = String::from_utf8(key_buf)
            .with_context(|| format!("Invalid UTF-8 in key for entry {i}"))?;

        // Read value length
        let mut value_len_buf = [0u8; 4];
        file.read_exact(&mut value_len_buf)
            .with_context(|| format!("Failed to read value length for entry {i}"))?;
        let value_len = u32::from_le_bytes(value_len_buf) as usize;

        // Read value data
        let mut value_buf = vec![0u8; value_len];
        file.read_exact(&mut value_buf)
            .with_context(|| format!("Failed to read value data for entry {i}"))?;
        let value = String::from_utf8(value_buf)
            .with_context(|| format!("Invalid UTF-8 in value for entry {i}"))?;

        config.insert(key, value);
    }

    debug!(
        "Loaded {} entries from {}",
        config.len(),
        config_path.display()
    );
    Ok(config)
}

/// Save config to binary file
pub fn save_config(
    module_id: &str,
    config_type: ConfigType,
    config: &HashMap<String, String>,
) -> Result<()> {
    // Validate config count
    validate_config_count(config)?;

    // Validate all keys and values
    for (key, value) in config {
        validate_config_key(key).with_context(|| format!("Invalid config key: '{key}'"))?;
        validate_config_value(value)
            .with_context(|| format!("Invalid config value for key '{key}'"))?;
    }

    ensure_config_dir(module_id)?;

    let config_path = get_config_path(module_id, config_type);
    let temp_path = config_path.with_extension("tmp");

    // Write to temporary file first
    let mut file = File::create(&temp_path)
        .with_context(|| format!("Failed to create temp config file: {}", temp_path.display()))?;

    // Write magic
    file.write_all(&MODULE_CONFIG_MAGIC.to_le_bytes())
        .with_context(|| "Failed to write magic"
Download .txt
gitextract_v7dadxbv/

├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── actions/
│   │   └── setup-build-env/
│   │       └── action.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── CI_up.yml
│       └── build.yml
├── .gitignore
├── Build-Debug.bat
├── Build-Release.bat
├── LICENSE
├── README.md
├── README_EN.md
├── README_JA.md
├── apd/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── build.rs
│   └── src/
│       ├── apd.rs
│       ├── assets.rs
│       ├── banner
│       ├── cli.rs
│       ├── defs.rs
│       ├── event.rs
│       ├── install_jq.sh
│       ├── installer.sh
│       ├── installer_bind.sh
│       ├── lua.rs
│       ├── magic_mount.rs
│       ├── main.rs
│       ├── metamodule.rs
│       ├── module.rs
│       ├── module_config.rs
│       ├── package.rs
│       ├── pty.rs
│       ├── resetprop.rs
│       ├── restorecon.rs
│       ├── sepolicy.rs
│       ├── supercall.rs
│       └── utils.rs
├── app/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── libs/
│   │   └── arm64-v8a/
│   │       ├── .gitignore
│   │       ├── libkpatch.so.version
│   │       └── libkptools.so.version
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── aidl/
│           │   └── me/
│           │       └── bmax/
│           │           └── apatch/
│           │               └── IAPRootService.aidl
│           ├── assets/
│           │   ├── .gitignore
│           │   ├── InstallAP.sh
│           │   ├── UninstallAP.sh
│           │   ├── boot_extract.sh
│           │   ├── boot_flash.sh
│           │   ├── boot_patch.sh
│           │   ├── boot_unpatch.sh
│           │   ├── jq/
│           │   │   └── jq
│           │   ├── kpimg.version
│           │   └── util_functions.sh
│           ├── cpp/
│           │   ├── CMakeLists.txt
│           │   ├── apjni.cpp
│           │   ├── apjni.hpp
│           │   ├── jni_helper.hpp
│           │   ├── security.cpp
│           │   ├── security.hpp
│           │   ├── supercall.h
│           │   ├── type_traits.hpp
│           │   ├── uapi/
│           │   │   └── scdefs.h
│           │   └── version
│           ├── java/
│           │   └── me/
│           │       └── bmax/
│           │           └── apatch/
│           │               ├── APatchApp.kt
│           │               ├── Natives.kt
│           │               ├── data/
│           │               │   └── ScriptInfo.kt
│           │               ├── services/
│           │               │   └── RootServices.java
│           │               ├── ui/
│           │               │   ├── CrashHandleActivity.kt
│           │               │   ├── MainActivity.kt
│           │               │   ├── WebUIActivity.kt
│           │               │   ├── component/
│           │               │   │   ├── Dialog.kt
│           │               │   │   ├── DropdownMenu.kt
│           │               │   │   ├── ExpressiveCard.kt
│           │               │   │   ├── ExpressiveSwitch.kt
│           │               │   │   ├── FilePicker.kt
│           │               │   │   ├── KeyEventBlocker.kt
│           │               │   │   ├── KpmAutoLoadConfig.kt
│           │               │   │   ├── LoadingIndicator.kt
│           │               │   │   ├── ModuleCardComponents.kt
│           │               │   │   ├── SearchBar.kt
│           │               │   │   ├── SectionHeader.kt
│           │               │   │   ├── SegmentedControl.kt
│           │               │   │   ├── SettingsItem.kt
│           │               │   │   ├── SplicedColumnGroup.kt
│           │               │   │   ├── SplicedLazyColumn.kt
│           │               │   │   ├── ThemeColorPicker.kt
│           │               │   │   ├── ThemeModeSelector.kt
│           │               │   │   ├── ToggleSettingCard.kt
│           │               │   │   ├── TwoColumnGrid.kt
│           │               │   │   ├── UmountConfig.kt
│           │               │   │   ├── UpdateDialog.kt
│           │               │   │   ├── WarningCard.kt
│           │               │   │   └── chart/
│           │               │   │       ├── ChartUtils.kt
│           │               │   │       ├── ModulePieChart.kt
│           │               │   │       ├── StorageColumnChart.kt
│           │               │   │       ├── SystemAreaChart.kt
│           │               │   │       └── SystemLineChart.kt
│           │               │   ├── model/
│           │               │   │   └── ApiMarketplaceItem.kt
│           │               │   ├── screen/
│           │               │   │   ├── APM.kt
│           │               │   │   ├── AboutScreen.kt
│           │               │   │   ├── ApiMarketplace.kt
│           │               │   │   ├── ApmBulkInstallScreen.kt
│           │               │   │   ├── AppProfile.kt
│           │               │   │   ├── BannerApiService.kt
│           │               │   │   ├── BottomBarDestination.kt
│           │               │   │   ├── ExecuteAPMAction.kt
│           │               │   │   ├── Home.kt
│           │               │   │   ├── HomeCircle.kt
│           │               │   │   ├── HomeStats.kt
│           │               │   │   ├── HomeV2.kt
│           │               │   │   ├── HomeV3.kt
│           │               │   │   ├── HomeV4.kt
│           │               │   │   ├── Install.kt
│           │               │   │   ├── InstallModeSelect.kt
│           │               │   │   ├── KPM.kt
│           │               │   │   ├── KpmAutoLoadConfigScreen.kt
│           │               │   │   ├── MyThemesScreen.kt
│           │               │   │   ├── OnlineKPMScreen.kt
│           │               │   │   ├── OnlineModuleScreen.kt
│           │               │   │   ├── OnlineScriptScreen.kt
│           │               │   │   ├── Patches.kt
│           │               │   │   ├── ScriptExecutionLogScreen.kt
│           │               │   │   ├── ScriptLibrary.kt
│           │               │   │   ├── Settings.kt
│           │               │   │   ├── SuAuditLogScreen.kt
│           │               │   │   ├── SuperUser.kt
│           │               │   │   ├── ThemeStore.kt
│           │               │   │   └── settings/
│           │               │   │       ├── AppearanceSettings.kt
│           │               │   │       ├── AppearanceSettingsScreen.kt
│           │               │   │       ├── BackupSettings.kt
│           │               │   │       ├── BackupSettingsScreen.kt
│           │               │   │       ├── BehaviorSettings.kt
│           │               │   │       ├── BehaviorSettingsScreen.kt
│           │               │   │       ├── FunctionSettings.kt
│           │               │   │       ├── FunctionSettingsScreen.kt
│           │               │   │       ├── GeneralSettings.kt
│           │               │   │       ├── GeneralSettingsScreen.kt
│           │               │   │       ├── LanguagePickerScreen.kt
│           │               │   │       ├── ModuleSettings.kt
│           │               │   │       ├── ModuleSettingsScreen.kt
│           │               │   │       ├── MultimediaSettings.kt
│           │               │   │       ├── MultimediaSettingsScreen.kt
│           │               │   │       ├── SecuritySettings.kt
│           │               │   │       ├── SecuritySettingsScreen.kt
│           │               │   │       └── SettingsShared.kt
│           │               │   ├── theme/
│           │               │   │   ├── AmberTheme.kt
│           │               │   │   ├── BackgroundConfig.kt
│           │               │   │   ├── BackgroundLayer.kt
│           │               │   │   ├── BackupConfig.kt
│           │               │   │   ├── BlueGreyTheme.kt
│           │               │   │   ├── BlueTheme.kt
│           │               │   │   ├── BrownTheme.kt
│           │               │   │   ├── CardManage.kt
│           │               │   │   ├── CyanTheme.kt
│           │               │   │   ├── DeepOrangeTheme.kt
│           │               │   │   ├── DeepPurpleTheme.kt
│           │               │   │   ├── FontConfig.kt
│           │               │   │   ├── GreenTheme.kt
│           │               │   │   ├── IndigoTheme.kt
│           │               │   │   ├── InkWashTheme.kt
│           │               │   │   ├── LightBlueTheme.kt
│           │               │   │   ├── LightGreenTheme.kt
│           │               │   │   ├── LimeTheme.kt
│           │               │   │   ├── MusicConfig.kt
│           │               │   │   ├── OrangeTheme.kt
│           │               │   │   ├── PinkTheme.kt
│           │               │   │   ├── PurpleTheme.kt
│           │               │   │   ├── RedTheme.kt
│           │               │   │   ├── SakuraTheme.kt
│           │               │   │   ├── SoundEffectConfig.kt
│           │               │   │   ├── TealTheme.kt
│           │               │   │   ├── Theme.kt
│           │               │   │   ├── ThemeManager.kt
│           │               │   │   ├── Type.kt
│           │               │   │   ├── VibrationConfig.kt
│           │               │   │   └── YellowTheme.kt
│           │               │   ├── viewmodel/
│           │               │   │   ├── APModuleViewModel.kt
│           │               │   │   ├── ApiMarketplaceViewModel.kt
│           │               │   │   ├── DashboardViewModel.kt
│           │               │   │   ├── KPModel.kt
│           │               │   │   ├── KPModuleViewModel.kt
│           │               │   │   ├── OnlineKPMViewModel.kt
│           │               │   │   ├── OnlineModuleViewModel.kt
│           │               │   │   ├── OnlineScriptViewModel.kt
│           │               │   │   ├── PatchesViewModel.kt
│           │               │   │   ├── ScriptLibraryViewModel.kt
│           │               │   │   ├── SuperUserViewModel.kt
│           │               │   │   └── ThemeStoreViewModel.kt
│           │               │   └── webui/
│           │               │       ├── AppIconUtil.kt
│           │               │       ├── MimeUtil.java
│           │               │       ├── MonetColorsProvider.kt
│           │               │       ├── SuFilePathHandler.java
│           │               │       └── WebViewInterface.kt
│           │               └── util/
│           │                   ├── APatchCli.kt
│           │                   ├── APatchKeyHelper.java
│           │                   ├── AppData.kt
│           │                   ├── BackupLogManager.kt
│           │                   ├── BiometricUtils.kt
│           │                   ├── BulkInstallManager.kt
│           │                   ├── ComposePrefs.kt
│           │                   ├── DPIUtils.kt
│           │                   ├── DeviceInfoUtils.kt
│           │                   ├── Downloader.kt
│           │                   ├── FolkApiClient.kt
│           │                   ├── HanziToPinyin.java
│           │                   ├── HardwareMonitor.kt
│           │                   ├── IOStreamUtils.kt
│           │                   ├── LauncherIconUtils.kt
│           │                   ├── LogEvent.kt
│           │                   ├── ModuleBackupUtils.kt
│           │                   ├── ModuleShortcut.kt
│           │                   ├── MusicManager.kt
│           │                   ├── PermissionUtils.kt
│           │                   ├── PkgConfig.kt
│           │                   ├── SafeFileProvider.kt
│           │                   ├── SafeUriResolver.kt
│           │                   ├── ScriptLibraryManager.kt
│           │                   ├── SoundEffectManager.kt
│           │                   ├── SuAuditLog.kt
│           │                   ├── ThemeDownloader.kt
│           │                   ├── UpdateChecker.kt
│           │                   ├── Version.kt
│           │                   ├── VibrationManager.kt
│           │                   ├── WebDavUtils.kt
│           │                   └── ui/
│           │                       ├── APDialogBlurBehindUtils.kt
│           │                       ├── AnsiUtils.kt
│           │                       ├── CompositionProvider.kt
│           │                       ├── GlassEffectHelper.kt
│           │                       ├── HomeBottomSpacer.kt
│           │                       ├── HyperlinkText.kt
│           │                       ├── NavigationBarsSpacer.kt
│           │                       └── ToastExt.kt
│           └── res/
│               ├── drawable/
│               │   ├── device_mobile_down.xml
│               │   ├── github.xml
│               │   ├── ic_clear_background.xml
│               │   ├── ic_custom_background.xml
│               │   ├── ic_launcher_foreground_alt.xml
│               │   ├── ic_launcher_monochrome.xml
│               │   ├── ic_launcher_monochrome_alt.xml
│               │   ├── info_circle_filled.xml
│               │   ├── package_import.xml
│               │   ├── play_circle.xml
│               │   ├── settings.xml
│               │   ├── telegram.xml
│               │   ├── trash.xml
│               │   └── webui.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   ├── ic_launcher_alt.xml
│               │   ├── ic_launcher_alt_round.xml
│               │   └── ic_launcher_round.xml
│               ├── resources.properties
│               ├── values/
│               │   ├── arrays.xml
│               │   ├── colors.xml
│               │   ├── ic_launcher_background.xml
│               │   ├── strings.xml
│               │   └── themes.xml
│               ├── values-ar/
│               │   └── strings.xml
│               ├── values-es/
│               │   └── strings.xml
│               ├── values-in/
│               │   └── strings.xml
│               ├── values-ja/
│               │   └── strings.xml
│               ├── values-ko/
│               │   └── strings.xml
│               ├── values-mgl/
│               │   └── strings.xml
│               ├── values-night/
│               │   └── themes.xml
│               ├── values-pl/
│               │   └── strings.xml
│               ├── values-pt-rBR/
│               │   └── strings.xml
│               ├── values-ru/
│               │   └── strings.xml
│               ├── values-tr-rTR/
│               │   └── strings.xml
│               ├── values-vi/
│               │   └── strings.xml
│               ├── values-zh-rAG/
│               │   └── strings.xml
│               ├── values-zh-rAT/
│               │   └── strings.xml
│               ├── values-zh-rCK/
│               │   └── strings.xml
│               ├── values-zh-rCN/
│               │   └── strings.xml
│               ├── values-zh-rMC/
│               │   └── strings.xml
│               ├── values-zh-rTW/
│               │   └── strings.xml
│               ├── values-zh-rWC/
│               │   └── strings.xml
│               └── xml/
│                   ├── backup_rules.xml
│                   ├── data_extraction_rules.xml
│                   ├── file_paths.xml
│                   └── network_security_config.xml
├── auth.properties.template
├── build.gradle.kts
├── fastlane/
│   └── metadata/
│       └── android/
│           ├── en-US/
│           │   ├── full_description.txt
│           │   └── short_description.txt
│           └── pl-PL/
│               ├── full_description.txt
│               └── short_description.txt
├── fpd/
│   ├── Cargo.toml
│   └── src/
│       ├── main.rs
│       ├── prop_patch.rs
│       └── umount.rs
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── keystore.properties.template
├── local.properties.template
├── scripts/
│   ├── Build-Debug.sh
│   ├── Build-Release.sh
│   ├── init-wsl.sh
│   ├── setup-wsl.sh
│   ├── update_binary.sh
│   └── update_script.sh
└── settings.gradle.kts
Download .txt
SYMBOL INDEX (700 symbols across 34 files)

FILE: apd/build.rs
  function get_git_version (line 3) | fn get_git_version() -> Result<(u32, String), std::io::Error> {
  function main (line 32) | 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 ModuleConfigCmd (line 109) | enum ModuleConfigCmd {
  function run (line 150) | 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 MAGIC_MOUNT_FILE (line 10) | pub const MAGIC_MOUNT_FILE: &str = concatcp!(ADB_DIR, ".magic_mount_enab...
  constant HIDE_SERVICE_FILE (line 11) | pub const HIDE_SERVICE_FILE: &str = concatcp!(ADB_DIR, ".hide_service_en...
  constant HIDE_BINARY_PATH (line 12) | pub const HIDE_BINARY_PATH: &str = concatcp!(ADB_DIR, "fp/bin/fpd");
  constant UMOUNT_SERVICE_FILE (line 13) | pub const UMOUNT_SERVICE_FILE: &str = concatcp!(ADB_DIR, ".umount_servic...
  constant UMOUNT_BINARY_PATH (line 14) | pub const UMOUNT_BINARY_PATH: &str = concatcp!(ADB_DIR, "fp/bin/fpd");
  constant UTS_SPOOF_ENABLE_FILE (line 15) | pub const UTS_SPOOF_ENABLE_FILE: &str = concatcp!(ADB_DIR, ".uts_spoof_e...
  constant UTS_SPOOF_CONFIG_FILE (line 16) | pub const UTS_SPOOF_CONFIG_FILE: &str = concatcp!(ADB_DIR, ".uts_spoof_c...
  constant UTS_SPOOF_BOOT_PENDING (line 17) | pub const UTS_SPOOF_BOOT_PENDING: &str = concatcp!(ADB_DIR, ".uts_spoof_...
  constant DAEMON_PATH (line 18) | pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "apd");
  constant AUTO_EXCLUDE_KNOWN_PACKAGES_FILE (line 19) | pub const AUTO_EXCLUDE_KNOWN_PACKAGES_FILE: &str = concatcp!(WORKING_DIR...
  constant PATHHIDE_DIR (line 21) | pub const PATHHIDE_DIR: &str = concatcp!(ADB_DIR, "fp/pathhide/");
  constant PATHHIDE_ENABLE_FILE (line 22) | pub const PATHHIDE_ENABLE_FILE: &str = concatcp!(ADB_DIR, "fp/pathhide/e...
  constant PATHHIDE_PATHS_FILE (line 23) | pub const PATHHIDE_PATHS_FILE: &str = concatcp!(ADB_DIR, "fp/pathhide/pa...
  constant PATHHIDE_UIDS_FILE (line 24) | pub const PATHHIDE_UIDS_FILE: &str = concatcp!(ADB_DIR, "fp/pathhide/uid...
  constant PATHHIDE_UID_MODE_FILE (line 25) | pub const PATHHIDE_UID_MODE_FILE: &str = concatcp!(ADB_DIR, "fp/pathhide...
  constant PATHHIDE_FILTER_SYSTEM_FILE (line 26) | pub const PATHHIDE_FILTER_SYSTEM_FILE: &str = concatcp!(ADB_DIR, "fp/pat...
  constant NETISOLATE_DIR (line 28) | pub const NETISOLATE_DIR: &str = concatcp!(ADB_DIR, "fp/netisolate/");
  constant NETISOLATE_ENABLE_FILE (line 29) | pub const NETISOLATE_ENABLE_FILE: &str = concatcp!(ADB_DIR, "fp/netisola...
  constant NETISOLATE_UIDS_FILE (line 30) | pub const NETISOLATE_UIDS_FILE: &str = concatcp!(ADB_DIR, "fp/netisolate...
  constant MODULE_DIR (line 32) | pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/");
  constant AP_MAGIC_MOUNT_SOURCE (line 33) | pub const AP_MAGIC_MOUNT_SOURCE: &str = concatcp!(WORKING_DIR, "magic_mo...
  constant MODULE_UPDATE_DIR (line 36) | pub const MODULE_UPDATE_DIR: &str = concatcp!(ADB_DIR, "modules_update/");
  constant TEMP_DIR (line 38) | pub const TEMP_DIR: &str = "/debug_ramdisk";
  constant TEMP_DIR_LEGACY (line 39) | pub const TEMP_DIR_LEGACY: &str = "/sbin";
  constant MODULE_WEB_DIR (line 41) | pub const MODULE_WEB_DIR: &str = "webroot";
  constant MODULE_ACTION_SH (line 42) | pub const MODULE_ACTION_SH: &str = "action.sh";
  constant DISABLE_FILE_NAME (line 43) | pub const DISABLE_FILE_NAME: &str = "disable";
  constant SKIP_MOUNT_FILE_NAME (line 44) | pub const SKIP_MOUNT_FILE_NAME: &str = "skip_mount";
  constant UPDATE_FILE_NAME (line 45) | pub const UPDATE_FILE_NAME: &str = "update";
  constant REMOVE_FILE_NAME (line 46) | pub const REMOVE_FILE_NAME: &str = "remove";
  constant METAMODULE_MOUNT_SCRIPT (line 49) | pub const METAMODULE_MOUNT_SCRIPT: &str = "metamount.sh";
  constant METAMODULE_METAINSTALL_SCRIPT (line 50) | pub const METAMODULE_METAINSTALL_SCRIPT: &str = "metainstall.sh";
  constant METAMODULE_METAUNINSTALL_SCRIPT (line 51) | pub const METAMODULE_METAUNINSTALL_SCRIPT: &str = "metauninstall.sh";
  constant METAMODULE_DIR (line 52) | pub const METAMODULE_DIR: &str = concatcp!(ADB_DIR, "metamodule/");
  constant FP_KPMS_DIR (line 54) | pub const FP_KPMS_DIR: &str = concatcp!(ADB_DIR, "fp/kpms/");
  constant FP_KPMS_AUTOLOAD_DIR (line 55) | pub const FP_KPMS_AUTOLOAD_DIR: &str = concatcp!(ADB_DIR, "fp/kpms/autol...
  constant KPM_AUTOLOAD_CONFIG (line 56) | pub const KPM_AUTOLOAD_CONFIG: &str = concatcp!(ADB_DIR, "fp/kpms/kpm_au...
  constant MODULE_CONFIG_DIR (line 59) | pub const MODULE_CONFIG_DIR: &str = concatcp!(WORKING_DIR, "module_confi...
  constant PERSIST_CONFIG_NAME (line 60) | pub const PERSIST_CONFIG_NAME: &str = "persist.config";
  constant TEMP_CONFIG_NAME (line 61) | pub const TEMP_CONFIG_NAME: &str = "tmp.config";
  constant PTS_NAME (line 63) | pub const PTS_NAME: &str = "pts";
  constant VERSION_CODE (line 65) | pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/V...
  constant VERSION_NAME (line 66) | pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/V...

FILE: apd/src/event.rs
  function report_kernel (line 29) | pub fn report_kernel(superkey: Option<String>, event: &str, state: &str)...
  function setup_fp_directories (line 41) | fn setup_fp_directories() -> Result<()> {
  function setup_logging (line 55) | fn setup_logging() -> Result<()> {
  function disable_all_modules_safe (line 106) | fn disable_all_modules_safe() {
  function on_post_data_fs (line 112) | pub fn on_post_data_fs(superkey: Option<String>) -> Result<()> {
  function run_stage (line 265) | fn run_stage(stage: &str, superkey: Option<String>, block: bool) {
  function on_services (line 295) | pub fn on_services(superkey: Option<String>) -> Result<()> {
  function run_uid_monitor (line 305) | fn run_uid_monitor() {
  function on_boot_completed (line 327) | pub fn on_boot_completed(superkey: Option<String>) -> Result<()> {
  function start_uid_listener (line 390) | 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/magic_mount.rs
  constant REPLACE_DIR_FILE_NAME (line 30) | const REPLACE_DIR_FILE_NAME: &str = ".replace";
  constant REPLACE_DIR_XATTR (line 31) | const REPLACE_DIR_XATTR: &str = "trusted.overlay.opaque";
  type NodeFileType (line 34) | enum NodeFileType {
    method from_file_type (line 42) | fn from_file_type(file_type: FileType) -> Self {
    method needs_tmpfs_vs_real (line 56) | fn needs_tmpfs_vs_real(&self, real_path: &Path) -> bool {
  type Node (line 72) | struct Node {
    method collect_module_files (line 83) | fn collect_module_files<P>(&mut self, module_dir: P) -> Result<bool>
    method dir_is_replace (line 109) | fn dir_is_replace<P>(path: P) -> bool
    method new_root (line 122) | fn new_root<T: ToString>(name: T) -> Self {
    method new_module (line 133) | fn new_module<S>(name: &S, entry: &DirEntry) -> Option<Self>
  function collect_module_files (line 162) | fn collect_module_files() -> Result<Option<Node>> {
  function clone_symlink (line 230) | fn clone_symlink<Src: AsRef<Path>, Dst: AsRef<Path>>(src: Src, dst: Dst)...
  function mount_mirror (line 243) | fn mount_mirror<P: AsRef<Path>, WP: AsRef<Path>>(
  function should_create_tmpfs (line 290) | fn should_create_tmpfs(path: &Path, current: &mut Node, has_tmpfs: bool)...
  function prepare_tmpfs_skeleton (line 311) | fn prepare_tmpfs_skeleton(
  function handle_mount_result (line 340) | fn handle_mount_result(result: Result<()>, path: &Path, name: &str, has_...
  function process_existing_entries (line 350) | fn process_existing_entries(
  function process_remaining_children (line 375) | fn process_remaining_children(
  function move_tmpfs_to_target (line 392) | fn move_tmpfs_to_target(work_dir_path: &Path, target: &Path) -> Result<(...
  function do_magic_mount (line 403) | fn do_magic_mount<P: AsRef<Path>, WP: AsRef<Path>>(
  function magic_mount (line 483) | pub fn magic_mount() -> Result<()> {

FILE: apd/src/main.rs
  function main (line 19) | 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 200) | pub fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
  function exec_stage_script (line 274) | pub fn exec_stage_script(stage: &str, block: bool) -> Result<()> {
  function exec_common_scripts (line 286) | pub fn exec_common_scripts(dir: &str, wait: bool) -> Result<()> {
  function load_system_prop (line 308) | pub fn load_system_prop() -> Result<()> {
  function prune_modules (line 324) | pub fn prune_modules() -> Result<()> {
  function _install_module (line 384) | fn _install_module(zip: &str) -> Result<()> {
  function install_module (line 534) | pub fn install_module(zip: &str) -> Result<()> {
  function _uninstall_module (line 539) | pub fn _uninstall_module(id: &str, update_dir: &str) -> Result<()> {
  function uninstall_module (line 580) | pub fn uninstall_module(id: &str) -> Result<()> {
  function undo_uninstall_module (line 586) | pub fn undo_uninstall_module(id: &str) -> Result<()> {
  function read_module_prop (line 602) | pub fn read_module_prop(module_path: &Path) -> Result<HashMap<String, St...
  function run_action (line 623) | pub fn run_action(id: &str) -> Result<()> {
  function _change_module_state (line 634) | fn _change_module_state(module_dir: &str, mid: &str, enable: bool) -> Re...
  function _enable_module (line 655) | pub fn _enable_module(id: &str, update_dir: &Path) -> Result<()> {
  function enable_module (line 664) | pub fn enable_module(id: &str) -> Result<()> {
  function _disable_module (line 670) | pub fn _disable_module(id: &str, update_dir: &Path) -> Result<()> {
  function disable_module (line 679) | pub fn disable_module(id: &str) -> Result<()> {
  function _disable_all_modules (line 686) | pub fn _disable_all_modules(dir: &str) -> Result<()> {
  function disable_all_modules (line 698) | pub fn disable_all_modules() -> Result<()> {
  function resolve_module_icon_path (line 710) | fn resolve_module_icon_path(
  function _list_modules (line 737) | fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
  function list_modules (line 828) | pub fn list_modules() -> Result<()> {

FILE: apd/src/module_config.rs
  constant MODULE_CONFIG_MAGIC (line 14) | const MODULE_CONFIG_MAGIC: u32 = 0x4150544D;
  constant MODULE_CONFIG_VERSION (line 15) | const MODULE_CONFIG_VERSION: u32 = 1;
  constant MAX_CONFIG_KEY_LEN (line 18) | pub const MAX_CONFIG_KEY_LEN: usize = 256;
  constant MAX_CONFIG_VALUE_LEN (line 19) | pub const MAX_CONFIG_VALUE_LEN: usize = 1024 * 1024;
  constant MAX_CONFIG_COUNT (line 20) | pub const MAX_CONFIG_COUNT: usize = 32;
  type ConfigType (line 23) | pub enum ConfigType {
    method filename (line 29) | const fn filename(self) -> &'static str {
  function validate_config_key (line 42) | pub fn validate_config_key(key: &str) -> Result<()> {
  function validate_config_value (line 66) | pub fn validate_config_value(value: &str) -> Result<()> {
  function validate_config_count (line 80) | fn validate_config_count(config: &HashMap<String, String>) -> Result<()> {
  function get_config_dir (line 91) | fn get_config_dir(module_id: &str) -> PathBuf {
  function get_config_path (line 96) | fn get_config_path(module_id: &str, config_type: ConfigType) -> PathBuf {
  function ensure_config_dir (line 101) | fn ensure_config_dir(module_id: &str) -> Result<PathBuf> {
  function load_config (line 108) | pub fn load_config(module_id: &str, config_type: ConfigType) -> Result<H...
  function save_config (line 186) | pub fn save_config(
  function get_config_value (line 268) | pub fn get_config_value(
  function set_config_value (line 278) | pub fn set_config_value(
  function delete_config_value (line 297) | pub fn delete_config_value(module_id: &str, key: &str, config_type: Conf...
  function clear_config (line 309) | pub fn clear_config(module_id: &str, config_type: ConfigType) -> Result<...
  function merge_configs (line 322) | pub fn merge_configs(module_id: &str) -> Result<HashMap<String, String>> {
  function get_all_module_configs (line 349) | pub fn get_all_module_configs() -> Result<HashMap<String, HashMap<String...
  function clear_all_temp_configs (line 387) | pub fn clear_all_temp_configs() -> Result<()> {
  function clear_module_configs (line 429) | pub fn clear_module_configs(module_id: &str) -> Result<()> {

FILE: apd/src/package.rs
  constant DEFAULT_SCONTEXT (line 16) | const DEFAULT_SCONTEXT: &str = "u:r:untrusted_app:s0";
  constant MAGISK_SCONTEXT (line 17) | const MAGISK_SCONTEXT: &str = "u:r:magisk:s0";
  type PackageConfig (line 20) | pub struct PackageConfig {
  function read_known_user_packages (line 29) | fn read_known_user_packages() -> HashSet<String> {
  function write_known_user_packages (line 42) | fn write_known_user_packages(packages: &HashSet<String>) -> io::Result<(...
  function list_user_packages (line 52) | fn list_user_packages() -> HashSet<String> {
  function sync_auto_exclude_new_apps (line 83) | pub fn sync_auto_exclude_new_apps(
  function read_ap_package_config (line 140) | pub fn read_ap_package_config() -> Vec<PackageConfig> {
  function write_ap_package_config (line 175) | pub fn write_ap_package_config(package_configs: &[PackageConfig]) -> io:...
  function read_lines (line 223) | fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
  function synchronize_package_uid (line 230) | 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/resetprop.rs
  type WaitTimeoutError (line 14) | pub struct WaitTimeoutError {
    method fmt (line 19) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  type Args (line 29) | pub struct Args {
  type ResetPropParser (line 84) | struct ResetPropParser {
  function resetprop_main (line 89) | pub fn resetprop_main(args: &[String]) -> ! {
  function run_from_args (line 105) | fn run_from_args(args: &[String]) -> Result<()> {
  function execute (line 123) | pub fn execute(cli: &Args) -> Result<()> {
  function load_system_prop_file (line 229) | pub fn load_system_prop_file(path: &Path) -> 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 WriteAdapter (line 8) | struct WriteAdapter<T>(T);
  function write_str (line 11) | fn write_str(&mut self, s: &str) -> std::fmt::Result {
  type Args (line 19) | pub struct Args {
  type MagiskPolicyParser (line 64) | struct MagiskPolicyParser {
  function policy_main (line 69) | pub fn policy_main(args: &[String]) -> ! {
  function run_from_args (line 80) | fn run_from_args(args: &[String]) -> Result<()> {
  function get_policy_main (line 98) | pub fn get_policy_main(args: &[String]) -> Result<SePolicy> {
  function execute (line 125) | pub fn execute(cli: &Args) -> Result<()> {
  function execute_next (line 148) | fn execute_next(cli: &Args, sepol: &mut SePolicy) -> Result<()> {
  function print_usage (line 191) | fn print_usage(cmd: &str) {

FILE: apd/src/supercall.rs
  constant MAJOR (line 14) | const MAJOR: c_long = 0;
  constant MINOR (line 15) | const MINOR: c_long = 13;
  constant PATCH (line 16) | const PATCH: c_long = 1;
  constant KSTORAGE_EXCLUDE_LIST_GROUP (line 18) | const KSTORAGE_EXCLUDE_LIST_GROUP: i32 = 1;
  constant KSTORAGE_AUTO_EXCLUDE_GROUP (line 19) | const KSTORAGE_AUTO_EXCLUDE_GROUP: i32 = 3;
  constant __NR_SUPERCALL (line 21) | const __NR_SUPERCALL: c_long = 45;
  constant SUPERCALL_SU (line 22) | const SUPERCALL_SU: c_long = 0x1010;
  constant SUPERCALL_KSTORAGE_WRITE (line 23) | const SUPERCALL_KSTORAGE_WRITE: c_long = 0x1041;
  constant SUPERCALL_KSTORAGE_READ (line 24) | const SUPERCALL_KSTORAGE_READ: c_long = 0x1042;
  constant SUPERCALL_SU_GRANT_UID (line 25) | const SUPERCALL_SU_GRANT_UID: c_long = 0x1100;
  constant SUPERCALL_SU_REVOKE_UID (line 26) | const SUPERCALL_SU_REVOKE_UID: c_long = 0x1101;
  constant SUPERCALL_SU_NUMS (line 27) | const SUPERCALL_SU_NUMS: c_long = 0x1102;
  constant SUPERCALL_SU_LIST (line 28) | const SUPERCALL_SU_LIST: c_long = 0x1103;
  constant SUPERCALL_SU_RESET_PATH (line 29) | const SUPERCALL_SU_RESET_PATH: c_long = 0x1111;
  constant SUPERCALL_SU_GET_SAFEMODE (line 30) | const SUPERCALL_SU_GET_SAFEMODE: c_long = 0x1112;
  constant SUPERCALL_KPM_LOAD (line 32) | const SUPERCALL_KPM_LOAD: c_long = 0x1020;
  constant SUPERCALL_UTS_SET (line 34) | const SUPERCALL_UTS_SET: c_long = 0x1050;
  constant SUPERCALL_UTS_RESET (line 35) | const SUPERCALL_UTS_RESET: c_long = 0x1051;
  constant SUPERCALL_PATHHIDE_ENABLE (line 37) | const SUPERCALL_PATHHIDE_ENABLE: c_long = 0x1064;
  constant SUPERCALL_PATHHIDE_ADD (line 38) | const SUPERCALL_PATHHIDE_ADD: c_long = 0x1060;
  constant SUPERCALL_PATHHIDE_CLEAR (line 39) | const SUPERCALL_PATHHIDE_CLEAR: c_long = 0x1063;
  constant SUPERCALL_PATHHIDE_UID_MODE (line 40) | const SUPERCALL_PATHHIDE_UID_MODE: c_long = 0x106A;
  constant SUPERCALL_PATHHIDE_UID_ADD (line 41) | const SUPERCALL_PATHHIDE_UID_ADD: c_long = 0x1066;
  constant SUPERCALL_PATHHIDE_UID_CLEAR (line 42) | const SUPERCALL_PATHHIDE_UID_CLEAR: c_long = 0x1069;
  constant SUPERCALL_PATHHIDE_FILTER_SYSTEM (line 43) | const SUPERCALL_PATHHIDE_FILTER_SYSTEM: c_long = 0x106B;
  constant SUPERCALL_NETISOLATE_ENABLE (line 45) | const SUPERCALL_NETISOLATE_ENABLE: c_long = 0x1070;
  constant SUPERCALL_NETISOLATE_UID_ADD (line 46) | const SUPERCALL_NETISOLATE_UID_ADD: c_long = 0x1072;
  constant SUPERCALL_NETISOLATE_UID_REMOVE (line 47) | const SUPERCALL_NETISOLATE_UID_REMOVE: c_long = 0x1073;
  constant SUPERCALL_NETISOLATE_UID_LIST (line 48) | const SUPERCALL_NETISOLATE_UID_LIST: c_long = 0x1074;
  constant SUPERCALL_NETISOLATE_UID_CLEAR (line 49) | const SUPERCALL_NETISOLATE_UID_CLEAR: c_long = 0x1075;
  constant SUPERCALL_SCONTEXT_LEN (line 51) | const SUPERCALL_SCONTEXT_LEN: usize = 0x60;
  type SuProfile (line 54) | struct SuProfile {
  function ver_and_cmd (line 60) | fn ver_and_cmd(cmd: c_long) -> c_long {
  function sc_su_revoke_uid (line 65) | fn sc_su_revoke_uid(key: &CStr, uid: uid_t) -> c_long {
  function sc_su_grant_uid (line 79) | fn sc_su_grant_uid(key: &CStr, profile: &SuProfile) -> c_long {
  function sc_kstorage_write (line 93) | fn sc_kstorage_write(
  function sc_kstorage_read (line 117) | fn sc_kstorage_read(
  function sc_set_ap_mod_exclude (line 141) | fn sc_set_ap_mod_exclude(key: &CStr, uid: i64, exclude: i32) -> c_long {
  function get_new_app_profile_mode (line 152) | pub fn get_new_app_profile_mode() -> i32 {
  function sc_su_get_safemode (line 169) | pub fn sc_su_get_safemode(key: &CStr) -> c_long {
  function sc_su (line 190) | fn sc_su(key: &CStr, profile: &SuProfile) -> c_long {
  function sc_su_reset_path (line 204) | fn sc_su_reset_path(key: &CStr, path: &CStr) -> c_long {
  function sc_kpm_load (line 218) | fn sc_kpm_load(key: &CStr, path: &CStr, args: &CStr) -> c_long {
  function sc_su_uid_nums (line 234) | fn sc_su_uid_nums(key: &CStr) -> c_long {
  function sc_su_allow_uids (line 241) | fn sc_su_allow_uids(key: &CStr, buf: &mut [uid_t]) -> c_long {
  function read_file_to_string (line 259) | fn read_file_to_string(path: &str) -> io::Result<String> {
  function convert_string_to_u8_array (line 266) | fn convert_string_to_u8_array(s: &str) -> [u8; SUPERCALL_SCONTEXT_LEN] {
  function convert_superkey (line 274) | fn convert_superkey(s: &Option<String>) -> Option<CString> {
  function refresh_ap_package_list (line 278) | pub fn refresh_ap_package_list(skey: &CStr, mutex: &Arc<Mutex<()>>) {
  function privilege_apd_profile (line 346) | pub fn privilege_apd_profile(superkey: &Option<String>) {
  function init_load_su_path (line 361) | pub fn init_load_su_path(superkey: &Option<String>) {
  function autoload_kpm_modules (line 392) | pub fn autoload_kpm_modules(superkey: &Option<String>, event_filter: &st...
  function sc_pathhide_enable (line 521) | fn sc_pathhide_enable(key: &CStr, enable: bool) -> c_long {
  function sc_pathhide_add (line 535) | fn sc_pathhide_add(key: &CStr, path: &CStr) -> c_long {
  function sc_pathhide_clear (line 549) | fn sc_pathhide_clear(key: &CStr) -> c_long {
  function sc_pathhide_uid_mode (line 562) | fn sc_pathhide_uid_mode(key: &CStr, enable: bool) -> c_long {
  function sc_pathhide_filter_system (line 576) | fn sc_pathhide_filter_system(key: &CStr, enable: bool) -> c_long {
  function sc_pathhide_uid_add (line 590) | fn sc_pathhide_uid_add(key: &CStr, uid: i32) -> c_long {
  function sc_pathhide_uid_clear (line 604) | fn sc_pathhide_uid_clear(key: &CStr) -> c_long {
  function sc_netisolate_enable (line 617) | fn sc_netisolate_enable(key: &CStr, enable: bool) -> c_long {
  function sc_netisolate_uid_add (line 631) | fn sc_netisolate_uid_add(key: &CStr, uid: i32) -> c_long {
  function sc_netisolate_uid_clear (line 645) | fn sc_netisolate_uid_clear(key: &CStr) -> c_long {
  function apply_netisolate (line 658) | pub fn apply_netisolate(superkey: &Option<String>) {
  function apply_pathhide (line 714) | pub fn apply_pathhide(superkey: &Option<String>) {
  function sc_uts_set (line 817) | fn sc_uts_set(key: &CStr, release: Option<&CStr>, version: Option<&CStr>...
  function sc_uts_reset (line 840) | fn sc_uts_reset(key: &CStr) -> c_long {
  function apply_uts_spoof (line 853) | pub fn apply_uts_spoof(superkey: &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_dir_with_perms (line 43) | pub fn ensure_dir_with_perms(dir: &Path, parent: &Path, mode: u32) -> Re...
  function ensure_binary (line 59) | pub fn ensure_binary<T: AsRef<Path>>(path: T) -> Result<()> {
  function get_work_dir (line 64) | pub fn get_work_dir() -> &'static str {
  function getprop (line 69) | pub fn getprop(prop: &str) -> Option<String> {
  function getprop (line 74) | pub fn getprop(_prop: &str) -> Option<String> {
  function run_command (line 77) | pub fn run_command(
  function is_safe_mode (line 90) | pub fn is_safe_mode(superkey: Option<String>) -> bool {
  function switch_mnt_ns (line 116) | pub fn switch_mnt_ns(pid: i32) -> Result<()> {
  function switch_cgroup (line 131) | fn switch_cgroup(grp: &str, pid: u32) {
  function switch_cgroups (line 143) | pub fn switch_cgroups() {
  function umask (line 158) | pub fn umask(mask: u32) {
  function umask (line 163) | pub fn umask(_mask: u32) {
  function has_magisk (line 167) | pub fn has_magisk() -> bool {
  function get_tmp_path (line 170) | pub fn get_tmp_path() -> &'static str {

FILE: app/src/main/cpp/apjni.cpp
  function jboolean (line 17) | jboolean nativeReady(JNIEnv *env, jobject /* this */, jstring super_key_...
  function jlong (line 27) | jlong nativeKernelPatchVersion(JNIEnv *env, jobject /* this */, jstring ...
  function jstring (line 35) | jstring nativeKernelPatchBuildTime(JNIEnv *env, jobject /* this */, jstr...
  function jlong (line 45) | jlong nativeSu(JNIEnv *env, jobject /* this */, jstring super_key_jstr, ...
  function jint (line 63) | jint nativeSetUidExclude(JNIEnv *env, jobject /* this */, jstring super_...
  function jint (line 70) | jint nativeGetUidExclude(JNIEnv *env, jobject /* this */, jstring super_...
  function jlong (line 77) | jlong nativeSetNewAppProfileMode(JNIEnv *env, jobject /* this */, jstrin...
  function jint (line 88) | jint nativeGetNewAppProfileMode(JNIEnv *env, jobject /* this */, jstring...
  function jintArray (line 100) | jintArray nativeSuUids(JNIEnv *env, jobject /* this */, jstring super_ke...
  function jobject (line 123) | jobject nativeSuProfile(JNIEnv *env, jobject /* this */, jstring super_k...
  function jlong (line 147) | jlong nativeLoadKernelPatchModule(JNIEnv *env, jobject /* this */, jstri...
  function jobject (line 161) | jobject nativeControlKernelPatchModule(JNIEnv *env, jobject /* this */, ...
  function jlong (line 186) | jlong nativeUnloadKernelPatchModule(JNIEnv *env, jobject /* this */, jst...
  function jlong (line 199) | jlong nativeKernelPatchModuleNum(JNIEnv *env, jobject /* this */, jstrin...
  function jstring (line 211) | jstring nativeKernelPatchModuleList(JNIEnv *env, jobject /* this */, jst...
  function jstring (line 225) | jstring nativeKernelPatchModuleInfo(JNIEnv *env, jobject /* this */, jst...
  function jlong (line 239) | jlong nativeGrantSu(JNIEnv *env, jobject /* this */, jstring super_key_j...
  function jlong (line 251) | jlong nativeRevokeSu(JNIEnv *env, jobject /* this */, jstring super_key_...
  function jstring (line 258) | jstring nativeSuPath(JNIEnv *env, jobject /* this */, jstring super_key_...
  function jboolean (line 271) | jboolean nativeResetSuPath(JNIEnv *env, jobject /* this */, jstring supe...
  function jlong (line 280) | jlong nativeUtsSet(JNIEnv *env, jobject /* this */, jstring super_key_jstr,
  function jlong (line 295) | jlong nativeUtsReset(JNIEnv *env, jobject /* this */, jstring super_key_...
  function jlong (line 306) | jlong nativePathHideAdd(JNIEnv *env, jobject /* this */, jstring super_k...
  function jlong (line 318) | jlong nativePathHideRemove(JNIEnv *env, jobject /* this */, jstring supe...
  function jstring (line 330) | jstring nativePathHideList(JNIEnv *env, jobject /* this */, jstring supe...
  function jlong (line 342) | jlong nativePathHideClear(JNIEnv *env, jobject /* this */, jstring super...
  function jlong (line 353) | jlong nativePathHideEnable(JNIEnv *env, jobject /* this */, jstring supe...
  function jlong (line 364) | jlong nativePathHideStatus(JNIEnv *env, jobject /* this */, jstring supe...
  function jlong (line 375) | jlong nativePathHideUidAdd(JNIEnv *env, jobject /* this */, jstring supe...
  function jlong (line 385) | jlong nativePathHideUidRemove(JNIEnv *env, jobject /* this */, jstring s...
  function jstring (line 395) | jstring nativePathHideUidList(JNIEnv *env, jobject /* this */, jstring s...
  function jlong (line 407) | jlong nativePathHideUidClear(JNIEnv *env, jobject /* this */, jstring su...
  function jlong (line 417) | jlong nativePathHideUidMode(JNIEnv *env, jobject /* this */, jstring sup...
  function jlong (line 427) | jlong nativePathHideFilterSystem(JNIEnv *env, jobject /* this */, jstrin...
  function jlong (line 437) | jlong nativeNetIsolateEnable(JNIEnv *env, jobject /* this */, jstring su...
  function jlong (line 447) | jlong nativeNetIsolateStatus(JNIEnv *env, jobject /* this */, jstring su...
  function jlong (line 457) | jlong nativeNetIsolateUidAdd(JNIEnv *env, jobject /* this */, jstring su...
  function jlong (line 467) | jlong nativeNetIsolateUidRemove(JNIEnv *env, jobject /* this */, jstring...
  function jstring (line 477) | jstring nativeNetIsolateUidList(JNIEnv *env, jobject /* this */, jstring...
  function jlong (line 489) | jlong nativeNetIsolateUidClear(JNIEnv *env, jobject /* this */, jstring ...
  function jstring (line 499) | jstring nativeSuAuditList(JNIEnv *env, jobject /* this */, jstring super...
  function jlong (line 542) | jlong nativeSuAuditClear(JNIEnv *env, jobject /* this */, jstring super_...
  function JNIEXPORT (line 553) | 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/security.cpp
  function bytesToHex (line 26) | std::string bytesToHex(JNIEnv *env, jbyteArray bytes) {
  function getSignatureHash (line 41) | std::string getSignatureHash(JNIEnv *env, jobject context) {
  function getPackageName (line 85) | std::string getPackageName(JNIEnv *env, jobject context) {
  function jstring (line 97) | jstring nativeGetApiToken(JNIEnv *env, jobject thiz, jobject context) {

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_su_audit_nums (line 549) | static inline long sc_su_audit_nums(const char *key)
  function sc_su_audit_list (line 555) | static inline long sc_su_audit_list(const char *key, struct su_audit_ent...
  function sc_su_audit_clear (line 561) | static inline long sc_su_audit_clear(const char *key)
  function sc_bootlog (line 567) | static inline long sc_bootlog(const char *key)
  function sc_panic (line 573) | static inline long sc_panic(const char *key)
  function __sc_test (line 579) | static inline long __sc_test(const char *key, long a1, long a2, long a3)
  function sc_uts_set (line 593) | static inline long sc_uts_set(const char *key, const char *release, cons...
  function sc_uts_reset (line 606) | static inline long sc_uts_reset(const char *key)
  function sc_pathhide_add (line 613) | static inline long sc_pathhide_add(const char *key, const char *path)
  function sc_pathhide_remove (line 620) | static inline long sc_pathhide_remove(const char *key, const char *path)
  function sc_pathhide_list (line 627) | static inline long sc_pathhide_list(const char *key, char *out_buf, int ...
  function sc_pathhide_clear (line 634) | static inline long sc_pathhide_clear(const char *key)
  function sc_pathhide_enable (line 640) | static inline long sc_pathhide_enable(const char *key, int enable)
  function sc_pathhide_status (line 646) | static inline long sc_pathhide_status(const char *key)
  function sc_pathhide_uid_add (line 652) | static inline long sc_pathhide_uid_add(const char *key, int uid)
  function sc_pathhide_uid_remove (line 658) | static inline long sc_pathhide_uid_remove(const char *key, int uid)
  function sc_pathhide_uid_list (line 664) | static inline long sc_pathhide_uid_list(const char *key, char *out_buf, ...
  function sc_pathhide_uid_clear (line 671) | static inline long sc_pathhide_uid_clear(const char *key)
  function sc_pathhide_uid_mode (line 677) | static inline long sc_pathhide_uid_mode(const char *key, int enable)
  function sc_pathhide_filter_system (line 683) | static inline long sc_pathhide_filter_system(const char *key, int enable)
  function sc_netisolate_enable (line 689) | static inline long sc_netisolate_enable(const char *key, int enable)
  function sc_netisolate_status (line 695) | static inline long sc_netisolate_status(const char *key)
  function sc_netisolate_uid_add (line 701) | static inline long sc_netisolate_uid_add(const char *key, int uid)
  function sc_netisolate_uid_remove (line 707) | static inline long sc_netisolate_uid_remove(const char *key, int uid)
  function sc_netisolate_uid_list (line 713) | static inline long sc_netisolate_uid_list(const char *key, char *out_buf...
  function sc_netisolate_uid_clear (line 720) | static inline long sc_netisolate_uid_clear(const char *key)

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 94) | struct su_profile
  type su_audit_entry (line 103) | struct su_audit_entry

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 51) | ArrayList<PackageInfo> getInstalledPackagesAll(int flags) {
    method getInstalledPackagesAsUser (line 60) | List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
    class Stub (line 72) | class Stub extends IAPRootService.Stub {
      method getPackages (line 73) | @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 43) | public final class SuFilePathHandler implements WebViewAssetLoader.PathH...
    method SuFilePathHandler (line 87) | public SuFilePathHandler(@NonNull Context context, @NonNull File direc...
    method isAllowedInternalStorageDir (line 102) | private boolean isAllowedInternalStorageDir(@NonNull Context context) ...
    method handle (line 132) | @Override
    method getCanonicalDirPath (line 161) | public static String getCanonicalDirPath(@NonNull File file) throws IO...
    method getCanonicalFileIfChild (line 167) | public static File getCanonicalFileIfChild(@NonNull File parent, @NonN...
    method handleSvgzStream (line 177) | @NonNull
    method openFile (line 183) | public static InputStream openFile(@NonNull File file, @NonNull Shell ...
    method guessMimeType (line 198) | @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 164) | 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) {

FILE: fpd/src/main.rs
  constant VERSION (line 4) | const VERSION: &str = env!("CARGO_PKG_VERSION");
  constant HELP_URL (line 5) | const HELP_URL: &str = "https://fp.mysqil.com/";
  function print_version (line 7) | fn print_version() {
  function print_help (line 11) | fn print_help() {
  function usage (line 15) | fn usage() -> ! {
  function main (line 20) | fn main() {

FILE: fpd/src/prop_patch.rs
  constant RESETPROP (line 3) | const RESETPROP: &str = "/data/adb/ap/bin/resetprop";
  type PropItem (line 5) | struct PropItem {
  constant PATCH_LIST (line 10) | const PATCH_LIST: &[PropItem] = &[
  constant BOOT_KEYS (line 125) | const BOOT_KEYS: &[&str] = &["ro.bootmode", "ro.boot.bootmode", "vendor....
  function set_prop (line 127) | fn set_prop(key: &str, value: &str) {
  function get_prop (line 134) | fn get_prop(key: &str) -> Option<String> {
  function patch_boot_keys (line 150) | fn patch_boot_keys() {
  function run (line 160) | pub fn run() {

FILE: fpd/src/umount.rs
  constant MNT_DETACH (line 5) | const MNT_DETACH: libc::c_int = 2;
  function umount_lazy (line 7) | fn umount_lazy(path: &str) -> bool {
  function config_path (line 21) | fn config_path() -> PathBuf {
  function run (line 31) | pub fn run() -> u32 {
Condensed preview — 309 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (4,259K chars).
[
  {
    "path": ".gitattributes",
    "chars": 652,
    "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": 3782,
    "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": 368,
    "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/actions/setup-build-env/action.yml",
    "chars": 912,
    "preview": "name: Setup Build Environment\ndescription: Install all build dependencies (Java, Android SDK, Gradle, Rust toolchain)\n\nr"
  },
  {
    "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/workflows/CI_up.yml",
    "chars": 3159,
    "preview": "name: CI Upload to Telegram\n\non:\n  workflow_call:\n    inputs:\n      artifact_names:\n        required: true\n        type:"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 7217,
    "preview": "name: Build FolkPatch\n\non:\n  push:\n    branches: [ main, develop ]\n    paths: [ 'app/**', 'fpd/**', '.github/workflows/b"
  },
  {
    "path": ".gitignore",
    "chars": 380,
    "preview": "*.iml\n.gradle\n.idea\n.DS_Store\nbuild\ncaptures\n.cxx\n*.keystore\n*.jks\nkeystore.properties\nlocal.properties\nauth.properties\n"
  },
  {
    "path": "Build-Debug.bat",
    "chars": 813,
    "preview": "@echo off\r\nchcp 65001 > nul 2>&1\r\nsetlocal enabledelayedexpansion\r\n\r\necho [1/4] Entering apd directory...\r\ncd /d apd\r\nif"
  },
  {
    "path": "Build-Release.bat",
    "chars": 817,
    "preview": "@echo off\r\nchcp 65001 > nul 2>&1\r\nsetlocal enabledelayedexpansion\r\n\r\necho [1/4] Entering apd directory...\r\ncd /d apd\r\nif"
  },
  {
    "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": 2597,
    "preview": "<div align=\"center\">\n<img src='logo.png' width='500px' alt=\"FolkPatch logo\">\n\n[![Latest Release](https://img.shields.io/"
  },
  {
    "path": "README_EN.md",
    "chars": 4455,
    "preview": "<div align=\"center\">\n<img src='logo.png' width='500px' alt=\"FolkPatch logo\">\n\n[![Latest Release](https://img.shields.io/"
  },
  {
    "path": "README_JA.md",
    "chars": 3124,
    "preview": "<div align=\"center\">\n<img src='logo.png' width='500px' alt=\"FolkPatch logo\">\n\n[![Latest Release](https://img.shields.io/"
  },
  {
    "path": "apd/.gitignore",
    "chars": 15,
    "preview": "/target\n.cargo/"
  },
  {
    "path": "apd/Cargo.toml",
    "chars": 1881,
    "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": 2297,
    "preview": "use std::{env, fs::File, io::Write, path::Path, process::Command};\n\nfn get_git_version() -> Result<(u32, String), std::i"
  },
  {
    "path": "apd/src/apd.rs",
    "chars": 7117,
    "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": 721,
    "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": 317,
    "preview": "   ___  ___  __          ___  _   _____  ___        \n  / __\\/___\\/ /   /\\ /\\ / _ \\/_\\ /__   \\/ __\\ /\\  /\\\n / _\\ //  // /"
  },
  {
    "path": "apd/src/cli.rs",
    "chars": 10473,
    "preview": "use crate::{defs, event, lua, module, module_config, supercall, utils};\n#[cfg(target_os = \"android\")]\nuse android_logger"
  },
  {
    "path": "apd/src/defs.rs",
    "chars": 3554,
    "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": 15262,
    "preview": "use crate::sepolicy::get_policy_main;\nuse anyhow::{Context, Result};\nuse libc::SIGPWR;\nuse log::{info, warn};\nuse notify"
  },
  {
    "path": "apd/src/install_jq.sh",
    "chars": 497,
    "preview": "#!/system/bin/sh\n# Install jq binary to /data/adb/jq\n# This script is called during APatch installation\n\nJQ_DIR=\"/data/a"
  },
  {
    "path": "apd/src/installer.sh",
    "chars": 11540,
    "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/magic_mount.rs",
    "chars": 15550,
    "preview": "use std::{\n    cmp::PartialEq,\n    collections::{HashMap, hash_map::Entry},\n    fs,\n    fs::{DirEntry, FileType, create_"
  },
  {
    "path": "apd/src/main.rs",
    "chars": 324,
    "preview": "mod apd;\nmod assets;\nmod cli;\nmod defs;\nmod event;\nmod lua;\nmod magic_mount;\nmod metamodule;\nmod module;\nmod module_conf"
  },
  {
    "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": 28289,
    "preview": "use crate::sepolicy::get_policy_main;\nuse crate::{lua, module_config};\nuse anyhow::{Context, Result, anyhow, bail, ensur"
  },
  {
    "path": "apd/src/module_config.rs",
    "chars": 13410,
    "preview": "use std::{\n    collections::HashMap,\n    fs::{self, File},\n    io::{Read, Write},\n    path::{Path, PathBuf},\n};\n\nuse any"
  },
  {
    "path": "apd/src/package.rs",
    "chars": 10267,
    "preview": "use std::{\n    collections::{HashMap, HashSet},\n    fs::File,\n    io::{self, BufRead},\n    path::Path,\n    process::Comm"
  },
  {
    "path": "apd/src/pty.rs",
    "chars": 5043,
    "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/resetprop.rs",
    "chars": 6964,
    "preview": "use anyhow::{bail, Context, Result};\nuse clap::error::ErrorKind;\nuse clap::Parser;\nuse log::info;\nuse prop_rs_android::r"
  },
  {
    "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": 6595,
    "preview": "use anyhow::{Context, Result, bail};\nuse clap::Parser;\nuse policy::{SePolicy, format_statement_help};\nuse std::io::{self"
  },
  {
    "path": "apd/src/supercall.rs",
    "chars": 27018,
    "preview": "use std::{\n    ffi::{CStr, CString},\n    fs::File,\n    io::{self, Read},\n    process,\n    sync::{Arc, Mutex},\n};\n\nuse li"
  },
  {
    "path": "apd/src/utils.rs",
    "chars": 5137,
    "preview": "#[allow(unused_imports)]\nuse std::fs::{Permissions, set_permissions};\n#[cfg(unix)]\nuse std::os::unix::prelude::Permissio"
  },
  {
    "path": "app/.gitignore",
    "chars": 54,
    "preview": "/build\n/release/\n*.jks\n*.keystore\nkeystore.properties\n"
  },
  {
    "path": "app/build.gradle.kts",
    "chars": 15002,
    "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/libs/arm64-v8a/libkpatch.so.version",
    "chars": 6,
    "preview": "0.10.7"
  },
  {
    "path": "app/libs/arm64-v8a/libkptools.so.version",
    "chars": 6,
    "preview": "0.13.1"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 2443,
    "preview": "-dontwarn org.bouncycastle.jsse.BCSSLParameters\n-dontwarn org.bouncycastle.jsse.BCSSLSocket\n-dontwarn org.bouncycastle.j"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 7314,
    "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_flash.sh",
    "chars": 761,
    "preview": "#!/system/bin/sh\n#######################################################################################\n# APatch Boot I"
  },
  {
    "path": "app/src/main/assets/boot_patch.sh",
    "chars": 2998,
    "preview": "#!/system/bin/sh\n#######################################################################################\n# APatch Boot I"
  },
  {
    "path": "app/src/main/assets/boot_unpatch.sh",
    "chars": 1695,
    "preview": "#!/system/bin/sh\n#######################################################################################\n# APatch Boot I"
  },
  {
    "path": "app/src/main/assets/kpimg.version",
    "chars": 6,
    "preview": "0.13.1"
  },
  {
    "path": "app/src/main/assets/util_functions.sh",
    "chars": 14437,
    "preview": "#!/system/bin/sh\n#######################################################################################\n# Helper Functi"
  },
  {
    "path": "app/src/main/cpp/CMakeLists.txt",
    "chars": 1959,
    "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": 25548,
    "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/security.cpp",
    "chars": 5508,
    "preview": "#include \"security.hpp\"\n#include <string>\n#include <cstring>\n#include <strings.h>\n#include <android/log.h>\n#include <jni"
  },
  {
    "path": "app/src/main/cpp/security.hpp",
    "chars": 145,
    "preview": "#pragma once\n\n#include <jni.h>\n\n#define XSTR(s) STR(s)\n#define STR(s) #s\n\njstring nativeGetApiToken(JNIEnv *env, jobject"
  },
  {
    "path": "app/src/main/cpp/supercall.h",
    "chars": 20951,
    "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": 4131,
    "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 1\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/APatchApp.kt",
    "chars": 20038,
    "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": 9896,
    "preview": "package me.bmax.apatch\n\nimport android.os.Parcelable\nimport android.content.Context\nimport androidx.annotation.Keep\nimpo"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/data/ScriptInfo.kt",
    "chars": 314,
    "preview": "package me.bmax.apatch.data\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\nimport java.util.UUID\n\n@Par"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/services/RootServices.java",
    "chars": 2831,
    "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": 5849,
    "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": 86041,
    "preview": "package me.bmax.apatch.ui\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.net.Uri\ni"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/WebUIActivity.kt",
    "chars": 9368,
    "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": 3164,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.ColumnScope\nimport androidx.compose.found"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ExpressiveCard.kt",
    "chars": 2880,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.int"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ExpressiveSwitch.kt",
    "chars": 2168,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport andr"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/FilePicker.kt",
    "chars": 11119,
    "preview": "package me.bmax.apatch.ui.component\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport"
  },
  {
    "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/KpmAutoLoadConfig.kt",
    "chars": 9028,
    "preview": "package me.bmax.apatch.ui.component\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.compose.runt"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/LoadingIndicator.kt",
    "chars": 1253,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.found"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ModuleCardComponents.kt",
    "chars": 13089,
    "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": 6452,
    "preview": "package me.bmax.apatch.ui.component\n\nimport android.util.Log\nimport androidx.activity.compose.BackHandler\nimport android"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SectionHeader.kt",
    "chars": 659,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SegmentedControl.kt",
    "chars": 2633,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.fou"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SettingsItem.kt",
    "chars": 6514,
    "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/SplicedColumnGroup.kt",
    "chars": 6963,
    "preview": "package me.bmax.apatch.ui.component\n\nimport android.os.Build\nimport androidx.compose.animation.AnimatedVisibility\nimport"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/SplicedLazyColumn.kt",
    "chars": 3189,
    "preview": "package me.bmax.apatch.ui.component\n\nimport android.os.Build\nimport androidx.compose.animation.core.Spring\nimport androi"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ThemeColorPicker.kt",
    "chars": 8587,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.animation.core.Spring\nimport androidx.compose.animation.cor"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ThemeModeSelector.kt",
    "chars": 4885,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.animation.core.Spring\nimport androidx.compose.animation.cor"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/ToggleSettingCard.kt",
    "chars": 3486,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport andr"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/TwoColumnGrid.kt",
    "chars": 3628,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.found"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/UmountConfig.kt",
    "chars": 7112,
    "preview": "package me.bmax.apatch.ui.component\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.compose.runt"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/UpdateDialog.kt",
    "chars": 2129,
    "preview": "package me.bmax.apatch.ui.component\n\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.shap"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/WarningCard.kt",
    "chars": 3458,
    "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/component/chart/ChartUtils.kt",
    "chars": 2304,
    "preview": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics."
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/chart/ModulePieChart.kt",
    "chars": 4230,
    "preview": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation."
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/chart/StorageColumnChart.kt",
    "chars": 3402,
    "preview": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundat"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/chart/SystemAreaChart.kt",
    "chars": 4738,
    "preview": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.animation.core.Animatable\nimport androidx.compose.ani"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/component/chart/SystemLineChart.kt",
    "chars": 4985,
    "preview": "package me.bmax.apatch.ui.component.chart\n\nimport androidx.compose.animation.core.Animatable\nimport androidx.compose.ani"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/model/ApiMarketplaceItem.kt",
    "chars": 554,
    "preview": "package me.bmax.apatch.ui.model\n\nimport java.util.Locale\n\n/**\n * API Marketplace item data model\n * Represents a banner "
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/APM.kt",
    "chars": 87839,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.app.Activity.RESULT_OK\nimport android.content.ClipData\nimport android.c"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/AboutScreen.kt",
    "chars": 8794,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.clickable\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/ApiMarketplace.kt",
    "chars": 16150,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport me.bmax.apatch.util.ui.showToast\nimport androidx.compose.foundation.layout.*\nim"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/ApmBulkInstallScreen.kt",
    "chars": 17626,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.net.Uri\nimport android.provider.OpenableColumns\nimport android.content."
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/AppProfile.kt",
    "chars": 9333,
    "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/screen/BannerApiService.kt",
    "chars": 9437,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.content.Context\nimport android.util.Log\nimport kotlinx.coroutines.Dispa"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/BottomBarDestination.kt",
    "chars": 2598,
    "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": 6329,
    "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": 65721,
    "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/HomeCircle.kt",
    "chars": 28097,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.content.Context\nimport android.os.Build\nimport android.system.Os\nimport"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/HomeStats.kt",
    "chars": 30976,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.remembe"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/HomeV2.kt",
    "chars": 16136,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.remembe"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/HomeV3.kt",
    "chars": 23605,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.os.Build\nimport androidx.compose.foundation.layout.*\nimport androidx.co"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/HomeV4.kt",
    "chars": 48074,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.os.BatteryManager\nimport android.os.Build\nimport android.system.Os\nimpo"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/Install.kt",
    "chars": 8866,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.net.Uri\nimport android.os.Environment\nimport android.content.Intent\nimp"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/InstallModeSelect.kt",
    "chars": 17831,
    "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": 58718,
    "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/KpmAutoLoadConfigScreen.kt",
    "chars": 30571,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.act"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/MyThemesScreen.kt",
    "chars": 13157,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.net.Uri\nimport androidx.compose.foundation.clickable\nimport androidx.co"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/OnlineKPMScreen.kt",
    "chars": 9463,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.content.Context\nimport me.bmax.apatch.util.ui.showToast\nimport androidx"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/OnlineModuleScreen.kt",
    "chars": 9265,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.content.Context\nimport me.bmax.apatch.util.ui.showToast\nimport androidx"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/OnlineScriptScreen.kt",
    "chars": 10785,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.d"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/Patches.kt",
    "chars": 30835,
    "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/ScriptExecutionLogScreen.kt",
    "chars": 13422,
    "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/ScriptLibrary.kt",
    "chars": 22107,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.content.SharedPreferences\nimport android.net.Uri\nimport androidx.activi"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/Settings.kt",
    "chars": 15991,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.click"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/SuAuditLogScreen.kt",
    "chars": 16126,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.compose.animation.Crossfade\nimport androidx.compose.foundation.backgro"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/SuperUser.kt",
    "chars": 33194,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.act"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/ThemeStore.kt",
    "chars": 27607,
    "preview": "package me.bmax.apatch.ui.screen\n\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.compose.foundatio"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/AppearanceSettings.kt",
    "chars": 141886,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport android.app.Activity\nimport android.content.ActivityNotFoundException\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/AppearanceSettingsScreen.kt",
    "chars": 3812,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/BackupSettings.kt",
    "chars": 15658,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport android.content.Intent\nimport me.bmax.apatch.util.ui.showToast\nimport "
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/BackupSettingsScreen.kt",
    "chars": 3532,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/BehaviorSettings.kt",
    "chars": 13220,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose."
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/BehaviorSettingsScreen.kt",
    "chars": 3603,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/FunctionSettings.kt",
    "chars": 48890,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose."
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/FunctionSettingsScreen.kt",
    "chars": 25406,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/GeneralSettings.kt",
    "chars": 68761,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.net."
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/GeneralSettingsScreen.kt",
    "chars": 5162,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/LanguagePickerScreen.kt",
    "chars": 7573,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.compose.found"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/ModuleSettings.kt",
    "chars": 6533,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/ModuleSettingsScreen.kt",
    "chars": 3485,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/MultimediaSettings.kt",
    "chars": 60124,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport android.content.ActivityNotFoundException\nimport android.net.Uri\nimpor"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/MultimediaSettingsScreen.kt",
    "chars": 3079,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/SecuritySettings.kt",
    "chars": 7503,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.biometric.BiometricPrompt\nimport androidx.compose.foundation."
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/SecuritySettingsScreen.kt",
    "chars": 3416,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/screen/settings/SettingsShared.kt",
    "chars": 233,
    "preview": "package me.bmax.apatch.ui.screen.settings\n\nfun shouldShow(searchText: String, vararg texts: String?): Boolean {\n    if ("
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/AmberTheme.kt",
    "chars": 7916,
    "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/BackgroundConfig.kt",
    "chars": 43054,
    "preview": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.net.Uri\nimport android.os.Environment\nimp"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/BackgroundLayer.kt",
    "chars": 12106,
    "preview": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.compose.foundatio"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/BackupConfig.kt",
    "chars": 1748,
    "preview": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport androidx.compose.runtime.getValue\nimport androidx"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/BlueGreyTheme.kt",
    "chars": 7922,
    "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": 7915,
    "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": 7916,
    "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/CardManage.kt",
    "chars": 312,
    "preview": "package me.bmax.apatch.ui.theme\n\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.runtime.Composab"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/CyanTheme.kt",
    "chars": 7914,
    "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": 7927,
    "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": 7926,
    "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/FontConfig.kt",
    "chars": 5912,
    "preview": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.graphics.Typeface\nimport android.net.Uri\n"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/GreenTheme.kt",
    "chars": 7916,
    "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": 7918,
    "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/InkWashTheme.kt",
    "chars": 7921,
    "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": 7925,
    "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": 7926,
    "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": 7914,
    "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/MusicConfig.kt",
    "chars": 5160,
    "preview": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.Log\nimport an"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/OrangeTheme.kt",
    "chars": 7918,
    "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": 7914,
    "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": 7918,
    "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": 7912,
    "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": 7918,
    "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/SoundEffectConfig.kt",
    "chars": 9210,
    "preview": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.compose.runtime."
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/TealTheme.kt",
    "chars": 7915,
    "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": 11260,
    "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/ThemeManager.kt",
    "chars": 46373,
    "preview": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.Log\nimport an"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/Type.kt",
    "chars": 1764,
    "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/VibrationConfig.kt",
    "chars": 1670,
    "preview": "package me.bmax.apatch.ui.theme\n\nimport android.content.Context\nimport androidx.compose.runtime.getValue\nimport androidx"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/theme/YellowTheme.kt",
    "chars": 7918,
    "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": 12716,
    "preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.SharedPreferences\nimport android.os.SystemClock\nimport andro"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/ApiMarketplaceViewModel.kt",
    "chars": 7236,
    "preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.co"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/DashboardViewModel.kt",
    "chars": 19186,
    "preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content"
  },
  {
    "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": 3543,
    "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/OnlineKPMViewModel.kt",
    "chars": 3887,
    "preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.co"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/OnlineModuleViewModel.kt",
    "chars": 5620,
    "preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.co"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/OnlineScriptViewModel.kt",
    "chars": 3746,
    "preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.util.Log\nimport androidx.compose.runtime.getValue\nimport androidx.co"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/PatchesViewModel.kt",
    "chars": 29325,
    "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/ScriptLibraryViewModel.kt",
    "chars": 5207,
    "preview": "package me.bmax.apatch.ui.viewmodel\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/viewmodel/SuperUserViewModel.kt",
    "chars": 18470,
    "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/viewmodel/ThemeStoreViewModel.kt",
    "chars": 19380,
    "preview": "package me.bmax.apatch.ui.viewmodel\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.Log\nimpor"
  },
  {
    "path": "app/src/main/java/me/bmax/apatch/ui/webui/AppIconUtil.kt",
    "chars": 1708,
    "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/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": 4951,
    "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": 8789,
    "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": 7995,
    "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": 25535,
    "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": 6473,
    "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/AppData.kt",
    "chars": 5105,
    "preview": "package me.bmax.apatch.util\n\nimport android.os.SystemClock\nimport android.util.Log\nimport kotlinx.coroutines.Dispatchers"
  }
]

// ... and 109 more files (download for full content)

About this extraction

This page contains the full source code of the matsuzaka-yuki/FolkPatch GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 309 files (3.7 MB), approximately 986.9k tokens, and a symbol index with 700 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.

Copied to clipboard!